Keeping secrets secret without using .gitignore

In the past I used to keep all files containing sensitive data (passwords, api keys and other secrets) out of my git repository. For example, I would add database.yml in the .gitignore file. Then I would put my database.yml on my production server in the « shared » folder. Finally I would ask capistrano to create a symlink to that file in the deployment recipe. This has been fine with me for a while.
But one day, a small gnome came in my home office, all dancing and laughing. So i said : « what are you doing here, little gnome? ». He first told me that I should not speak to him aloud like that, as it would probably scare my wife and my kids and that they would start being concerned with my mental health. « Good point, I kept to myself ». Then, he told me : « There is a better way! You don’t have to rely on .gitignore if you don’t want to expose your secrets. Let me show you how…»
Since that day, my application files that contain secrets are in my repository. And, as you probably guessed, I use environment variables in those files instead of plain-text secrets, exactly like it is suggested in the secrets.rb file of a rails project. You probably have seen it already:

# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

Hey, It’s not rocket science! Why a blog post about this!?
Because I struggled at first to set these environment variables correctly. How to set the variables in developement? How to set them in production?
At first I tried using the rbenv-vars plugin both in development and in production. rbenv-vars is a simple plugin for rbenv that let you declare environment variables in a straightforward manner. You just create a .rbenv-vars file in your application directory that looks like this :

DB_USER=db user
DB_PASS=db password
aws_access_key_id=some access key here
aws_secret_access_key=secret access key here

And you’re ready to go (as long as you use rbenv!). Of course this file should not be in your repository as it contains secrets. And since the file is not really a part of your application to begin with, adding it to .gitignore makes complete sense. Anyway, to the point. rbenv-vars worked perfectly on my development machine, so I decided to use it in production, but it didn’t work very well this time.
The Phusion Passenger gotcha
On one of my production server, I use Phusion Passenger, and no matter what I tried it would not set the environment variables configured in my .rbenv-vars file. I know others had success with this approach but it didn’t work for me.
If you use Phusion Passenger (>= 5.0) like me and .rbenv-vars doesn’t work for you neither, just set the environment variables in your nginx or apache configuration file, like this (I use nginx) :

server {
  listen 80;
  server_name mygreatapp.com
  root /home/username/apps/mygreatapp/current/public;
  passenger_ruby /home/username/.rbenv/versions/2.2.4/bin/ruby;
  passenger_enabled on;
  passenger_env_var SECRET_KEY_BASE supersecret
  passenger_env_var DB_USER mydbuser;
  passenger_env_var DB_PASSWORD shhhItsSecret;
}

All in all I’m really happy with this approach. It allows me to keep files like secrets.yml and database.yml in my repository (instead of gitignoring them) without exposing passwords or other secrets.

5 thoughts on “Keeping secrets secret without using .gitignore

  1. I always wondered. Do people .gitignore them when they have private got repositories? When I’m working on a private one I usually don’t bother.

  2. Daniel Hollands: The gnome was indeed short & sweet, but in the end I chose to .gitignore him to keep my sanity!
    Gary: In the past I would .gitignore database.yml even in private (non open-source) repositories. Otherwise it would have exposed my own development database login info to my co-workers. Not only would it have been a privacy issue, it would have also force everyone to put back their own database passwords after each “git pull”.
    But it’s true that if you are the only one who has an access to the repository, I guess it is a non issue.

  3. Ahh, the gnome is a metaphor.. I get metaphors!
    We use saltstack to populate secrets on deployments to qa/staging/production/etc, but heroku and aws/opsworks offer fairly pain free ways to deal with this.

  4. It’s still a good idea to keep secrets out of your repo. Not only because they’re secrets and “git repos are forever” (it’s very cumbersome to actually erase history from a git repo), but because they’re not part of your application; they’re configuration.
    Take the database user as an example. Is the database username really part of your application, or is it part of the configuration for a particular installation? What happens when you need to install your application in multiple locations? You shouldn’t store configuration within the application source code for similar reasons that you shouldn’t use string literals all over your application.
    Configuration is the responsibility of the person/tool that will deploy the application. Your choice of deployment methodology/tool may be dictated by any number of factors. For example, Heroku provides a very specific method of deployment. When using Passenger, it might make more sense to use the secrets.yml file directly (excluding it from your repo), relying on a provisioning tool like Capistrano to link the file in from the ‘shared/’ folder provided by Capistrano’s standard layout, while your server provisioning tool (like Chef) stores the config for a given instance.
    Personally, I get a little annoyed by the whole 12-factor dogma of using environment variables for everything. How many server applications have you installed where all the config is stuffed in to the environment. It’s not uncommon to see some bootstrapping config values or flags passed through the environment, but cramming the entire config in there always seemed crazy to me. It also seems a little crazy to me that this approach treats secrets.yml as a configuration map. As you observed, using the environment is just a layer of indirection to another system of configuration. How is a .rbenv-vars file any better/different than a secrets.yml file that you don’t check in to your repo?
    What’s frustrating for system administrators is that your Rails + Passenger applications environment-based-configuration must now reside inside Nginx’s virtual host configuration. That is, IMO, a poor separation of concerns. I’d much rather see configuration compartmentalized with the app.
    The entire “configuration goes in the environment” solution was the result of Heroku’s immutable filesystem. It’s a fine workaround for Heroku, but it’s a workaround, none the less. There are other solutions that are just as valid, and may reduce the amount of indirection required, depending upon your application’s hosting stack.
    Sorry to turn this in to a 12-factor rant 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *