Rails multi environment credentials
Background
Generally in applications there are various secrets and credentials, that we need to make use of like API keys, secrets, etc. For such secrets we need the ability to conveniently and securely manage credentials.
Rails 5.1 added a feature to use secrets to manage credentials.
Rails 5.2 replaced secrets with credentials, since encrypted and un-encrypted secrets were making it harder to manage them.
A set of files were used to manage these credentials:
config/credentials.yml.enc
config/master.key
config/credentials.yml.enc
is an encrypted file which store the credentials. As this is a encrypted file, we can safely commit it to our version control systems.
config/master.key
contains RAILS_MASTER_KEY
which is used to decrypt the config/credentials.yml.enc
. We should not commit this file to version control.
Interacting with credentials
As config/credentials.yml.enc
is encrypted we should never directly read from or write to it. Instead, we will use utilities provided by Rails which abstract encryption and decryption process for us.
How to add/update credentials?
We can edit the credentials by running the following command:
$ EDITOR=vim rails credentials:edit
This will open a vim editor with the decrypted version of the credentials file.
We can add new credentials in YAML format. Lets add the following lines, save the changes, and exit.
aws:
secret_access_key: 123456
RDS_DATABASE=db_name
RDS_USERNAME=db_username
RDS_PASSWORD=db_password
RDS_HOST=db-host.rds.amazonaws.com
github:
app_id: 123
app_secret: 345
secret_key_base:
...
production:
...
When we save it, it encrypts again using the same master key.
If default editor is not set and we haven’t specified the editor, then we get the following message:
$ rails credentials:edit
No $EDITOR to open file in. Assign one like this:
EDITOR="mate --wait" bin/rails credentials:edit
For editors that fork and exit immediately, it's important to pass a wait flag,
otherwise the credentials will be saved immediately with no chance to edit.
How to read credentials?
We can now access the credentials in the following way:
> Rails.application.credentials.config
#=> {:aws=>{:secret_access_key=>"123456", :RDS_DATABASE=>"db_name", :RDS_USERNAME=>"db_username", ...}, :github=>{:app_id=>"123", :app_secret=>"345"}, ...}
> Rails.application.credentials.github
#=> {:app_id=>"123", :app_secret=>"345"}
> Rails.application.credentials.github[:app_id]
#=> "123"
Managing multi environment credentials before Rails 6
There was no built in support for multiple environment credentials before Rails 6. We could manage credentials for different environments but it was upto us to explicitly specify which set of credentials to use for a specific environment.
We could store the credentials in a single file as below:
development:
aws:
secret_access_key: 123456
RDS_DATABASE=db_name
RDS_USERNAME=db_username
RDS_PASSWORD=db_password
RDS_HOST=db-host.rds.amazonaws.com
production:
aws:
secret_access_key: M0KZLSAwmkokAHXVVKS5SgTUP4ennqhi
RDS_DATABASE=db_name_pro
RDS_USERNAME=db_username_prod
RDS_PASSWORD=db_password_prod
RDS_HOST=db-host-prod.rds.amazonaws.com
Then, config can be accessed using the following command:
> Rails.application.credentials[Rails.env.to_sym][:aws][:RDS_DATABASE]
#=> "db_name"
There are some problems with this approach:
- There is just 1 master key and everyone on the development team had access to it. Which means everyone on the development team had access to production environment.
- We needed to explicitly specify which environment credentials to use in the code.
Another way to manage environment specific credentials was by creating environment specific files. For example, we can create config/staging.yml.enc
for staging environment and config/production.yml
.enc for production environment. To read config from these files, Rails 5.2 provided encrypted method to support for managing multiple credentials files.
This approach involved writing even more boiler plate code to manage the keys and the encrypted files for every environment.
In Rails 6
Now, Rails 6 has added support for multi environment credentials.
It provides utility to easily create and use environment specific credentials. Each of these have their own encryption keys.
Global Credentials
The changes added in the above PR are backwards compatible. If environment specific credentials are not present then rails will use the global credentials and master key which are represented by following files:
config/credentials.yml.enc
config/master.key
We use the global configuration only for development and test environments. We share the config/master.key
with our entire team.
Create credentials for production environment
To create credentials for production environment, we can run the following command:
$ rails credentials:edit --environment production
The above command does the following:
- Creates
config/credentials/production.key
if missing. Don’t commit this file to VCS. - Creates
config/credentials/production.yml.enc
if missing. Commit this file to VCS. - Decrypts and opens the production credentials file in the default editor.
We share the production.key
with limited members of our team who have access for production deployment.
Let’s add following credentials and save:
aws:
secret_access_key: M0KZLSAwmkokAHXVVKS5SgTUP4ennqhi
RDS_DATABASE=db_name_prod
RDS_USERNAME=db_username_prod
RDS_PASSWORD=db_password_prod
RDS_HOST=db-host-prod.rds.amazonaws.com
Similarly we can create credentials for different environment like staging.
Using the credentials in Rails
For any environment Rails automatically detects which set of credential to use. Environment specific credentials will take precedence over global credentials. If environment specific credentials are present, they will be used else Rails will default to global credentials.
For development:
$ rails c
> Rails.application.credentials.config
#=> {:aws=>{:secret_access_key=>"123456", :RDS_DATABASE=>"db_name", ...} }}
> Rails.application.credentials.aws[:access_key_id]
#=> "123"
For production:
$ RAILS_ENV=production rails c
> Rails.application.credentials.config
#=> {:aws=>{:secret_access_key=>"M0KZLSAwmkokAHXVVKS5SgTUP4ennqhi", :RDS_DATABASE=>"db_name_prod", ...}}
> Rails.application.credentials.aws[:access_key_id]
#=> "1f3649fe-ebbd-11e9-81b4-2a2ae2dbcce4"
Storing encryption key in environment variables
We can also set the value of the encryption key in RAILS_MASTER_KEY environment variable.
If RAILS_MASTER_KEY
is set, we don’t need to create the *.key
files. Rails will auto detect this environment variable and use it to encrypt/decrypt the credential files.
The environment variable can be used for example on Heroku or similar platforms.
# Setting master key on Heroku
heroku config:set RAILS_MASTER_KEY=`cat config/credentials/production.key`
Comments
Post a Comment