Andy from Webcrunch

Subscribe for email updates:

Portrait of Andy Leverenz
Andy Leverenz

March 30, 2022

Last updated January 12, 2024

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 fully encrypted.

Unfortunately, the documentation around using such tech isn't the best. This guide is my attempt to expose as much as I can to credentials and how you can use 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. For example, this might be your payment provider's API keys or your email service provider's API keys.

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 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 arose 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 a 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 many 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 decrypt the data correctly.

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 also created if not present. Do not commit the master.key file to version control. Add it to your project's .gitignore file if not already in place.

In Rails 7, this is all 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 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 an excellent source of truth for those data types that your team can share securely.

When running the command, you must declare what tool you'll edit the credentials file. Also, below, I'm declaring the RAILS_ENV variable to production. This ensures the default credentials.yml.enc file is used.

RAILS_ENV=production 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 use credentials in your app, you can do so from anywhere.

Loading up the rails console is the quickest way to verify credentials can be accessed appropriately.

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 reasonably easily. Say I wanted only to 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, at least 3 environments are 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 uses 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 an excellent 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 for 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

A new development.key is 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. This file 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 this is ensuring 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, ensuring you're using the correct ones.

Deploying to a remote server

Many Rails developers use Heroku, so I'll use it as an example for deployment. Any host will have their requirements, so 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, permitting it to decrypt those yaml files.

You can set your app up to require 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.

Link this article
Est. reading time: 8 minutes
Stats: 38,584 views

Categories

Collection

Part of the Ruby on Rails collection