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
There are two most popular ways to manage secrets in your application.
- 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.
- 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: 345secret_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:
- Adding
dev, staging, production
keys and put all needed under the related key. - Override
credentials
method on Rails application.
Top-level Hash as an environment key.
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]
Override credentials
method on Rails application.
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.
Encrypted secrets with Rails older than 5.0
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
Encrypted secrets with pure Ruby apps (non-Rails)
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.