Encrypted Secrets(Credentials) in Rails 6, Rails 5.1/5.2, older versions and non-Rails applications

How to manage encrypted keys for different environments

Kirill Shevchenko
Jan 9 · 4 min read

There are two most popular ways to manage secrets in your application.

  1. Encrypted file with secrets. Best choice for a single monolith application. There’s no need for additional software, just keep your encrypted data in the app repository and move decrypt key under git ignore.
  2. Centralized storage. For large and complex systems it’s better to use dedicated storage for all services and provide an interface for them. Vault is a cool example of this kind of solution.

In this article, I want to talk about the first approach.

Encrypted secrets were first introduced in Rails 5.1. Rails store secrets in config/credentials.yml.enc by default. For applications created prior to Rails 5.2, we’ll automatically generate a new
credentials file in config/credentials.yml.enc the first time you run:

rails credentials:edit

If you don’t have a master key, that will be created too. Applications after Rails 5.2 automatically have a basic credentials file generated that already contains the secret_key_base.

Here is an example of config/credentials.yml.enc

aws:
access_key_id: 123
secret_access_key: 345
secret_key_base: xxx

And this is how to get value from credentials:

Rails.application.credentials.aws[:secret_access_key]
=> 345

Below I’ll list three cases of managing encrypted variables: Rails 6, Rails 5.1/5.2, older versions and non-Rails applications.

Rails 5.1.x and 5.2.x: Single file for all environments

This was the main problem. One single file with all credentials and one single key for them. There is no way to share access between developers for a specific environment. Though you can easily hack a naming problem in two ways:

  1. Adding dev, staging, production keys and put all needed under the related key.
  2. Override credentials method on Rails application.
development:
aws:
access_key_id: 123
secret_access_key: 345
production
aws:
access_key_id: 321
secret_access_key: 543

Fetching value in the code will be through a call Rails.env on Rails.application.credentials

Rails.application.credentials.send(Rails.env)[:aws][:secret_access_key]
module AppModule
class Application < Rails::Application
def credentials
if Rails.env.production?
super
else
encrypted(
"config/credentials.#{Rails.env}.yml.enc",
key_path: "config/#{Rails.env}.key"
)
end
end
end
end

This uses the default credentials file config/credentials.yml.enc if the Rails environment is production. With this solution decrypt key can be specific for each environment.

Rails 6: Specify And Manage credentials file for each environment

So, now Rails 6 supports Multi Environment Credentials.

The credentials command supports passing an--environment option to create an environment-specific override file with variables. That override will take precedence over the global config/credentials.yml.env file when running in that environment.

Let’s create an example for the development environment:

rails credentials:edit --environment development

This task will create config/credentials/development.yml.enc with the new encryption key in config/credentials/development.key

Let’s add our keys here:

aws:
access_key_id: 123

To get the value just use Rails.application.credentials

Rails.application.credentials.aws[:access_key_id]
=> 123

Encrypted secrets for non-Rails applications

Gem sekrets is the most flexible solution I’ve worked with. It works just fine for any kind of Ruby app. Even rails encrypted secrets were based on this.

Add to Gemfile of Rails project:

gem 'sekrets'

Then create an encrypted config file (for each needed environment)

ruby -r yaml -e'puts({:some_key => 000}.to_yaml)' | sekrets write config/sekrets.yml.development.enc --key yoursecretkey

Keep your secret key into .sekrets.key file:

echo yoursecretkey > .sekrets.key

Now you can edit them with the following task:

sekrets edit config/sekrets.yml.development.enc

Add this line to config/application.rb which will load secrets for the current environment

require_relative 'boot'
require 'rails/all'
Bundler.require(*Rails.groups)
module Rails5Cred
class Application < Rails::Application
config.sekrets = Sekrets.settings_for(Rails.root.join('config', "sekrets.yml.#{Rails.env}.enc"))
end
end

To get the desired value use Rails.configuration.sekrets

Rails.configuration.sekrets['some_key']
=> 0

Add to Gemfile of Ruby project:

gem 'sekrets'

Here is an example of a secret reader class for a rack-based env variable RACK_ENV . Just replace it, if you have another environment id.

class Secret
def self.[](key)
root = Pathname.new('./').expand_path
sec_key = File.read(root.join('.sekrets.key')).strip
Sekrets.settings_for("./config/sekrets.yml.#{ENV['RACK_ENV']}.enc", key: sec_key)[key]
end
end

The process of creating and editing a file with variables is identical for Rails older than 5.x. To receive values just call Secret class. But first of all this file should be required globally or in a used location.

Secret['some_key']
=> 0

Conclusions

  • Separate encryption key for each environment. Do not create a single key for all environments. It is safer to have separate keys for CI, development, and production
  • Encrypted secrets make easier deploys. Variables can now be shipped with the code. You only need to upload the key to the server once.
  • This solution can be used for any kind of Ruby or Rails application.

Kirill Shevchenko

Written by

Software Engineer. Interested in Full-Stack Development and DevOps.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade