SSW logo Blog - Michael's Musings

Tue, 26 Jan 2016

Setting pepper in staging/production

The devise authentication plug for rails has been a standard piece of instructure for me for years. One of those things you should not roll on your own.

In setting up the staging system for the IP4B SG2 system, I wanted to be able to copy bits of the production database to the staging system so that we can see real data. Logins were not working, and I was mystified until I realized that the pepper values in the config/initializers/devise.rb were different as I upgraded the devise, and started with a new file. I had set a longer pepper in the new revision, probably too long actually as it might prevent the per-user salt from having any effect.

I then realized that I don't want this pepper value into the git tree it all, it should be set by the production.rb, or better the secrets.yml. I didn't find a clear way to do this, so I had to Use The Source, Luke (curiously, in the Percy Jackson novels, Luke is a bad guy)

I used bundle show devise to tell me where devise was installed, and then I went into that directory with Emacs, and used grep -R for "pepper". I was also looking for where the Device.setup routine is and how it sets up the config. A clue was in test/models_test.rb, where I found:

    test 'set a default value for pepper' do
      assert_equal 'abcdef', Configurable.pepper

Hmm, what is the Configurable thing? Naw, it's in test/test_models.rb, so it's just for the test cases. The real meat is in lib/devise. I leant from test_models.rb that I can specify the pepper: in the devise model, so if I have to I can put it together in my app.

I decided to grep for secrets:

grep -R -nH -e secrets *
rails.rb:33:      if app.respond_to?(:secrets)
rails.rb:34:        Devise.secret_key ||= app.secrets.secret_key_base

Which is nice, as it points to the secret_key_base, which I knew I had to initialize, but that is used for the cookies. I don't need to synchronize the cookies between staging and production. I was hoping to put the pepper into the secrets.yml.

Looking at this, it seems that I ought to be able to add pepper to this, so I tried:

module Devise
  class Engine < ::Rails::Engine
    initializer "devise.pepper" do |app|
      if app.respond_to?(:secrets)
        Devise.pepper ||= app.secrets.pepper
        puts "APP has new pepper: #{Devise.pepper}\n"

I monkey patched this into my user.rb file, before I create the User. (I could fork and make a pull request on this, but I'll do that after I'm sure this is the right way) I wondered what app was, I suspect it comes from the ::Rails::Engine.

I stuffed my secrets.yml on my desktop with:

  secret_key_base: mysecret
  pepper: "happyhappyjoyjoy"

and invoked rails console. Good news it didn't blow up yet! I dumped the pepper:

2.1.5 :001 > Devise.pepper
 => "42e1d47ee85f2bb825429384729345234935924310c651bcdb822a65919d8b4"
Well, that's not the pepper I set. I went and I removed it from config/initializers/devise.rb, and restarted rails console.
2.1.5 :001 > Devise.pepper
 => nil

Okay, so there is a change... but it didn't load my monkey patch yet. Maybe if I make it load that code:

2.1.5 :002 > User.first
2.1.5 :003 > Devise.pepper
 => nil

nope. So let's see if my idea was even sound. I stuffed the initializer code into the lib/devise/rails.rb in my local gem repo, and low and behold, it ran:

obiwan-[nv/clientportaltest/beaumont](2.1.5) mcr 10027 %rails console
APP has new pepper: happyhappyjoyjoy
Loading development environment (Rails 4.2.5)

Conclusion: I monkey patched too late. I put the same code at the top of config/initializers/devise.rb. Too late. How about at the end of config/application.rb? Yes. that worked.

I'm not too thrilled by this, I think it ought to go elsewhere, but one step forward at a time.