sorenandersen.com

Dynamic configuration with Lambda, Middy and the SSM Parameter Store

August 17, 2020

Updated 22/8: Middy upgraded to 1.x with an independent package structure.

Environment variables are indispensable for static configuration and references to intra-service resources that are owned and used only by a single service, such as DynamoDB table names.

For dynamic configuration - settings or variables that we want to be able to change without redeploying - we can use AWS SSM Parameter Store.

With Middy’s ssm middleware it’s a breeze to read and cache values from the store, as the following example outlines:

index.js

const middy = require('@middy/core')
const ssm = require('@middy/ssm')
const { serviceName, stage, deploy_time_value } = process.env

module.exports.handler = middy(async () => {
  return {
    deploy_time_value,
    configurable: process.env.someProp,
  }
}).use(
  ssm({
    cache: true,
    cacheExpiryInMillis: 1 * 60 * 1000, // For testing purposes just one minute cache time
    names: {
      config: `/${serviceName}/${stage}/index/config`,
    },
    onChange: () => {
      const config = JSON.parse(process.env.config)
      process.env.someProp = config.someProp
    },
  }),
)

This example reads parameter with name /demo-lambda-middy-ssm-param/dev/index/config and pulls off the someProp property. The value is cached using an environment variable and the onChange handler refreshes it from the store whenever the configured cache time expires.

We’ve stored the value as a JSON document so that we can easily add as many properties as needed.

{
  "someProp": "Value configurable in Parameter Store and applied dynamically via Middy middleware."
}

To read from the store the Lambda function needs permissions:.

serverless.yml

service: ...
provider:
  name: aws
  runtime: nodejs12.x
  ...
  environment:
    serviceName: ${self:service}
    stage: ${self:provider.stage}

functions:
  index:
    ...
    environment:
      deploy_time_value: "Changeable only by redeploying."
    iamRoleStatements:
      - Effect: Allow
        Action: ssm:GetParameters*
        Resource: arn:aws:ssm:#{AWS::Region}:#{AWS::AccountId}:parameter/${self:service}/${self:provider.stage}/index/config

plugins:
  - serverless-iam-roles-per-function
  - serverless-pseudo-parameters

I’ve put together a runnable example here.

Secrets should always be encrypted

Please note that this example is only recommended for non-sensitive information. Secrets, such as passwords and API keys, should be loaded from either SSM Parameter Store (type SecureString) or Secrets Manager, decrypted, and then stored in the application context - not as an environment variable.

SSM with the Serverless Framework

The Serverless Framework has built-in support for reading SSM parameters and assigning them to variables. However, the values are fetched at deployment time and baked into the generated CloudFormation template so there’s no way of reloading them on the fly without redeployment.

And we definately shouldn’t load secrets this way.

References

I learned this by attending Yan Cui’s “Production-Ready Serverless” workshop. I really recommend this workshop for anyone wanting to up their AWS serverlesss skills.


Søren Andersen

Digital garden of Søren Andersen.
Posts on tech that I use and learn.