How to protect DB credentials for Rails app

This gem helps us to keep DB credentials for rails app in private place.

Installation

Add this line to to your Gemfile:

gem "shadow_db_credentials"

And then execute it:

$ bundle

Usage

Create “shadowed” credentials file (your_prod_credentials) inside some directory (e.g. ~/.credentials):

username: your_username
password: your_password

The location of your credentials directory is controlled by CREDENTIALS_DIR environment variable. Register it in config/initializers/env_variables.rb file:

ENV['CREDENTIALS_DIR'] ||= "#{ENV['HOME']}/.credentials"

If you want to have different credentials directory per environment, define it in corresponding env file:

# config/environments/development.rb
...
ENV['CREDENTIALS_DIR'] ||= "#{ENV['HOME']}/.credentials"

Remove all the credentials (username/password) that you don’t want to keep inside config/database.yml and replace them with single credentials attribute. It points to the name of your_prod_credentials file:

development:
  adapter: postgresql
  database: your_dev_db
  credentials: your_dev_credentials

production:
  adapter: postgresql
  database: your_prod_db
  credentials: your_prod_credentials

Note: If you want, you can move other sensitive information as well, such as database name, url etc.

adapter : postgresql
database: your_prod_db
username: your_username
password: your_password

Next, you have to create code hook inside rails config/application.rb in order to call gem’s API:

require 'shadow_db_credentials'

...

module YourRailsApp
  class Application < Rails::Application
    ...

    def config.database_configuration
      orig_db_configurations = super

      processor = ShadowDbCredentials.new(ENV['CREDENTIALS_DIR'])

      processor.process_configurations(orig_db_configurations)
    end
  end
end

The hook will access original DB configuration and try to expand all credentials attributes with corresponding values dynamically, at run time.

If you think that processing all environments is not necessary, you cant process only current environment:

module YourRailsApp
  class Application < Rails::Application
    ...

    def config.database_configuration
      orig_db_configurations = super

      processor = ShadowDbCredentials.new(ENV['CREDENTIALS_DIR'])

      processor.process_configuration(orig_db_configurations, Rails.env)
    end
  end
end

You can check result of your work:

$ rails console production
> ActiveRecord::Base.configurations["production"]
 => {"adapter"=>"postgresql", "username"=>"your_username", "password"=>"your_password"}

> Rails.application.config.database_configuration['production']
 => {"adapter"=>"postgresql", "username"=>"your_username", "password"=>"your_password"}

Or you can get connection information with API call:

require 'shadow_db_credentials'

credentials_dir = ENV['CREDENTIALS_DIR']
processor = ShadowDbCredentials.new(credentials_dir)

# 1. get production hash, read configuration from default location

prod_conf = processor.retrieve_configuration "production"
prod_conf.inspect

# 2. get development hash, read configuration from dynamic source

source = StringIO.new <<-TEXT
  development:
    adapter: postgresql
    credentials: your_dev_db
TEXT

dev_conf = processor.retrieve_configuration 'development', source
dev_conf.inspect