Rotating Ruby on Rails secret_key_base
Rails use secret_key_base to derive keys that are used to generate and verify encrypted cookies, signed cookie and also signed message. So it is important to keep your secret_key_base value secure. If your secret_key_base is somehow exposed, I urge you to change it immediately as your rails app is vulnerable to a security breach.
Rails provides a guide on how to rotate encrypted/signed cookies configuration. It gives an example of changing the digest used for signed cookies from SHA1 to SHA256. Based on this, the next sections will show you how to rotate the key used for encrypted cookies and how to deploy the changes gracefully.
Configuration
First, you need to change the secret_key_base value to a new one using rails secret command. Then put the old values in another placeholder, for example, an environment variable named OLD_SECRET_KEY_BASE.
Now add the following configuration rotation so that cookies encrypted with the old key can still be read, but then the response will have cookies generated using the new key.
Note that the above configuration is only for rotating encrypted cookies’ key in Rails 5.2 which uses AES GCM encryption.
Strategy
Rotating the key in a single app deployment can still cause an issue if the request with cookies using the new key, ends up in the old app server that cannot read it. Your users will be logged out or even get an invalid token response when trying to submit a form.
If you want to avoid this, you can do it by deploying the app several times with changes as follows:
However, there’s a caveat for this strategy, well at least for my case where I use cookies to store users’ session. So, when both old and new servers using the opposite secret_key_base and old_secret_key_base are up at the same time, at some point the app will raise aCookieOverflow error.
It turns out, that when the app receives a cookie encrypted using the opposite key, it somehow adds a session item named :value with a value of an encrypted "null" string. Then, each time the cookies get read by the other app using the opposite secret_key_base, the “value” session keeps getting encrypted, which gets bigger in size, thus causing the error. While I haven’t found what causes this, fortunately, there’s a simple workaround. You just need to add a before_action filter to your base controller class that removes the problematic session.
before_action { session.delete :value }
Voilà! Hopefully this post can help you to secure your Rails app. 🍻