Storing Secrets in Serverless Apps
The general idea behind secrets is to store them outside of your codebase — don’t commit them to Git! And to make them available at runtime. There are many ways people tend to do this, some are less secure than others. This chapter is going to layout the best practice for storing secrets and managing them across multiple environments.
Store Secrets in AWS Parameter Store
AWS Systems Manager Parameter Store (SSM) is an AWS service that lets you store configuration data and secrets as key-value pairs in a central place. The values can be stored as plain text or as encrypted data. When stored as encrypted data, the value is encrypted on write using your AWS KMS key, and decrypted on read.
As an example, we are going to use SSM to store our Stripe secret key. Note that, Stripe gives us 2 keys: a live key and a test key. We are going to store:
- The live key in the Production account’s SSM console.
- The test key in the Development account’s SSM console.
First go in to your Production account, and go to your Systems Manager console.
Select Parameter Store from the left menu, and select Create parameter.
Fill in:
- Name: /stripeSecretKey/live
- Description: Stripe secret key - live
Select SecureString, and paste your live Stripe key in Value.
Scroll to the bottom and hit Create parameter.
The key is added.
Then, switch to your Development account, and repeat the steps to add the test Stripe key with:
- Name: /stripeSecretKey/test
- Description: Stripe secret key - test
Access SSM Parameter in Lambda
Now to use our SSM parameters, we need to let Lambda function know which environment it is running in. We are going to pass the name of the stage to Lambda functions as environment variables.
Update our serverless.yml
.
...
custom: ${file(../../serverless.common.yml):custom}
provider:
environment:
stage: ${self:custom.stage}
resourcesStage: ${self:custom.resourcesStage}
notePurchasedTopicArn:
Ref: NotePurchasedTopic
iamRoleStatements:
- ${file(../../serverless.common.yml):lambdaPolicyXRay}
- Effect: Allow
Action:
- ssm:GetParameter
Resource:
Fn::Join:
- ''
-
- 'arn:aws:ssm:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':parameter/stripeSecretKey/*'
...
We are granting Lambda functions permission to fetch and decrypt the SSM parameters.
Next we’ll add the parameter names in our config.js
.
const stage = process.env.stage;
const resourcesStage = process.env.resourcesStage;
const adminPhoneNumber = "+14151234567";
const stageConfigs = {
dev: {
stripeKeyName: "/stripeSecretKey/test"
},
prod: {
stripeKeyName: "/stripeSecretKey/live"
}
};
const config = stageConfigs[stage] || stageConfigs.dev;
export default {
stage,
resourcesStage,
adminPhoneNumber,
...config
};
The above code reads the current stage from the environment variable process.env.stage
, and selects the corresponding config.
- If the stage is
prod
, it exportsstageConfigs.prod
. - If the stage is
dev
, it exportsstageConfigs.dev
. - And if stage is
featureX
, it falls back to the dev config and exportsstageConfigs.dev
.
If you need a refresher on the structure of our config, refer to the Manage environment related config.
Now we can access the SSM value in our Lambda function.
import AWS from '../../libs/aws-sdk';
import config from "../../config";
// Load our secret key from SSM
const ssm = new AWS.SSM();
const stripeSecretKeyPromise = ssm
.getParameter({
Name: config.stripeKeyName,
WithDecryption: true
})
.promise();
export const handler = (event, context) => {
...
// Charge via stripe
const stripeSecretKey = await stripeSecretKeyPromise;
const stripe = stripePackage(stripeSecretKey.Parameter.Value);
...
};
By calling ssm.getParameter
with WithDecryption: true
, the value returned to you is decrypted and ready to be used.
Note that, you want to decrypt your secrets outside your Lambda function handler. This is because you only need to do this once per Lambda function invocation. Your secrets will get decrypted on the first invocation and you’ll simply use the same value in subsequent invocations.
So in summary, you want to store your sensitive data in SSM. Store the SSM parameter name in your config. When the Lambda function runs, use the environment it’s running in to figure out which SSM parameter to use. Finally, decrypt the secret outside your Lambda handler function.
Next, we’ll look at how we can setup our API Gateway custom domains to work across environments.
For help and discussion
Comments on this chapterIf you liked this post, please subscribe to our newsletter, give us a star on GitHub, and follow us on Twitter.