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
- Securing Rails Applications
- The power of Rails master.key
- Manage Rails app secrets with Rails Encrypted Credentials
- Rails 6 adds support for multi environment credentials
- RAILS_MASTER_KEY and per-environment init
- How to set RAILS_PRODUCTION_KEY config var on a Rails 6 app on Heroku
- Rails 6 Heroku Devise error ActiveSupport::MessageEncryptor::InvalidMessage
- Generate a Rails Secret Key