It has happened to us all. It’s late at night and you’re working on your side project. You’ve been trying to get that darned external API working for hours, and OH MY GOODNESS IT FINALLY WORKS! You push your changes up and abandon your computer for some well deserved rest. That is, however, until you wake up in the morning and your plain text API key you just threw into your app has been stolen by some jerk on the internet who was looking through GitHub search for unknowing victims. That causes your app to be hacked, your bank accounts to be emptied, and your inevitable arrest.
No? No one? Ok, maybe that’s just happened to me then.
“Ugh whatever I don’t care,” you may be thinking. “My repository is private.” Well, smartypants, you still need to be careful! If you ever want to open source that repository, your Git history will have these keys smattered throughout for the public to steal. Even if you keep the repository private, what if you want to give a contractor access temporarily?
Fellow Bugsnagger Conrad Irwin wrote dotgpg, a gem made for backing up and versioning your application secrets or shared passwords securely and easily. dotgpg makes the encryption process pretty simple, only interfacing with the basics of GPG that are needed for what we’re trying to accomplish.
According to Twelve-Factor, an app is supposed to store its configuration options in environment variables. Like that API key you just lost. In your code, you should be referencing ENV["SECRET_PASSWORD"], grabbing the value associated with the “SECRET_PASSWORD” key.
Rails 4.1 has this handy change that includes a secrets.yml file. All we have to do is add our keys to the file and reference them with Rails.application.secrets.secret_password. Easy enough!
How do these environment variables get set up? Well, in a Unix shell, you could always export them via command line.
However, this only lasts for that shell session and then disappears. One solution is to export the environment variable in one of our shell startup scripts (.bash_profile, .zshrc, etc.). Another is to make a file that stores all of these secrets and then read it in a Rails initializer so that it loads our variables on boot.
We recommend just using a gem like dotenv that has already solved this problem for us. Dotenv is just a shim to load environment variables from a file named .env. Either way, we’ll have to add the file containing our secrets to the .gitignore to make sure we don’t push it up.
brew install gpg
Now that we have GPG installed, we need to generate a public and private key pair.
There’s only a few rules surrounding the key pair you’ve just made!
*There’s a small chance you won’t be sent to jail, but these are still very important.
Some people store their keys on a flash drive and put it in an actual physical safe, some keep them in cloud providers like Dropbox, and some write them on actual paper. The same goes for your key passcode (I store my key passcodes in 1Password).
For more information about GPG, see the Extra Notes & Resources section.
To get started, you need to either run gem install dotgpg or add gem "dotgpg" to your Gemfile and run bundle. Then, you need to run:
Ok awesome! This created a .gpg folder, which will later be used to hold each team member’s public key.
A new team member can run dotgpg key to output her public key, which she must then send to an existing team member. If she hasn’t already created a key, this will walk her through the prompts to do so.
When the key is sent over, the existing team member can run dotgpg add, which will prompt her to paste in the new team member’s public key. This will create a file named by the new team member’s GPG e-mail. The file will contain her public key, and will be automatically added into the .gpg folder.
Now all collaborators can create and edit files with:
dotgpg edit <pgp-encrypted-filename>
This will open <pgp-encrypted-filename> in your default editor (which you can change by setting the EDITOR environment variable). After entering your GPG passphrase, you’ll see the decrypted file!
You can also decrypt a PGP-encrypted file and pipe it to standard out (which will also ask for your GPG passphrase):
dotgpg cat <pgp-encrypted-filename>
So now you’re putting all your environment variables in .bash_profile, secrets.yml, or .env, and your coworker is coming up to you because she is having a problem starting her local server. She doesn’t have ENV["SECRET_PASSWORD"]! Teams have different ways of sharing keys, but now each person generally has to play fill-in-the-blank-scavenger-hunt, go-find-a-random-google-docs-password-file-that-feels-kinda-insecure, overhaul-the-companies-entire-setup-process-by-making-everyone-use-boxen, or the classic bug-my-coworker-until-she-IMs-me-the-key.
Let’s say your repository has a PGP-encrypted config/secrets.yml.gpg file (or .env.gpg if you’re using Rails pre-4.1). Since config/secrets.yml.gpg is encrypted, random users can’t read the application secrets and passwords contained within. However, since your public key has been added to the repository, you can run:
dotgpg cat config/secrets.yml.gpg > config/secrets.yml
You now have a local copy of all the application secrets and passwords in config/secrets.yml! Make sure this is in your .gitignore, lest you defeat the whole purpose of all this extra security.
Now, even if you made this repository public, you’re still safe. Only existing team members can dotgpg add new public keys, and only people whose public keys have been added can decrypt config/secrets.yml.gpg. Removing a team member would entail deleting her public key and rolling the secrets. Unapproved viewers will be locked out, and will have to come up with their own set of application secrets and passwords!