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 executeecho Hello
, printing the fixed valueHello
.test2
will executeecho 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).