Secure Configs with AWS SSM Parameter Store and Rails on Lambda

Part of an series while developing, Lamby - Simple Rails & AWS Lambda Integration using Rack 🚂🐑.

Most Rails applications require over a dozen environment variables to configure themselves or use popular gems. Most notable is the DATABASE_URL or others like SECRET_KEY_BASE which is used by Rails itself to sign encrypted cookies for sessions.

There are numerous ways to configure environment variables ranging from quick and dirty commits to GitHub all the way to a strict separation of config from code using countless methods to achieve a proper Twelve-Factor app. This document speaks to one we think fits nicely with AWS and Lambda. But first, some alternatives:

AWS SMS Parameter Store & Dotenv

AWS Systems Manager Parameter Store provides secure, hierarchical storage for configuration data management and secrets management and is offered at no additional charge.

To say Parameter Store is versatile is an understatement. It can be used in CloudFormation templates to the CLI and has unlimited ways to configure IAM for data access. Both Lamby and this guide below follow AWS guides-lines for Organizing Parameters into Hierarchies and a technique called Labeling Parameters. If Parameter store is new to you, please take some time to read up on it afterward.

Overview

Lamby supports a few ways to integrate with Parameter Store, but we suggest using our Lamby::SsmParameterStore object with Dotenv. The end result below will write out a .env.RAILS_ENV file within your deploy script and ensure it is included in your Lambda package.

Quick Start

In your Gemfile add both Dotenv and the AWS SDK for SMS.

gem 'dotenv-rails'
gem 'aws-sdk-ssm'

In your Rails config/application.rb file, add Dotenv's rails-now require right after rails is required. This ensures Dotenv works when using `rails server.

require_relative 'boot'

require "rails"
require 'dotenv/rails-now'
# ...

Change your Lamby app.rb to require and load Dotenv after the boot require and before requiring Lamby. This will load your environment variables when your function is deployed or using SAM's development server locally.

require_relative 'config/boot'
require 'dotenv' ; Dotenv.load ".env.#{ENV['RAILS_ENV']}"
require 'lamby'
require_relative 'config/application'
require_relative 'config/environment'
# ...

Add this to your bin/deploy script right before the sam package lines. Replace myapp with the name of your app or completely customize the path to match your own hierarchal structure.

./bin/rake lamby:ssm:dotenv \
  LAMBY_SSM_PARAMS_PATH="/config/${RAILS_ENV}/myapp/env"
mv ".env.${RAILS_ENV}" ./.aws-sam/build/RailsFunction

Lamby Support

The above lamby:ssm:dotenv rake task is made possible with our Lamby:: SsmParameterStore class. But to understand how the above code works, we first need to create a few SSM Parameters. We can use the AWS CLI for that. Here is what we did for the Lamby Demo app's SECRET_KEY_BASE env var.

aws ssm put-parameter \
  --name "/config/production/myapp/env/SECRET_KEY_BASE" \
  --type "SecureString" \
  --value $(rails secret)

The name option is our path and is an example hierarchy, you can come up with your own. But here is how we have structured ours.

Again, feel free to come up with your own path hierarchies. Whatever is found in the path you pass via LAMBY_SSM_PARAMS_PATH will be converted to entries in your Dotenv file.

Labeling

Parameters have a history. The last version created is found by default. However, if you want to take advantage of labels and for example use a live label migrate from one env to another in a controlled way, use the LAMBY_SSM_PARAMS_LABEL variable.

./bin/rake lamby:ssm:dotenv \
  LAMBY_SSM_PARAMS_PATH="/config/${RAILS_ENV}/myapp/env" \
  LAMBY_SSM_PARAMS_LABEL="live"

Advanced Usage

The Lamby:: SsmParameterStore can be used at runtime too when your Lambda starts, assuming you have written out the needed Policies in your template.yaml file. This example below will write directly to the ENV after loading all your parameters.

path = "/config/#{RAILS_ENV}/myapp/env"
envs = Lamby::SsmParameterStore path, label: 'live'
envs.to_env

More Resources

by Ken Collins
AWS Serverless Hero