This article is part of #ServerlessSeptember. You'll find other helpful articles, detailed tutorials, and videos in this all-things-serverless content collection. New articles are published every day — that's right, every day — from community members and cloud advocates in the month of September.
Find out more about how Microsoft Azure enables your Serverless functions at https://docs.microsoft.com/azure/azure-functions/.
If you prefer videos, some of the content in this article is covered in talks I've given at a few ServerlessDays events (London 2018, Tel Aviv 2019).
I end up talking a good bit about serverless security. And while I haven't yet gotten the exact question from the title of this post, there are definitely some expectations that come up in these conversations. Yes, by choosing serverless, you have set a good baseline security posture. But serverless isn't some silver bullet that makes your app invulnerable. I think it's perfectly fair to ask: Do the servers even matter if they can hack your app?
Spoilers: they don’t. Our applications are more than our servers. They are collections of services. They are operational practices that we espouse. They are fallible in their own right.
The moment you say the words "cloud security," it's almost guaranteed that someone is going to bring up the "shared responsibility model," which describes how some aspects of security are burden of the cloud provider and some are burden of the cloud customer. Here's how I like to draw it:
There are still plenty of things we need to be concerned with when building serverless apps. And at times I think the hype cycle and some confusion about serverless has led folks to forget that this is the case. I've long been fascinated by the concept of "risk compensation." The idea there is that if people feel safer, they take greater risks. The classic example is that cyclists wearing helmets are more likely to ride more aggressively in traffic than those without. So what do you think happens when we keep talking about "serverless" in terms of what we don't have to be concerned with?
Common pitfalls in serverless
The Open Web Application Security Project (OWASP) publishes lists of the top 10 security vulnerabilities, and there's a scary trend that you see over the years: the list doesn't change much. That means we collectively are still really bad at these. And guess what? All of them still apply to serverless applications. The OWASP even has a handy interpretation for serverless. You may think you would never create such vulnerabilities, but that's the surest path to making them.
Let's look at some problems in action. Here's a nice little function that thanks someone for opening a pull request on GitHub:
module.exports = function (context, payload) {
if (payload.action != "opened") {
context.done();
return;
}
var comment = { "body": "Thank you for your contribution! We will get to it shortly." };
if (payload.pull_request) {
var pr = payload.pull_request;
context.log(pr.user.login, " submitted PR#", pr.number, ": ", pr.title);
SendGitHubRequest(pr.comments_url, comment, context); // posting a comment
}
context.done();
};
function SendGitHubRequest(url, requestBody, context) {
var request = require('request');
var githubCred = 'Basic ' + 'mattchenderson:8e254ed4';
request({
url: url,
method: 'POST',
headers: {
'User-Agent': 'mattchenderson',
'Authorization': githubCred
},
json: requestBody
}, function (error, response, body) {
if (error) {
context.log(error);
} else {
context.log(response.statusCode, body);
}
});
}
Do you see it? Check where I assign githubCred
. I seem to have left some credentials in my source code! This is bad, but certainly it only happens to other people, right? Well, it's way more common than you think, but there are all sorts of tools to help with this. And it's worth calling out that this can happen with any traditional application. So why bring it up in the context of serverless?
Well, most solutions you will see here rely on identity and permissions. Permissions are tough, especially when you're talking about them at the granularity of a function. In Azure, we have this concept called a “function app,” which is just a collection of functions that share code, config, or management. So if I group functions with different permission needs, I could end up granting all permissions to all functions. That’s violating the principle of least privilege, and this kind of overgrouping is extremely common. I should be splitting things up into different function apps where I can.
So how about this code? What's wrong here?
module.exports = function (context, req) {
if (req.body && req.body.name)) {
context.res = {
status: 202
};
context.bindings.outQueueMessage = {
action: "delete",
target: req.body.name
};
}
else {
context.res = {
status: 400,
body: "Please pass a name in the request body"
};
}
context.done();
};
I would say that it's the fact that I'm just taking the input and passing the data downstream via a queue (via the target
property assignment). That's a problem because right now we don't know what's downstream. Would you like to know? It's this:
var Connection = require('tedious').Connection;
var config = {
//... Get from env vars
};
var connection = new Connection(config);
connection.on('connect', function(err) {
console.log("Connected");
});
//...
module.exports = function (context, myQueueItem) {
if (myQueueItem.action === "delete") {
let request = new Request("DELETE FROM Inventory WHERE ItemName='" + myQueueItem.target + "';", function(err) {
if (err) {
console.log(err);}
});
connection.execSql(request);
}
context.done();
};
Why, that's a SQL injection vulnerability! This injection may seem extreme, but imagine this is a third-party dependency problem, or really anything else. Yes, this function should absolutely be validating input, but I think it's also fair to question the function that came before it. It could have been a better steward of the overall application by making sure it wasn't introducing malicious payloads for someone else to fall prey to.
This is an uncomfortable idea for a lot of folks, and you sometimes see blame flying between teams deploying microservices into one application. If you're ever in that situation, please just remember the phrase "it's not my fault, but it is my responsibility." At the end of the day, it's a team effort, and application security is everyone's responsibility.
More secure serverless
So what do we do? How do we set ourselves up for success with serverless security? It's an interesting question, because we don't have all of the same tools we might be used to from the serverfull days. That said, serverless platforms have evolved in terms of security features and networking options, and the serverless security tooling ecosystem has been maturing nicely.
At the end of the day, the main adjustments I see teams needing to make are the same kinds of things you find in distributed systems conversations. But for a lot of teams, serverless is their big introduction to more distributed architectures. Observability is key to this, and there's lots of great conversations going on around serverless observability. If you're using Azure, be sure to set up Application Insights for your apps.
And in general, please leverage any of the security features built into your cloud provider! I keep finding folks that don't even know about things like Azure Security Center or managed identities. Not everything is free, but there are a ton of capabilities that are, and some of them are as simple as flipping a switch.
Also check what things can be plugged in to your DevOps pipeline. That's a fantastic place to centralize all sorts of security enforcement for your organization, and again, there are lots of things you can just flip a switch to enable. In general, there have been some really cool developments in the DevSecOps space. I'm seeing more and more applications of serverless technologies as automation tools to help teams with their security posture. This is a very interesting trend, and I think there's a lot of promise here.
And it's not the glamorous answer you might be hoping for, but the truth is that diligence and threat modeling are always good practices. We probably haven't been doing enough of that even in our existing apps, if we're being honest. I know I've been guilty of this. The most important thing is to be intentional about development. At its core, serverless is about productivity, but that doesn't mean we get to cut corners. Maybe some of the time we recover from certain operations can just be put toward improving our security practices.
Keep learning
There's tons of great content out there if you want to learn more. I've found the following to be particularly helpful:
- Practical Auth in a Serverless World by Andreas Grimm
- Getting Started with AppSec by Tanya Janca
- Many-Faced Threats to Serverless Security by Yan Cui
- Calling $@*! on Security: A Cultural Challenge by Mark Nunnikhoven
I've also found both the serverless and DevSecOps communities to be super approachable in general. I would certainly check out local meetups and user groups if you can! It's one of the best ways to get better with these technologies, and you'll probably meet some great people as a result!