👋 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
- Problems with "environment variables for everything"
- Rails credentials
- Configuration files
- Environment variables
- Going further
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
- A token that must stay private: A Slack application client secret
portyour 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".
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:
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):
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 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
- 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 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.keyfile (automatically created and
.gitignored when using
rails new app) or the
RAILS_MASTER_KEYenvironment variable (in production you would have to create it)
- Their default content is the
secret_key_basekey (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
- 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
Going back to our example, we would use Rails credentials to store the Slack application
client secret this way:
in a terminal:
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: '***'
Save and then in your own code, you can use this via
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.
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:
- Edit config/environments/*.rb files and add your own namespace there
YAMLfiles 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.
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:
# ... 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:
One might think that this is not so great to have to use either
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!
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
.envfile 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
- 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.
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: