Managing application secrets on AWS with SSM and Chamber

The better alternative for Vault and AWS Secret Manager

April 10, 2018
Contact Us
Weekly Shorts are topics we discuss in our weekly remote meeting related to recent work we have done with our customers
Managing application secrets on AWS with SSM and Chamber

Where do you store your application secrets?
That’s the first question I ask many of our clients at ProdOps.I usually get one of two answers, either they thought I was talking about a user password manager like 1Password or LastPass, or I get really weird answers like Google Drive, AWS S3, environment variables in the code etc.

So I sharpen my question, asking where are API tokens are stored, RSA encrypted keys, and any other authentication methods our applications use to interact with external services like GCP, AWS, NPM, etc etc etc…

The untold truth is that 85% of the software companies store their application secrets hardcoded, or the better solution — a secret store in their CI server e.g TravisCI, Jenkins, Drone and the like.

The few saints that do take care of their security like pros, do it with Hashicorp’s Vault, which is an awesome product, and a most suitable solution for this exact use case.

But, Vault is somewhat tedious, to begin with, it requires a lot of API work to configure it so that it’s really compartementalizing your services from one another while using slim access policies that only allow read permissions, and from the single location that the application really needs.

Don’t get me wrong, I’m all in for Vault. I think it’s the best at its field of encrypting secrets and integrating with software applications. I wrote about it myself and I still stand behind my words. However, Vault requires to work to deploy it correctly and to configure it with its internal policies and access management. I recently found out that AWS provide it as a free service of their own as part of their Systems Manager. It’s called “Parameter Store”. Being an AWS oriented shop, we decided to implement a solution that is natively integrated to IAM, allowing us to control access from one central place, not only to users and cloud deployed services but to applications’ secrets as well.

Rumor has it that the AWS SSM Parameter Store is actually based on Vault, and when the guys over at Hashicorp heard about it, they obviously were not too excited about a competitor running their application as a service.

So? I still need to interact with an API, define roles and policies, how is that any better?

Well, not exactly. Yes, you still need to handle access management, but it’s done via IAM. So assuming you’re already handling your cloud resources’ access via policies and roles, you can apply these with Parameter Store.
The encryption is done using AWS KMS, so you get the encryption out of the box too.

And the API interaction to fetch and store parameters and secrets?
Good question! For that, I give you… “Chamber”
“Chamber is a tool for managing secrets. Currently it does so by storing secrets in SSM Parameter Store, an AWS service for storing secrets.”
- github.com/segmentio/chamber

Chamber is a very convenient tool, that gives you very easy access to the Parameter Store, and it structures your work with KMS. It requires a KMS key and an alias (which takes around 50 seconds to set up) and you’ve got yourself a read/right abstraction layer on top of the API; it’s as easy as:

#gist:<link rel="stylesheet" href="https://assets-cdn.github.com/assets/gist-embed-87673c31a5b37b5e6556b63e1081ebbc.css"><div id=\"gist90398253\" class=\"gist\">\n <div class=\"gist-file\">\n <div class=\"gist-data\">\n <div class=\"js-gist-file-update-container js-task-list-container file-box\">\n <div id=\"file-chmber01\" class=\"file\">\n \n\n <div itemprop=\"text\" class=\"blob-wrapper data type-text\">\n <table class=\"highlight tab-size js-file-line-container\" data-tab-size=\"8\">\n <tr>\n <td id=\"file-chmber01-L1\" class=\"blob-num js-line-number\" data-line-number=\"1\"><\/td>\n <td id=\"file-chmber01-LC1\" class=\"blob-code blob-code-inner js-file-line\"># Write:<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber01-L2\" class=\"blob-num js-line-number\" data-line-number=\"2\"><\/td>\n <td id=\"file-chmber01-LC2\" class=\"blob-code blob-code-inner js-file-line\">$ chamber write <service> <key> <value><\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber01-L3\" class=\"blob-num js-line-number\" data-line-number=\"3\"><\/td>\n <td id=\"file-chmber01-LC3\" class=\"blob-code blob-code-inner js-file-line\">\n<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber01-L4\" class=\"blob-num js-line-number\" data-line-number=\"4\"><\/td>\n <td id=\"file-chmber01-LC4\" class=\"blob-code blob-code-inner js-file-line\"># Read:<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber01-L5\" class=\"blob-num js-line-number\" data-line-number=\"5\"><\/td>\n <td id=\"file-chmber01-LC5\" class=\"blob-code blob-code-inner js-file-line\">$ chamber read <service> <key><\/td>\n <\/tr>\n<\/table>\n\n\n <\/div>\n\n <\/div>\n<\/div>\n\n <\/div>\n <div class=\"gist-meta\">\n <a href=\"https://gist.github.com/seanroisentul/dc60dbaa67c38e8c289a926366acc71a/raw/66daba46db1b3de741fd5012b41fe37fd71c0dee/Chmber01\" style=\"float:right\">view raw<\/a>\n <a href=\"https://gist.github.com/seanroisentul/dc60dbaa67c38e8c289a926366acc71a#file-chmber01\">Chmber01<\/a>\n hosted with ❤ by <a href=\"https://github.com\">GitHub<\/a>\n <\/div>\n <\/div>\n<\/div>\n

Many other options are available but these are the core of the ability to easily interact of the system securly.Assuming your services are attached to IAM roles, you simply add permissions to read/write values to SSM Parameter Store, here’s an example:

#gist:<link rel="stylesheet" href="https://assets-cdn.github.com/assets/gist-embed-87673c31a5b37b5e6556b63e1081ebbc.css"><div id=\"gist90398258\" class=\"gist\">\n <div class=\"gist-file\">\n <div class=\"gist-data\">\n <div class=\"js-gist-file-update-container js-task-list-container file-box\">\n <div id=\"file-chmber02\" class=\"file\">\n \n\n <div itemprop=\"text\" class=\"blob-wrapper data type-text\">\n <table class=\"highlight tab-size js-file-line-container\" data-tab-size=\"8\">\n <tr>\n <td id=\"file-chmber02-L1\" class=\"blob-num js-line-number\" data-line-number=\"1\"><\/td>\n <td id=\"file-chmber02-LC1\" class=\"blob-code blob-code-inner js-file-line\">{<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L2\" class=\"blob-num js-line-number\" data-line-number=\"2\"><\/td>\n <td id=\"file-chmber02-LC2\" class=\"blob-code blob-code-inner js-file-line\"> "Version": "2012-10-17",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L3\" class=\"blob-num js-line-number\" data-line-number=\"3\"><\/td>\n <td id=\"file-chmber02-LC3\" class=\"blob-code blob-code-inner js-file-line\"> "Statement": [<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L4\" class=\"blob-num js-line-number\" data-line-number=\"4\"><\/td>\n <td id=\"file-chmber02-LC4\" class=\"blob-code blob-code-inner js-file-line\"> {<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L5\" class=\"blob-num js-line-number\" data-line-number=\"5\"><\/td>\n <td id=\"file-chmber02-LC5\" class=\"blob-code blob-code-inner js-file-line\"> "Sid": "VisualEditor0",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L6\" class=\"blob-num js-line-number\" data-line-number=\"6\"><\/td>\n <td id=\"file-chmber02-LC6\" class=\"blob-code blob-code-inner js-file-line\"> "Effect": "Allow",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L7\" class=\"blob-num js-line-number\" data-line-number=\"7\"><\/td>\n <td id=\"file-chmber02-LC7\" class=\"blob-code blob-code-inner js-file-line\"> "Action": [<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L8\" class=\"blob-num js-line-number\" data-line-number=\"8\"><\/td>\n <td id=\"file-chmber02-LC8\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:PutParameter",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L9\" class=\"blob-num js-line-number\" data-line-number=\"9\"><\/td>\n <td id=\"file-chmber02-LC9\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:DeleteParameter",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L10\" class=\"blob-num js-line-number\" data-line-number=\"10\"><\/td>\n <td id=\"file-chmber02-LC10\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:DescribeParameters",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L11\" class=\"blob-num js-line-number\" data-line-number=\"11\"><\/td>\n <td id=\"file-chmber02-LC11\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:GetParameterHistory",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L12\" class=\"blob-num js-line-number\" data-line-number=\"12\"><\/td>\n <td id=\"file-chmber02-LC12\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:GetParametersByPath",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L13\" class=\"blob-num js-line-number\" data-line-number=\"13\"><\/td>\n <td id=\"file-chmber02-LC13\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:GetParameters",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L14\" class=\"blob-num js-line-number\" data-line-number=\"14\"><\/td>\n <td id=\"file-chmber02-LC14\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:GetParameter",<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L15\" class=\"blob-num js-line-number\" data-line-number=\"15\"><\/td>\n <td id=\"file-chmber02-LC15\" class=\"blob-code blob-code-inner js-file-line\"> "ssm:DeleteParameters"<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L16\" class=\"blob-num js-line-number\" data-line-number=\"16\"><\/td>\n <td id=\"file-chmber02-LC16\" class=\"blob-code blob-code-inner js-file-line\"> ],<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L17\" class=\"blob-num js-line-number\" data-line-number=\"17\"><\/td>\n <td id=\"file-chmber02-LC17\" class=\"blob-code blob-code-inner js-file-line\"> "Resource": "*"<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L18\" class=\"blob-num js-line-number\" data-line-number=\"18\"><\/td>\n <td id=\"file-chmber02-LC18\" class=\"blob-code blob-code-inner js-file-line\"> }<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L19\" class=\"blob-num js-line-number\" data-line-number=\"19\"><\/td>\n <td id=\"file-chmber02-LC19\" class=\"blob-code blob-code-inner js-file-line\"> ]<\/td>\n <\/tr>\n <tr>\n <td id=\"file-chmber02-L20\" class=\"blob-num js-line-number\" data-line-number=\"20\"><\/td>\n <td id=\"file-chmber02-LC20\" class=\"blob-code blob-code-inner js-file-line\">}<\/td>\n <\/tr>\n<\/table>\n\n\n <\/div>\n\n <\/div>\n<\/div>\n\n <\/div>\n <div class=\"gist-meta\">\n <a href=\"https://gist.github.com/seanroisentul/300a53887e1f22fe88d9567b93ed0db8/raw/1e4aceb8f2b9efb46421cda46bac4064207d06a9/Chmber02\" style=\"float:right\">view raw<\/a>\n <a href=\"https://gist.github.com/seanroisentul/300a53887e1f22fe88d9567b93ed0db8#file-chmber02\">Chmber02<\/a>\n hosted with ❤ by <a href=\"https://github.com\">GitHub<\/a>\n <\/div>\n <\/div>\n<\/div>\n

Pros & Cons

I’ll handle this as if my two options are Vault and SSM. Both are great, each has it’s own pitfalls and advantages.

The SSM Parameter store is a free managed service, it means that financially you don’t have a reason not to use it, it requires no running resources except the KMS key, it’s basically free. On the other hand, it’s a service, so in case of cloud migration, you’ll have to migrate your keys to a new system. Now, Hashicorp users tend to make the argument of “not being vendor locked” when using Vault / TerraForm and other tools. I can’t agree with this argument due to two major factors; 1. You’re vendor locked to Hashicorp, I’ll take Amazon any day.2. Cloud migration is something done very rarely. Your secrets will be the least of your concerns if that’s the case.

Yet, if you’re on the edge case of running a hybrid setting of cloud providers, or use on-premise resources then 1. I feel for you, and 2. You can still use the SSM but you’ll have to manage your authentication less natively, e.g using one-time rotated access keys by AWS Cognito.
But generally, if that’s the case then yes, Vault maybe the better option.

“But Vault has IAM backend access as a feature, even EC2!”Yes, that argument is valid, however, setting it up is no fun. You’ll need to create internal Vault policies allowing the access to a certain role to read and write using this backend authentication method, and then attach the role to your relevant resources. Again — no fun.Which is why if you’re AWS oriented, SSM is the more obvious solution, with easier safe access management.
Tools like chamber exist for Vault, one of them is our own vault-get, but it still constitutes a single point of failure in the form of an injected token/key/password/whatever method is used to authenticate and pull data.

For these reasons, I chose SSM+Chamber as the AWS “native” alternative to Vault, and currently the cheaper alternative to the new AWS secret manager.

On the day of writing the last word of this post, AWS had announced their new secret manager. I don’t think it makes the parameter store obsolete since it’s de-facto the best way of running AWS oriented secret managing service for free.
The new secret manager is a great solution, but for $0.40 for secret per month, not to mention the API calls that read them, parameter store finds it’s place.

Let me know your thoughts in the comments below, or connect with me directly on Twitter @omergsr.

Managing application secrets on AWS with SSM and Chamber
Omer Hamerman
Senior Software Operations Architect
Omer is an experienced software operations engineer and an open source contributor. He is always willing to go the extra mile to help our clients improve their software delivery. He is known for getting the job done very quickly and is clear-cut and very sharp, delivering almost any job on the spot. When he’s not helping our clients achieve scalable and resilient infrastructure, you’ll find him rock climbing and bouldering. He is passionate about beautiful code, cybersecurity and doing things right the first time. He is a keen writer of blog posts and a speaker at meetups.