Secret Rails Configurations
Last time,
I looked at keeping environment specific configuration using YAML
files and Rails.application.config_for
. One big issue with this
approach is security. It’s very common to have different sets of API
keys and API settings for different environments. A simple example is
credit card processing. Outside of production you use a sandbox and
test cards. Only in production are live API credentials needed.
We need a way to securely handle this information.
First there is the Rails way. Starting with Rails 4, there is a file
config/secrets.yml
. The file follows the environment format of
database.yml:
development:
secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
some_api_key: SOMEKEY
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
some_api_key: <%= ENV["SOME_API_KEY"] %>
and automatically loads them in to:
Rails.application.secrets
{
:secret_key_base => "3b7cd727ee24e8444053437c36cc66c3",
:secret_token => "SOMEKEY"
}
But, as it say right in the generated secrets.yml
file, you
shouldn’t actually keep secrets in it. Instead, the recommendation is
to set them in environmental variables in the web server
configuration, which is a pain in the neck. (SECRET_KEY_BASE
is
magically generated as part of the deploy process, so it’s go that
going for it).
Really, this environmental variables are how secrets have always been
managed in Rails, secrets.yml
just simplifies accessing them,
especially in development.
The better way is to use the sekrets gem which I’ve covered before. A short recap:
Install the sekrets gem (you know how), run:
rake sekrets:generate:key
to generate a random password in .sekrets.key
, and add it to your
.gitignore
:
echo '/..sekrets.key' >> .gitignore
Which allows you to read and write files encrypted with the key:
sekrets edit config/sekrets.yml.enc
sekrets read config/sekrets.yml.enc
Your sekrets.yml
might look something like:
development:
aws:
access_key_id: AAAAAAAAAAAAAAAAAAAA
secret_access_key: MLID0VMNmvprix9FkAdhOaVbzUlNWdqvQ2QRURDd
production:
aws:
access_key_id: ABBBBBBBBBBBBBBBBBBB
secret_access_key: 9x8uwiimCTyHFP5sYXRYf43WLLEl5EdQUfg8RfiB
Normally, you’d then run a Rake task to set up an initializer that
loads, the contents of sekrets.yml
in to a global constant SEKRETS,
however we can instead do follow the same pattern we have been using
for other config files. In config/application.rb, add:
class Application < Rails::Application
sekrets = File.join(Rails.root, 'config', 'sekrets.yml.enc')
config.sekrets = Sekrets.settings_for(sekrets)[Rails.env] || {}
end
Sekrets.settings_for
returns the whole file, [Rails.env] || {}
emulates Rails.application.config_for
by returning only the current
environment or an empty hash if nothing is set for that environment.
Sekrets gives you a Map, so you can use things like:
aws = Rails.configuration.sekrets.aws
right out of the box.
For production, you either set the SEKRETS_KEY in your web servers environment
or, if you use Capistrano, you can add require ‘sekrets/capistrano’ to
your Capfile which will upload .sekrets.key
as part of the deploy.
Now you secrets are both encrypted and easily access in you app.
Note there’s an open proposal to add encrypted version of secrets.yml to rails that would provide the same functionality as Sekrets. Consider supporting it.
Comments