March 30, 2022
The Complete Guide to Ruby on Rails Encrypted Credentials
Modern versions of Ruby on Rails ship with a very useful application credentials layer that allows you to store private keys and other information in a fully encrypted manner.
Unfortunately, the documentation around the use of such tech isn't the best. This guide is my attempt to expose as much as I can around credentials and how you can make use of them in your Ruby on Rails applications today.
Why encrypted credentials?
The need for encryption arose after many developers struggled to agree on a standard for storing sensitive API keys and the like. This might be your payment provider's API keys or your email service provider's API keys for example.
Keys were commonly shared in insecure manners and even checked into version control where anyone could get access.
Historically, some Rails developers used environment-based variables or the secrets.yml file which worked well enough but had its own set of challenges in a team setting. Sharing and updating these variables is very cumbersome, to say the least since most of the time they weren't in a place that could easily be shared amongst a team.
Because larger Rails apps need a better source of truth, encrypted credentials rose to answer the sharable interface problem. This empowers developers across a given network/team to safely share a common codebase without fear of mishandling sensitive data. A master key can then be shared via password manager or some other safe mechanism that allows all developers root access to everything.
What are encrypted credentials and why do we need them?
In short, you don't need encrypted credentials but they do solve a lot of issues when it comes to sharing keys and sensitive data across a team of developers.
The idea of encryption means we can safely commit code to private or public repos on the web where the code gets stored. Doing so before encryption meant your API keys or other sensitive data could be easily accessible by people you probably don't want to give access to.
The master key
When you create a new rails app a file called credentials.yml.enc
is added to the config
directory. This file will be decrypted in a production environment using a key stored either on a RAILS_MASTER_KEY
environment variable or a master.key
file I mentioned before. In development, your app will reference config/master.key
to properly decrypt the data.
If you don't have the credentials.yml.enc
file in your app already you can run a command to generate one.
rails credentails:edit
This first checks if that file is present and generates a new one if not. A master.key
file is created as well if not present. Be sure to not commit the master.key
file to version control. Add it to the .gitignore
file in your project if not already in place.
In Rails 6 and 7 this all is included by default with new applications.
Adding new credentials
You can add any data you want inside the credentials.yml.enc
file once decrypted. You shouldn't add anything to it without running the rails credentials:edit
command. I'll commonly put all my app's API keys or third-party services I use. It's a nice source of truth for those types of data that your team can share securely.
You need to declare what tool you'll edit the credentials file with when running the command.
EDITOR="code --wait" rails credentials:edit
In my workflow, I make use of VS Code. It has an alias you can install known as "code" that allows me to open projects quickly. Adding --wait
instructs the open window in VS code to wait until I close it to save and encrypt the file again.
The default credentials.yml.enc file looks similar to the following once decrypted for the first time:
# 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: e35911b02577eb6d60077161be047ac93f803f1c4fd3fef877732c601d65d1bec16d68c8aaef1c51c9fd080a1a0b0eb53896ac70b78a6c47bc3d0cfa7582b530
You might have already noticed but all credentials files are stored as YAML
code. This is a nice way to organize data in a way most ruby developers are familiar.
Adding a new key-value pair is simple. I'll add one grouping called stripe
. This is something I'll add to nearly any rails app that accepts payments. The values here are placeholder.
# 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: e35911b02577eb6d60077161be047ac93f803f1c4fd3fef877732c601d65d1bec16d68c8aaef1c51c9fd080a1a0b0eb53896ac70b78a6c47bc3d0cfa7582b530
stripe:
public_key: "test_public"
private_key: "test_private"
Closing the file encrypts the data which can now be version controlled. Your command prompt outputs:
"File encrypted and saved."
To signify the task was performed as planned.
Making use of credentials
When you want to make use of credentials in your app you can do so from pretty much anywhere.
Loading up rails console
is the quickest way to verify credentials can be properly accessed.
rails console
irb(main):001:0> Rails.application.credentials.stripe
=> {:public_key=>"test_public", :private_key=>"test_private"}
In an object-oriented fashion the call to Rails.application.credentials.stripe
returns a hash of the key-value pairs we just added.
One way I like to call keys is using the dig
method which translates to:
rails console
irb(main):002:0> Rails.application.credentials.dig(:stripe)
=> {:public_key=>"test_public", :private_key=>"test_private"}
From there you can build a small chain to dig through fairly easily. Say I wanted to only display the private stripe key. That looks like the following:
rails console
irb(main):003:0> Rails.application.credentials.dig(:stripe, :private_key)
=> "test_private"
Environment-specific credentials
In most Rails apps there are at least 3 environments created by default. Those are:
- Development
- Test
- Production
Each environment is defined inside the config/environments
folder. You might even have a staging
environment if your app makes use of another environment for previewing production-like code on a remote server.
With encrypted credentials, each environment can have its own credentials.yml.enc
type of file scoped by a different naming convention.
The default pattern on most rails apps is a master config/credentials.yml.enc
file. This acts as a global credentials file if no other environments are present with their own credentials file.
The need for this comes when third-party services/APIS out there have "live" and "test" modes.
Stripe is a great example of this. You can use the "test" mode to create example transactions, subscriptions, payments, and more without affecting your "live" environment.
Keeping track of those modes gets tedious so a newer feature to application credentials is to segregate alternative credential files per environment.
This means you can have different sets of keys per environment you set.
Since I'm in my local environment I'd prefer to use Stripe's test keys. We could run the following and set this up:
EDITOR="code --wait" rails credentials:edit --environment=development
Note: The --environment
flag can be open-ended as long as your app's environments align
If you don't have a credentials file for your development environment already set up, Rails will create one as well as prep your app to accommodate the new files that get generated:
Adding config/credentials/development.key to store the encryption key: c8d57cc62dda6d2c7c2a7dd653fe6fff
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/credentials/development.key
Ignoring config/credentials/development.key so it won't end up in Git history:
append .gitignore
Here a new development.key
is created and added to the .gitignore
file. This ensures it won't be version-controlled and put into the wrong hands.
You might also notice a new config/credentials
directory is created with a different development.yml.enc
inside it. This file is what will be referenced while you are in your development environment only.
In the decrypted file I'll add the following and close it to encrypt.
# aws:
# access_key_id: 123
# secret_access_key: 345
stripe:
public_key: "test_public_development"
secret_key: "test_secret_development"
Now I'll verify it works with rails console
rails console
irb(main):001:0> Rails.application.credentials.dig(:stripe, :public_key)
=> "test_public_development"
This now automatically pulls credentials from the config/development.yml.enc
file since I'm in the development
environment.
The great thing here is once our app gets deployed to a production environment we can swap the RAILS_ENV
environment variable to production
and that same Rails.application.credentials
query will automatically pick up the production environment keys if present.
The hardest part of all of this is making sure your keys remain consistent between each encrypted credentials file. So if you need different keys per environment you will want to add them to each making sure you're using the correct keys.
Deploying to a remote server
Many Rails developers make use of Heroku so I'll use it as an example for deployment. Any host will have their requirements so be sure to read their documentation first.
Rails ships with a RAILS_MASTER_KEY
environment variable. These hook into the credentials API and allow you to pass your master.key
to the hosted app allowing it permission to decrypt those yaml files.
You can set your app up to require the use of a master key to work in your environment file. So in my case, I might change a line inside config/environments/production.rb
to the following if I deem it necessary:
# config/environments/production.rb
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
config.require_master_key = true
I'd recommend doing this so you don't need to move your master key to any hosted environment this forces you to create security issues.
Heroku allows you to set this up via the web app or the command line.
heroku config:set RAILS_MASTER_KEY=`cat config/master.key`
This simple one-liner copies your master key and creates a new RAILS_MASTER_KEY
environment variable in one action.
Categories
Collection
Part of the Ruby on Rails collection