Extracting secret credentials from your code

November 28, 2015

If your source code contains credentials for your production environment, you're looking for trouble (do you remember the Ashley Madison leak?).

Still, I often find Rails apps where database.yml contains the credentials to access the production database, a hardcoded SECRET_TOKEN or fragments like:

1
2
3
4
if Rails.env.production? SomeService.api_key = "..." elsif Rails.env.staging? SomeService.api_key = "..."

The Twelve-Factor methodology recommends to store the config in environment variables, which in Ruby would look like:

1
SomeService = ENV["SOME_SERVICE_API_KEY"]

Platforms like Heroku provide advanced and secure ways to configure this environment variables in your servers, but it may sound difficult to implement in development for two reasons: - Development environments tend to store and run different apps, which may cause name conflicts in the env. variables. - Setting up the environment variables manually when a developer joins a team can be time consuming and error-prone.

There are two approaches to overcome those issues. Both require you to write your configuration variables in a text file called .env. For our previous example the .env file would contain:

1
SOME_SERVICE_API_KEY=the-api-key-goes-here

This may sound as dangerous as storing the credentials in the repo, but there's a difference. You'll have a .env file for each environment (you can call them .env.production, .env.staging, .env.development, .env.test, etc).

This way, .env.development and .env.test can be stored in the repo (as they only contain credentials for local or safe-for-development services), so all the development team can share them, making very easy to keep them in sync and using them to setup new development machines.

For your servers, you have a two options:

  • Setting actual environment variables manually or with the automation system of your choice.
  • Using .env.production and .env.staging files, which will only be stored in their respective servers and will never need to be in your repository.

Now, how can you load the configuration from an .env file so your app can access it? I'll describe two options.

Loading the credentials from the app

There are libraries like [dotenv](https://github.com/bkeepers/dotenv) for Ruby that allow us to load the environment variables from an .env file. Check the README or this tutorial to see how to integrate dotenv with your Ruby app.

If you don't use Ruby, there are similar libraries for many other languages.

Preloading the credentials before starting the application

Tools like foreman, forego or Heroku Local allow you to specify different processes you'd like to run by writing a text file named Procfile (with no extension).

A Procfile follows a simple format: process-name: the command to run that process.

Let's experiment with a simple Procfile defining two processes:

  • test1 will execute echo Hello, printing the fixed value Hello.
  • test2 will execute echo My name is && echo $NAME, which also reads and prints the value of the $NAME env. variable

The Procfile will look like:

1
2
test1: echo Hello test2: echo My name is && echo $NAME

Now, let's run those processes. I'll use foreman in my examples, but forego and Heroku Local work similarly.

We can run the first process with:

1
2
3
foreman run test1 # Output: Hello

and the second with

1
2
3
foreman run test2 # Output: prints My name is

Now, if we write a small .env.development file containing

1
NAME="Raul Murciano"

we can now ask foreman to read it to configure the environment variables used while running test2:

1
2
3
4
foreman run -e .env.development test2 # Output: My name is Raul Murciano

We can extrapolate this little example to run things like a test suite or an app by using foreman run -e <SOME-ENV-FILE> <SOME-COMMAND>.

Drawbacks

The only drawback I see with these approaches is that both of them a dependency, in the form of an external tool (when using forego et al) or an internal library (in dotenv or the different ports to different languages).