Where to store sensitive data in public rails app?

12,840

Solution 1

TLDR: Use environment variables!

I think @Bryce's comment offers an answer, which I'll just flush out. It seems one approach Heroku recommends is to use environment variables to store sensitive information (API key strings, database passwords). So survey your code and see in which you have sensitive data. Then create environment variables (in your .bashrc file for example) that store the sensivite data values. For example for your database:

export MYAPP_DEV_DB_DATABASE=myapp_dev
export MYAPP_DEV_DB_USER=username
export MYAPP_DEV_DB_PW=secret

Now, in your local box, you just refer to the environment variables whenever you need the sensitive data. For example in database.yml :

development:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: <%= ENV["MYAPP_DEV_DB_DATABASE"] %>
  pool: 5
  username: <%= ENV["MYAPP_DEV_DB_USER"] %>
  password: <%= ENV["MYAPP_DEV_DB_PW"] %>
  socket: /var/run/mysqld/mysqld.sock

I think database.yml gets parsed just at the app's initialization or restart so this shouldn't impact performance. So this would solve it for your local development and for making your repository public. Stripped of sensitive data, you can now use the same repository for the public as you do privately. It also solves the problem if you are on a VPS. Just ssh to it and set up the environment variables on your production host as you did in your development box.

Meanwhile, if your production setup involves a hands off deployment where you can't ssh to the production server, like Heroku's does, you need to look at how to remotely set up environment variables. For Heroku this is done with heroku config:add. So, per the same article, if you had S3 integrated into your app and you had the sensitive data coming in from the environment variables:

AWS::S3::Base.establish_connection!(
  :access_key_id     => ENV['S3_KEY'],
  :secret_access_key => ENV['S3_SECRET']
)

Just have Heroku create environment variables for it:

heroku config:add S3_KEY=8N022N81 S3_SECRET=9s83159d3+583493190

Another pro of this solution is that it's language neutral, not just Rails. Works for any app since they can all acquire the environment variables.

Solution 2

How about this...

Create a new project and check it into GitHub with placeholder values in the production.yml and development.yml files.

Update .gitignore to include production.yml and development.yml.

Replace the placeholder values with your secrets.

Now you can check your code into GitHub without compromising your secrets.

And anyone can clone your repo without any extra steps to create missing files (they'll just replace the placeholder values as you did).

Does that meet your goals?

Solution 3

Rails 4.1 has now a convention for it. You store this stuff in secrets.yml. So you don't end up with some global ENV calls scattered across Your app.

This yaml file is like database.yml erb parsed, so you are still able to use ENV calls here. In that case you can put it under version control, it would then serve just as a documentation which ENV vars has to be used. But you also can exlcude it from version control and store the actual secrets there. In that case you would put some secrets.yml.default or the like into the public repo for documentation purposes.

development: 
   s3_secret: 'foo'
production: 
   s3_secret: <%= ENV['S3_SECRET']%>

Than you can access this stuff under

Rails.application.secrets.s3_secret

Its discussed in detail at the beginning of this Episode

Solution 4

Use environment variables.

In Ruby, they're accessible like so:

ENV['S3_SECRET']

Two reasons:

  1. The values will not make it into source control.
  2. "sensitive data" aka passwords tend to change on a per-environment basis anyways. e.g. you should be using different S3 credentials for development vs production.

Is this a best practice?
Yes: http://12factor.net/config

How do I use them locally?
foreman and dotenv are both easy. Or, edit your shell.

How do I use them in production?
Largely, it depends. But for Rails, dotenv is an easy win.

What about platform-as-a-service?
Any PaaS should give you a way to set them. Heroku for example: https://devcenter.heroku.com/articles/config-vars

Doesn't this make it more complicated to set up a new developer for the project?
Perhaps, but it's worth it. You can always check a .env.sample file into source control with some example data in it. Add a note about it to your project's readme.

Solution 5

They're probably best put in initializers (config/initializers/api.yaml) though I think what you've got cooked up is fine. Add the actual keys to your .gitignore file and run git rm config/environments/production.yml to remove that sensitive data from your repo. Fair warning, it will remove that file too so back it up first.

Then, just create a config/environments/production.yml.example file next to your actual file with the pertinent details but with the sensitive data left out. When you pull it out to production, just copy the file without the .example and substitute the appropriate data.

Share:
12,840
tybro0103
Author by

tybro0103

Coffee junkie, computer nerd, fitness monster, and father of two cool dudes.

Updated on June 02, 2022

Comments

  • tybro0103
    tybro0103 almost 2 years

    My personal rails project uses a few API's for which I store the API keys/secrets in config/environments/production.yml and development.yml as global variables. I now want to push this project to github for others to use, but I don't want them to have those bits of sensitive data. I also don't want this file in .gitignore because it's required for the app to run. I've considered putting them in the DB somewhere, but am hoping to find a better solution.

  • tybro0103
    tybro0103 almost 13 years
    Yeah I hear you, but as said, I'm trying to find a solution that doesn't involve removing those files from the repository.
  • Max Williams
    Max Williams almost 13 years
    Out of interest, why? Are you deploying to heroku or somewhere similar that doesn't easily let you copy files directly onto a server?
  • tybro0103
    tybro0103 almost 13 years
    Yes. That, and I want it to be as simple as possible for others to get my code running.
  • tybro0103
    tybro0103 almost 13 years
    Not really, because then what happens when I add something to those files that does needs to make its way to other repositories?
  • Daniel Kehoe
    Daniel Kehoe almost 13 years
    If that won't happen often, just restore the placeholder values, remove the line from .gitignore, commit to GitHub with the changes, edit the files to restore your secrets and edit .gitignore to once again hide your secrets. There may be solutions that are more clever but this approach is simple.
  • brycemcd
    brycemcd almost 13 years
    the config files are conventional and wouldn't add any stumbling blocks to someone who needed to get your code running. Heroku has provided some docs on how to get them set up in their environment: devcenter.heroku.com/articles/config-vars
  • piersadrian
    piersadrian about 11 years
    This is by far the best solution presented here. Secure, simple, effective cross-platform, and performant. Couldn't ask for more.
  • Yo Ludke
    Yo Ludke about 11 years
    @yuvilio I have a follow-up question: How would one make said environment variables accessable to Ruby on Rails running on Ubuntu with Apache2/Phusion Passenger?
  • yuvilio
    yuvilio about 11 years
    @YoLudke For an app that's directed to through Apache, I would use the SetEnv directive in my Apache host file for the app. For example: SetEnv S3_KEY THESECRETKEY. The environment variable would be available through the ENV global variable as ENV['S3_KEY'] as well.
  • Yo Ludke
    Yo Ludke about 11 years
    Just to mention it - when your using Phusion Passenger with Apache2 you can direct Phusion Passenger in its Apache configs to a script that exports the variables before running ruby - instead of directly running ruby, and this works perfectly for me #!/bin/sh export EXAMPLE_VAR=example_value exec "/path/to/rvm/.rvm/wrappers/ruby-1.9.3-p371/ruby" "$@"
  • tybro0103
    tybro0103 about 10 years
    Worth mentioning Foreman (part of Heroku's toolbelt) here: ddollar.github.io/foreman/#ENVIRONMENT
  • tybro0103
    tybro0103 almost 10 years
    I provided this same answer, hopefully, a little more concisely... see below.
  • Zhomart
    Zhomart about 9 years
    There could be a bug that shows env variables, for example when app crashes (hello php). Don't you think it's better to store secrets in config files and put the files to the server manually (or using scripts).
  • shevy
    shevy about 4 years
    Where should this file be stored?