Secret Rails Configurations

2 minute read

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