Your serverless function has a secret... maybe it's a password for a remote API, a private key, or signing certificate. These secrets have to be stored somewhere, and in the old days that usually meant just a plaintext config file on your server. Sure, you could encrypt it, but then you have to put the key on the server, and you haven't gained anything except a bit of obfuscation. Or you could use more complex schemes, like Hiera-Eyaml, which is a small improvement, but you've really just moved the threat to a different part of your infrastructure.
Using AWS KMS (Key Management Service) with AWS Lambdas offers a significant reduction in attack surface area. You get HSM-like functionality, without the high cost (starting at $1/month). In this model, there are only three ways the secret can be accessed:
- From inside the container running the Lambda function code
- By an IAM user with admin rights - and you protected these accounts with two-factor auth, right?
- Amazon screws up catastrophically
I believe (3) is important to acknowledge here - the risk doesn't go away, but is outsourced to an extremely mature, security-focused organization with a stellar track record and a lot at stake.
Here's a short example using the Serverless framework, and Node.js. Many other language and framework combinations are possible.
Here are the steps. Refer to the quickstart docs for Serverless, KMS, and aws-cli if any of these are unfamiliar.
- Create a KMS key in the AWS console, and make a note of its ARN.
- Generate some ciphertext with the following CLI command:
aws kms encrypt --key-id 'arn:aws:kms...' --plaintext 'secret'
- Add the ciphertext as an environment variable
- Add the ARN to the
awsKmsKeyArn
property in your serverless.yml
file
- Update your code with
kms.decrypt()
to access the secret
index.js
'use strict';
const aws = require('aws-sdk');
var kms = new aws.KMS();
exports.hello = (event, context, callback) => {
kms.decrypt({CiphertextBlob:
new Buffer(process.env.password, 'base64')},
(err, data) => {
var password = data.Plaintext.toString('ascii');
/*
* Do stuff with plaintext password
*/
callback(null, {
statusCode: 200,
body: JSON.stringify("hello"),
headers: {
'Content-Type': 'application/json',
},
});
});
};
serverless.yml
service: example
provider:
name: aws
region: us-west-2
functions:
hello:
handler: index.hello
events:
- http:
path: hello
method: get
awsKmsKeyArn: 'arn:aws:kms:us-west-2:123456789:key/80abc534-be62-992a-7ba1-e3a44d4271ac'
environment:
# password value below is KMS ciphertext
password: 'AQICAHiNrBUmBQhi496ozpFzP9BrHUALq+NAyE7Xsz6gRsiz0AGsJnI1cyYl57qLnxn1FDSkAAAAbTBrBgkqhkiG9w0BBwagXjBcAgEAMFcGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM8TW0l6r7UEi1soz9AgEQgCp4Vy6sWq1W7SXXmeaz3jRL/X5ax4E2Uw3N0KGztPwv9kGK8O2Nk56tM4Q='
runtime: nodejs6.10