Ruby on Rails Secrets Management

Published: Sep 4, 2023

A web application needs various kinds of values, params and etc which should not be revealed to the public, say GitHub repo. For example, API keys, tokens, passwords, and endpoints, all those should be kept secret. Another important factor is that such secrets should be shared among the team members. Additionally, all those secrets should come along with the deployment.

Previously, the dotenv file (.env) is commonly used for this purpose. When the web application is deployed, the secrets are set up as environment variables.

Rails Way of Secrets Management

Since version 5.2, Rails provides a new way of secret management (Rails 5.2.0 FINAL: Active Storage, Redis Cache Store, HTTP/2 Early Hints, CSP, Credentials). It consists of an encrypted credentials file and a key to encrypt/decrypt it. By default, those are config/credentials.yml.enc and config/master.key, which are generated by a rails new command. The credentials.yml.enc is encrypted, so it can be safely pushed to the GitHub repo. While the master.key should kept secret and never be pushed to the GitHub repo. The master.key is often shared among team members by a secure measure.

To edit the credentials file, use the rails credentials:edit command with the EDITOR environment variable. For example:

$ EDITOR=vim rails credentials:edit
$ EDITOR="code --wait" rails credentials:edit

If the master.key doesn’t exist, Rails shows the message below.

When the config/master.key is missing, the command shows the suggestion.

$ EDITOR=vim rails credentials:edit Adding config/master.key to store the encryption key: ed36bdc2…….

Save this in a password manager your team can access.

If you lose the key, no one, including you, can access anything encrypted with it.

 create  config/master.key

Couldn’t decrypt config/credentials.yml.enc. Perhaps you passed the wrong key?

Even though the new master.key is created following the message, the existing credentials.yml.enc won’t be decrypted. It needs the exact key the file is encrypted. This way, we can safely push the credentials.yml.enc file to the GitHub repo.

How to Use Credentials

As the file extension expresses, the credentials file takes YAML format. It is structured, not like a dotenv file.

When the rails new command creates the credentials file, it looks like this.

# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 3cfc7cf4......

We can add various secrets in YAML format. For example, some_api_key, client_id and client_secret pairs for OAuth authentication can be written here.

secret_key_base: 3cfc7cf4......
some_api_key: HERE_IS_MY_API_KEY
oauth:
  twitter:
    client_id: MY_TWITTER_CLIENT_ID
    client_secret: MY_TWITTER_CLIENT_SECRET
  facebook:
    client_id: MY_FACEBOOK_CLIENT_ID
    client_secret: MY_FACEBOOK_CLIENT_SECRET

To use above secrets in Rails app, Rails.application.credentials.[key1].[key2]… is the way to access those. We can confirm that on the Rails console.

$ rails c
Loading development environment (Rails 7.0.4.2)
irb(main):001:0> Rails.application.credentials.some_api_key
=> "HERE_IS_MY_API_KEY"
irb(main):002:0> Rails.application.credentials.oauth.twitter.client_id
=> "MY_TWITTER_CLIENT_ID"
irb(main):003:0> Rails.application.credentials.oauth.twitter.client_secret
=> "MY_TWITTER_CLIENT_SECRET"

Multi-environment credentials

Since Rails 6, the multi-environment credentials are supported. When secrets are not the same between development and production environment, those could’ve been differentiated by a nested YAML notation. For example,

# before Rails 6
development:
  some_api_key: API_KEY_FOR_DEV
production:
  some_api_key: API_KEY_FOR_PROD

However, in the Rails app, we should’ve written something like:

# before Rails 6
if Rails.env == "development"
  api_key = Rails.application.credentials.development.some_api_key
elsif Rails.env == "production"
  api_key = Rails.application.credentials.production.some_api_key
end

In contract, with the multi-environment credentials, the credentials file is chosen based on the Rails environment. If the Rails environment is a production, config/credentials/production.yml.enc is used. The key to encrypt/decrypt is just for the production env, config/credentials/production.key. This way, we can eliminate the environment check in our code.

Create the environment specific credentials

To create the Rails environment based credentials, use –environment option.

$ EDITOR=vim rails credentials:edit --environment development
$ EDITOR=vim rails credentials:edit --environment production

Above commands creates credential and key files under config/credentials directory.

$ tree config/credentials
config/credentials
├── development.key
├── development.yml.enc
├── production.key
└── production.yml.enc

1 directory, 4 files

The newly generated development/production credential files don’t have a secret_key_base entry. Under the development environment, missing secret_key_base looks not an immediate problem. However, under the production environment, it raises an exception.

$ RAILS_ENV=production rails c
/Users/yoko/.gem/ruby/3.2.1/gems/railties-7.0.4.2/lib/rails/application.rb:581:in `validate_secret_key_base': Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit` (ArgumentError)
...
...

We should add secret_key_base in the credentials file. To generate the value, Rails provides the command:

$ rake secret
a2b30cf4d702a........

Open the production.yml.enc file and copy/paste the generated secret as the value of secret_key_base.

secret_key_base: a2b30cf4d702a........
some_api_key: API_KEY_FOR_PROD

Now, we can check the production secrets.

$ RAILS_ENV=production rails c
Loading production environment (Rails 7.0.4.2)
irb(main):001:0> Rails.application.credentials.some_api_key
=> "API_KEY_FOR_PROD"

Deploy to Heroku

Heroku is a popular hosting service for a Rails app. However, not like AWS EC2 instance, people don’t have a direct access to the instance. The encrypt/decrypt key should be installed by a heroku command.

One thing we should know is, Rails “looks for the environment variable ENV[“RAILS_MASTER_KEY”] to encrypt the credentials file” (Securing Rails Applications). Whether the environment is development or production, Rails sees ENV[“RAILS_MASTER_KEY”].

Heroku’s staging server runs under the production environment. To deploy the config/production.key, use the Heorku CLI command below:

heroku config:set RAILS_MASTER_KEY=`cat config/credentials/production.key`

References

Latest Posts

Vite + Vue + Bun on Rails

Vue.js is one of frontend frameworks gaining popularity among rapidly emerging JavaScript technologies. The combination of Vue.js and Rails is becoming more popular as well, however, Vue.js development on Rails is not so straightforward. The reason would be that Vue.js relies on Vite for a development environment such as HMR (Hot Module Replacement) and bundling.

Bun + React on Rails

In the frontend world, new technologies keep emerging rapidly these years. Still, React is a well-established and very popular frontend framework, Vue.js, Svelte, Astro and more frameworks are gaining popularity. Not just the frameworks, tools for a transpiler/bundler or sort are also under a rapid development. In JavaScript domain, esbuild, rollup, vite and some more are out.

Ruby on Rails Secrets Management

A web application needs various kinds of values, params and etc which should not be revealed to the public, say GitHub repo. For example, API keys, tokens, passwords, and endpoints, all those should be kept secret. Another important factor is that such secrets should be shared among the team members. Additionally, all those secrets should come along with the deployment.