Subscribe for email updates:

March 30, 2022

3 responses

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.

Leave a reply

Sign in or Sign up to leave a response.

3 responses

Replacement Level
Icons/flag
Icons/link diagonal

Thanks for sharing. I will notify.

RongHo VietNam
Icons/flag
Icons/link diagonal

Thanks for posting. I’ll bookmark your blog and test again here frequently.

Euro Officials
Icons/flag
Icons/link diagonal

Awesome! Its actually remarkable piece of writing, I have got much clear idea about from this article.

Thanks for sharing.

Est. reading time: 9 minutes
Stats: 4,532 views

Categories

Collection

Part of the Ruby on Rails collection