🤫 Secrets, environment variables & config files: the Ruby On Rails case
Tags: rails, security, configuration
Published: 2019-12-16 16:25:07

---
title: 🤫 Secrets, environment variables & config files: the Ruby On Rails case
published: true
description: This article walks you through the various ways to store and read configuration values in Ruby On Rails. How to and should you use credentials? Environment variables or configuration files?
tags: #rails #security #configuration
cover_image: https://thepracticaldev.s3.amazonaws.com/i/71uensuykt5hulorqjpv.jpg
---

👋 Hi there and welcome to my [5th post on dev.to](https://dev.to/vvo) 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](#custom-configuration-an-example-use-case)
- [Problems with "environment variables for everything"](#problems-with-environment-variables-for-everything)
- [Rails credentials](#rails-credentials)
- [Configuration files](#configuration-files)
- [Environment variables](#environment-variables)
- [Going further](#going-further)

# 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:
- Something that can be public: [A Slack application client id](https://api.slack.com/apps)
- A token that must stay private: [A Slack application client secret](https://api.slack.com/docs/oauth-safety#client_id_and_secret)
- The `port` your rails server runs on

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](https://thepracticaldev.s3.amazonaws.com/i/eg2nkdxfhduaj1fpm54q.png)

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

**.env**
```txt
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
```

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

```js
const cacheInSeconds = parseInt(process.env["DATA_CACHE_IN_S"], 10);
```

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](https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/) 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?](https://thepracticaldev.s3.amazonaws.com/i/2qlp1ze9eaqiz46gvr0h.png)

The official documentation on [Rails credentials](https://guides.rubyonrails.org/security.html#custom-credentials), once you manage to [find it](https://github.com/rails/rails/issues/36218#issuecomment-564482141), is well written but lacking some details. Here's the tl;dr; for you:

- Rails credentials are encrypted [YAML](https://yaml.org/) 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 [`.gitignore`d](https://help.github.com/en/github/using-git/ignoring-files) 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](https://medium.com/@michaeljcoyne/understanding-the-secret-key-base-in-ruby-on-rails-ce2f6f9968a1) 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](https://stackoverflow.com/questions/3539594/change-the-default-editor-for-files-opened-in-the-terminal-e-g-set-it-to-text)
- You can have [per-environment credentials](https://blog.bigbinary.com/2019/07/03/rails-6-adds-support-for-multi-environment-credentials.html) 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:**
```bash
rails credentials:edit
```

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

**in your editor**
```txt
# content
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: ***
slack:
  client_secret: '***'
```

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](https://thepracticaldev.s3.amazonaws.com/i/jbu1mb302u5lp0vxb2u1.png)

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](https://guides.rubyonrails.org/configuring.html#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**
```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
```

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**
```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
```

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](https://thepracticaldev.s3.amazonaws.com/i/zdh8652hob8fv13003tn.png)

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](https://github.com/DarthSim/overmind) to easily start and manage Rails, webpacker and docker-compose via a single command. You can [configure overmind](https://github.com/DarthSim/overmind#usage) 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](https://devcenter.heroku.com/articles/heroku-postgresql#provisioning-heroku-postgres) 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](https://www.vaultproject.io/) for secrets and [comfygure](https://github.com/marmelab/comfygure) for configuration. And there are [many more secret management softwares](https://gist.github.com/maxvt/bb49a6c7243163b8120625fc8ae3f3cd).

---

🔚 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:

{% twitter 1206611592156209152 %}

![A secret cat appears (animated)](https://thepracticaldev.s3.amazonaws.com/i/wuhairbkc0t4idts3zdv.gif)