Managing (private) settings with SettingsLogic

In a Ruby on Rails app, there are settings. Many settings. Some can vary from development to production. Some are private as they may be passwords, secret tokens…

This is how I deal with those, using the tiny lib SettingsLogic.

A French political party (far and ultra left wing, Gasp!) recently released the source code of their main website under GPL license on Github. As it is made with Rails, I was curious. I found a few flaws: they unintentionally leaked some credentials and security settings. This is the occasion for talking about SettingsLogic, and how to deal with sensitive settings.

SettingsLogic

SettingsLogic lets you have yaml files to store your settings.

First of all, let’s add the gem in the Gemfile:

gem 'settingslogic'

Let’s create the settings class. In app/models/settings.rb:

# encoding: utf-8
class Settings < Settingslogic
  source "#{Rails.root}/config/settings.yml"
  namespace Rails.env
end

Pretty simple. I let you create the corresponding yaml file as specified in the class. Here is a template with an example:

defaults: &defaults
  pagination:
    comments: 10

development:
  <<: *defaults

test:
  <<: *defaults

production:
  <<: *defaults

It lets you managing different values according to the environment. To use it where you need to, just call:

Settings.pagination.comments

# An example:
@post.comments.desc(:published_at).page(params[:page]).per(Settings.pagination.comments)

Voilà, the basics. Now let’s see how I manage the settings_private.yml file.

The private file

I’m used to separate general settings (pagination values…) from private settings (credentials, secret tokens…) as I don’t want the later ones to be included in my CVS that’s backed on Github or Bitbucket. These credentials are plenty: SMTP, Amazon S3, Facebook, Twitter… Besides the many tokens which include the “pepper” of Devise. And the great forgotten: the secret_token (located in config/initializers/secret_token.rb) that secures your cookies !

Doing that in app/models/settings_private.rb:

# encoding: utf-8
class SettingsPrivate < Settingslogic
  source "#{Rails.root}/config/settings_private.yml"
  namespace Rails.env
end

And you have a settings_private.yml file. An idea is to have a settings_private-sample.yml that is synced with the real one. I use dummy values but the keys are synced, and it appears in my CVS. It’s cleaner and it permits to quickly generate the real one without browsing the source code of the whole app.

As it actually contains the stuff I use in dev and prod, I prevent the settings_private.yml file from being in my CVS by adding it to the .gitignore file:

# Do not include by mistake the private settings
/config/settings_private.yml

If you use capistrano, you can add a task that setup your private file while deploying. In my deploy path, next to the releases and shared directories, I create a private_files directory and manually place a settings_private.yml file that is setup for production. And I add a dedicated task in the config/deploy.rb capistrano’s file. It creates a symbolic link in the right place in the release path to the private file:

namespace :deploy do
  task :setup_private_files do
    ["settings_private.yml", "mongoid.yml"].each do |file|
      run "rm -fr #{release_path}/config/#{file}"
      run "cd #{release_path}/config && ln -sf #{shared_path}/../private_files/#{file} #{file}"
    end
  end
end
before "deploy:finalize_update", "deploy:setup_private_files"

You can see that I’m doing the same thing with my mongoid.yml or database.yml files.

Enjoy a simple, more secure and deployment-proof way to deal with your settings, even the secret ones.

Next article: A series of Ruby videos each Friday that improves yourself

Previous article: Hello, World!

Maxime Garcia
Ruby, Rails & JS lead developer in Paris.