DEV Community

Cover image for 🤫 Secrets, environment variables & config files: the Ruby On Rails case
Vincent Voyer
Vincent Voyer

Posted on • Updated on

🤫 Secrets, environment variables & config files: the Ruby On Rails case

👋 Hi there and welcome to my 5th post on dev.to about Ruby On Rails learning. People started to ask me what were my motivations for writing articles (instead of doing 100% coding!). It's simple: I write the articles I wish I had found online. Everytime I spend one afternoon digging a particular subject, assembling various documentation and articles then I feel like I need to write it down. Both for me and for other people interested in the subject.

As a developer learning a new platform but already knowing how to program, the area where I spend the most of my time is: decision making. When you have potentially three ways to do one thing then you have to decide which way to take.

While this article is linked to Ruby On Rails, the challenge and solutions as for storing configuration is the same in all other platforms so you should keep on reading even if not using Rails.

As always, if you want to discuss and update this article with your experience and point of view, just add a comment and I'll be happy to reply.

Table of contents:

Custom configuration: an example use case

Today we'll talk about environment variables, config files & credentials. People (including me!) frequently wonder: "I have this configuration need, where do I store it?". Especially in Ruby On Rails where you are offered multiple ways to store configuration information.

Let's say you have three values you want to store and access in your Rails application:

Where to store all of that? Knowing that: you don't want to compromise on security, want be able to easily access the data (without parsing it) and have a clear indication in your code about the type of configuration you're dealing with. Turns out that the decision about where to store this configuration actually depends on the values it holds. Is this infrastructure configuration (port)? Public application configuration (client id)? Or private application configuration (client secret)?

First, let's revisit the often overlooked advice: "Use environment variables for everything".

Problems with "environment variables for everything"

12 factor configuration section

As with every best practices, we should be careful about this one. It comes from the 12factor apps recommendations. When programming in Node.js I used to store everything in a .env file and environment variables in production. This usually looks like:

.env

SLACK_CLIENT_ID=somevalue # manual namespacing
SLACK_CLIENT_SECRET=somevalue
ALGOLIA_API_KEY=somevalue
ALGOLIA_APPLICATION_ID=somevalue
WEB_CACHE=true # will this be a boolean in my code or a string containing 'true'?
DATA_CACHE_IN_SECONDS=60 # will this be a string in my running app? an integer?
# ... dozen of other configuration values
Enter fullscreen mode Exit fullscreen mode

And the mighty parsing side (Node.js code example):

const cacheInSeconds = parseInt(process.env["DATA_CACHE_IN_S"], 10);
Enter fullscreen mode Exit fullscreen mode

Not so practical.

Now when deploying, you have to decide if you should push this file to server or maybe configure your hosting provider to provide those environment variables to your production application (more decision making!). Which means deduplicating some of those variables again.

Here are the the issues of using only .env files for configuration needs:

  • the risk of committing those files
  • the risk of leaking ENV variables to another service like error handling and logging.
  • no parsing of booleans, integers, you have to do that yourself via a thing layer or add more dependencies to handle that
  • no namespacing but manual via conventions like ALGOLIA_
  • the challenge of deploying such configuration

Here's a good writeup on some of those issues in more details: gist https://gist.github.com/telent/9742059.

So, what should we use instead of environment variables?

Rails credentials

Got some secrets?

The official documentation on Rails credentials, once you manage to find it, is well written but lacking some details. Here's the tl;dr; for you:

  • Rails credentials are encrypted YAML files, the default one being config/credentials.yml.enc. Open it, you'll see it's only a big encrypted string)
  • the encryption key is either a master.key file (automatically created and .gitignored when using rails new app) or the RAILS_MASTER_KEY environment variable (in production you would have to create it)
  • Their default content is the secret_key_base key (different from RAILS_MASTER_KEY), used to encrypt and sign cookies for example
  • Since it's YAML, you automatically get namespacing and parsing included
  • You can access credentials from your code via Rails.application.credentials.slack[:client_secret]
  • You can edit credentials via rails credentials:edit, it opens your default editor
  • You can have per-environment credentials or global ones. To edit environment specific credentials use rails credentials:edit --environment production

Pretty neat!

Going back to our example, we would use Rails credentials to store the Slack application client secret this way:

in a terminal:

rails credentials:edit
Enter fullscreen mode Exit fullscreen mode

You'll notice the previous command takes some time to open your editor, because it first decrypts the file

in your editor

# content
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: ***
slack:
  client_secret: '***'
Enter fullscreen mode Exit fullscreen mode

Save and then in your own code, you can use this via Rails.application.credentials.slack[:client_secret].

But wait wait, since the master key encrypting the credential files needs to be an environment variable, how is this better than just storing secrets in environment variables?

That's a question I asked myself too. The difference is that if an attacker manages to access your environment variables, for example because it leaked on your logging service that got compromised, they would still have to access your hosting server and its filesystem to be able to use that key and read the secrets. Just like you can lose the key of your house, it doesn't mean people will be able to break into your house, because they don't know where you live.

Configuration files

Example configuration file

You could decide to store everything in Rails credentials because it's tempting to put everything in a single place, like with environment variables. But I like to be able to tell, from the code, what's public and what's private so I don't make mistakes later on. Plus for many configuration needs, it's great to be able to easily open a file and edit its content from my editor, without having to use rails credentials:edit every time I have the need.

For this need, you can use Rails configuration files. There are two main ways to add custom configuration:

  1. Edit config/environments/*.rb files and add your own namespace there
  2. Create YAML files in config/

I like to keep config/environments/*.rb files for actual Rails configuration and use other files to create application configuration. This is actually what the Rails guides suggest in the Custom Configuration section.

So in our case this means that we would use (2.) a custom YAML configuration file to store our ok-to-be-public Slack configuration.

config/slack.yml

default: &default
  sign_in_scopes: identity.basic,identity.email,identity.team,identity.avatar
  bot_scopes: chat:write,usergroups:read,users:read
  client_id: "858087363780.858555035760"

development:
  <<: *default

production:
  <<: *default
Enter fullscreen mode Exit fullscreen mode

Note that you have to define the various environments in the file otherwise it won't work. Here they just inherit from the default configuration, this is using YAML anchors just like the regular Rails configuration files.

Then load this file:

config/application.rb

# ... bunch of code

module MyApp
  class Application < Rails::Application
    # ... bunch of code
    config.slack = config_for(:slack) # this line loads the config/slack.yml file and store it in this namespace
  end
end
Enter fullscreen mode Exit fullscreen mode

That's it! Now you can access your Slack configuration at any moment this way: Rails.configuration.slack[:sign_in_scopes].

One might think that this is not so great to have to use either Rails.configuration.slack[:sign_in_scopes] or Rails.application.credentials.slack[:client_secret]. But again, I enjoy being able to tell what's a private credential from what's just regular configuration: your call!

Environment variables

Example of Heroku configuration for environment variables

We've seen how to store sensitive credentials and regular configuration with Rails. What are environment variables good for then? Again good question, nowadays I use them for either

  • Rails application PORT configuration if needed: on which port does Puma runs (the default Rails web server). See config/puma.rb in your project
  • Developer setup configuration, I use overmind to easily start and manage Rails, webpacker and docker-compose via a single command. You can configure overmind via a .env file that is also injected, by Overmind, into your Rails application automatically (no need for a specific gem).
  • Any configuration that is related to the infrastructure supporting my application rather than application and business logic configuration
  • Any configuration I know I am gonna edit in production, like debugging purposes with LOG_LEVEL, DEBUG
  • Any configuration my hosting provider already manages via environment variables, like the DATABASE_URL environment variable

In general, anything truly dynamic and non sensitive could be stored in environment variables.

Going further

While I am satisfied with Rails semantics as for configuration and secrets handling, you might find some companies going even further and using dedicated software to manage configuration and secrets. Especially if you have many servers and platforms willing to share secrets and configuration. For example Vault by HashiCorp for secrets and comfygure for configuration. And there are many more secret management softwares.


🔚 That's it!

🙏 Thank you for reading.

Any advice as for secret and configuration handling in Rails? Add your point of view or thank you note in the comments.

If you enjoyed this post, click below to share it:

Liquid error: internal

A secret cat appears (animated)

Top comments (4)

Collapse
 
guillaumeocculy profile image
Guillaume Occuly • Edited

Great article!

Is there any way to have a default value with "master" credentials ?
I explain myself :
If Rails.application.credentials.foo is nil on staging (config/credentials/staging.yml.enc),
we look to config/credentials.ymc.enc to find the default value BAR

Collapse
 
vvo profile image
Vincent Voyer

Hey there, I am no more using Rails so really I dunno! You can ask on their GitHub I guess and reply here if you find something. Good luck

Collapse
 
rochacbruno profile image
Bruno Rocha

I created Dynaconf (for Python) which resolves everything you mentiones including yaml, toml, json files, secrets, envvars and vault services.

are there any rails alternative?

Github.com/rochacbruno/dynaconf

Collapse
 
vvo profile image
Vincent Voyer

Hi there, I would say maybe the closest to that would be github.com/laserlemon/figaro but not sure so I hope people with experience in the Rails community will jump in also!

For now, Rails semantics are completely fine for most applications though.