Rails 5.1 applications can be a lot more secretive on Cloud Foundry and Heroku

Our applications need access to secrets – passwords, tokens, special URLs. Platforms like Cloud Foundry and Heroku have made environment variables easy to use, and so we use them. Albeit they are typically not as secretive as we might like.

Here’s a one-liner to look up every secret that you have access to across all applications on a single Cloud Foundry platform:

cf curl /v2/apps\?results-per-page=100 | jq -r ".resources[].entity.environment_json"

Sure, you have authority to look at all those secrets. But should it really be that easy? It’s like being able to open every safe in a vault simultaneously. Do you ever have a real reason to do it? Perhaps there are really only downsides from it being abused?

The next version of Ruby on Rails 5.1 will make it easier to keep secrets more secret: developers can encrypt secrets inside the source code so that they are only available inside the running application process.

To see this in action as of 5.1.0.beta1:

gem install rails -v 5.1.0.beta1
rails new testsekrets
cd testsekrets

The generated scaffolding includes a config/secrets.yml for storing unencrypted secrets. That is, from now on you would not use this file for production secrets.

Secretive secrets are not enabled by default. Turn them on with:

bin/rails secrets:setup

setup

As per the instructions, store the one-time generated encryption key in 1password, or vault, or wherever your team shares secrets:

1pass

As per the instructions, don’t lose this key.

Developers of the application can either:

  1. Place this key in config/secrets.yml.key; or
  2. Set env var RAILS_MASTER_KEY to this value

It is the latter that we will also use when deploying to production with Cloud Foundry below.

The file config/secrets.yml.enc is like a little encrypted database of secrets that you can safely commit with the rest of your source code.

To edit it:

EDITOR=vim bin/rails secrets:edit

The root key of the secret secrets.yml file is either the applicable RAILS_ENV environment (such as production) or shared for secrets that are available to all runtime environments.

shared:
  sharedkey: "I am shared"
production:
  mykey: "I am in production"

To access our new production secret, use Testsekrets::Application.secrets. Try the Rails console:

$ RAILS_ENV=production rails c
> Testsekrets::Application.secrets
 => {:api_key=>123, :secret_key_base=>nil,
    :sharedkey=>"I am shared",
    :mykey=>"I am in production",
    :secret_token=>nil}

There are our additional secrets: :sharedkey and :mykey.

The additional keys api_key and secret_key_base were merged in from the unencrypted/human readable config/secrets.yml that was created by rails new scaffolding.

In development, you will get different secrets:

$ rails c
> Testsekrets::Application.secrets
 => {:api_key=>123,
     :secret_key_base=>"f5ed76f34bfa542...",
     :secret_token=>nil}

You don’t see :sharedkey above because the config/environments/development.rb does not have the following line, but config/environments/production.rb does:

config.read_encrypted_secrets = true

Deploying to Cloud Foundry

Now, instead of storing secrets in environment variables, we have two changes to encrypt them:

  1. Use bin/rails secrets:edit to store the secrets; and access them within your application via Testsekrets::Application.secrets[key]
  2. Pass the secret-keeping encryption key from above via RAILS_MASTER_KEY:
cf set-env testsekrets RAILS_MASTER_KEY $(cat config/secrets.yml.key)
cf set-env testsekrets SECRET_KEY_BASE $(rake secret)

NOTE: at the time of writing I wasn’t sure what $SECRET_KEY_BASE used for; Rafael helped me out on Twitter. I’ve updated the command above.

Bonus

If you have Vault in production, then you can also share secrets with Cloud Foundry applications using a Vault service broker, such as https://www.hashicorp.com/blog/cloud-foundry-vault-service-broker/

Spread the word

twitter icon facebook icon linkedin icon