The term "serverless" implies having no server and might encourage the enthusiastic to think, "No more patching. No more vulnerability management." While there may be some truth in this, going serverless has its own set of security concerns and in this article we explore serverless security from the perspective of dependencies.

Quick Overview on Serverless

Serverless is short for the term "serverless computing" and, also, a programming framework. Serverless computing is the concept of running your code in the cloud without managing servers. "Functions as a Service" (FaaS) is another term used to refer to serverless computing; it aligns with the other cloud terms: "Platform as a Service" (Paas), "Infrastructure as a Service" (IaaS), and "Software as a Service" (Saas). FaaS is the next layer below SaaS in cloud computing. There are serverless frameworks which provide a structured way to take advantage of serverless computing for specific cloud providers. Serverless, Chalice, Amplify, Amazon Web Services Serverless Application Model (SAM), and many others; reference the "15 Frameworks for Creating Serverless Apps" article by Gleekfare. This article will use the Serverless framework and Node.js in its examples.

Dependency Management

Dependencies are our groups of software your software requires to run correctly. Leveraging other software speeds up the development process, but inherently introduces security concerns. When you import another software package, it may import other software packages; dependencies might have dependencies.

Node Package Manager (NPM) is used to manage dependencies. When you want to add a package to your code, it will determine what packages are required to install the desired package and make the appropriate changes. This simplicity in package management makes it super easy to install many software packages, and it might increase the likelihood of introducing vulnerable software into your code base.

It is essential to consider the dependencies used in the desired software package. For example, your software needs to make an HTTP request. You can use the built-in http library, but the code looks cumbersome.

const options = {
    hostname: 'www.google.com',
    port: 80,
    path: '/upload',
    method: 'POST',
    headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(postData)
    }
};
const req = http.request(options, (res) => {
    res.setEncoding('utf8');
    res.on('data', (chunk) => {
        console.log(`data: ${chunk}`);  // Print the HTML for the Google homepage.
    });
    res.on('end', () => {
        console.log('No more data in response.');
    });
});

Example code from https://nodejs.org/api/http.html#http_http_request_url_options_callback.

You may choose to use the request library, which is easier to use than the http library.

const request = require('request');
request('http://www.google.com', function (error, response, body) {
    console.log('error:', error); // Print the error if one occurred
    console.log('statusCode:', response.statusCode); // Print the response status code if a response was received
    console.log('body:', body); // Print the HTML for the Google homepage.
});

Example code from https://www.npmjs.com/package/request.

Using the request library uses much fewer lines of code. Is it the best approach though? This library requires twenty other packages to work effectively; in total it has 49 dependencies when observing the dependency tree. It was last updated eight months ago, at the time of this writing.

Image generated using Anvaka.

The got library might be another alternative to the http library.

(async () => {
    try {
        const response = await got('sindresorhus.com');
        console.log(response.body);
        //=> '<!doctype html> ...'
    } catch (error) {
        console.log(error.response.body);
        //=> 'Internal server error ...'
    }
})();

Example code from https://www.npmjs.com/package/got.

Using this library is more complicated than the request library, but it has eleven dependencies (24 in total) and was last updated three months ago.

Image generated using Anvaka.

Vulnerability Management

Ideally, you would want to review each dependency to understand what your entire software contains. Practically, this might be inefficient depending on the number of packages you import. Instead, you can take advantage of the Node Package Manager (NPM) which comes with Node. It will audit all your software packages.

npm install request got
npm audit

                       === npm audit security report ===

found 0 vulnerabilities
 in 100 scanned packages

This CLI example assumes you have Node and NPM installed.

You might want to consider running a periodic audit of your packages or use a vendor which integrates auditing into your continuous integration pipeline.

The Serverless Framework Dependencies

This article focuses on the Serverless framework using Node. I installed the serverless package as a global package; this means it is accessible as a command line function and I do not have to include it in my software package. The serverless package has 40 dependencies, and it requires 355 software packages in total. My computer now has 355 entry points if any dependent package is vulnerable. I will want to update the serverless package periodically. Now I can take advantage of deploying my serverless function that uses request or got by issuing the following command in the command line interface: serverless deploy.

Image generated using Anvaka.

Conclusion

It is a good idea to review which packages you import, the dependency tree, and known vulnerabilities when writing your serverless application.

Before You Go

A Note from the Author

Join the “Black Hat Chronicles” fan group to get updates on my writing and an upcoming book. Visit https://goo.gl/forms/mtdRcj3vDJF3qkGo1 to join.

Stay secure, Miguel

View my linkedIn profile

The awesome image used to head this article is called 'Dog Guru' and it was created by Dojrek.