Keeping better secrets with Amazon’s KMS
It is well-known that it’s a poor idea to store your application secrets in plaintext files, especially if they get committed to version control. So what should you do instead?
Previously, we have attempted to solve this problem by having a file that only exists on the live environments, but that is tricky to support. In this post, we describe how we use Amazon’s Key Management Service to safely encrypt our precious secrets…
What is KMS?
Key Management Service is a provided by Amazon Web Services (AWS) for securely storing secrets. The thing that makes it special is the way it solves the ‘master key’ problem.
With most secrets management systems, is how to store and manage the ‘master’ private key used to encrypt all your other data – do you store it locally? Encrypt it with passwords your developers know and/or keep on sticky notes pasted to monitors? None of these are great solutions. KMS does away with this by generating and storing the private key on their systems (backed by Hardware Security Modules). The end-user never directly uses or sees the private key.
Access for people or applications is managed, such as via IAM.
What do we use it for?
At the moment, we’re using it for a variety of pieces of information: including API keys for our own, and third-party services. It could feasibly be used for database passwords, SSH keys, etc.
So, how are we using it?
Having to directly talk to the AWS API to deal with this information is kind of a pain. So we developed and have released the kumo_ki gem as open source for anyone to use!
ENV['AWS_ACCESS_KEY_ID']=XXXX
ENV['AWS_SECRET_ACCESS_KEY']=YYYYY
require 'kumo_ki'
ciphertext = KumoKi::KMS.new.encrypt_for('my_env', 'shh_dont_tell')
puts ciphertext => "CiCRAIBw6aG5PR5XWhLpFs0CtWZCoVenu5eErfT6U2j+PhKZAQEBAgB4kQCA\ncOmhuT0eV1oS6RbNArVmQqFXp7uXhK30+lNo/j4AAABwMG4GCSqGSIb3DQEH\nBqBhMF8CAQAwWgYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzhsbGlKyvJ\nq+mnvFwCARCALUFLuyWPLZNO+vLnz5Vzxih+7kplGxqeJk0V4JILJBvzc1XX\ngHCAJ0yKlXCw6A==\n"
puts KumoKi::KMS.new.decrypt(ciphertext) => 'shh_dont_tell
We then apply some (for now) secret sauce to this in our development tooling to wrap these functions to output secrets into a YAML format, which we can then mix and match using erb templates to inject our secrets into our application configuration: e.g:
# common.yml
# ... :rollbar_api_key: <%= ENV['ROLLBAR_API_KEY'] %>
# ...
# development_secrets.yml
#... ROLLBAR_API_KEY: > [ENC,CiCRAIBw6aG5PR5XWhLpFs0CtWZCoVenu5eErfT6U2j+PhKnAQEBAgB4kQCA cOmhuT0eV1oS6RbNArVmQqFXp7uXhK30+lNo/j4AAAB+MHwGCSqGSIb3DQEH BqBvMG0CAQAwadfgjhgjhdfgjhdfgjhjfghdfljkghQBLjARBAyWFA8p1W0d mnkw1gkCARCAOxE9MZwlXJIraIy7Q9KdNx6bMZ3rH3V7IjRcFWp7wIvnTgMv HHwlSG/alfEopfU/WY3PTcGzY2J6Vpvm
# ...
The [ENC, prefix is how we decide if a piece of configuration data is encrypted, or should be treated as plaintext. With the right harness code around it, this can be used to merge your configuration yaml files with ones containing this encoded data when you load your application (or at deploy-time, into e.g. environment variables), without ever having to store your secret in plaintext anywhere at any time!