The session cookie, amongst others, is encrypted based on the value of the secret_key_base
secret. If this
secret changes then all existing session cookies become invalid and users would be forced to login again or
encounter errors. To rotate the secret_key_base
secret, for security reasons, while mitigating the impact
on users you can follow this guide.
Encrypted cookie rotation is performed by the config/initializers/cookie_rotation.rb
initializer
but it will only take action if there is an old_secret_key_base
secret in config/secrets.yml
.
Thr cookie rotator used by this guide has been removed from config/initializers/cookie_rotation.rb
and copied below for reference.
To rotate the secret_key_base
without inconveniencing users you must:
-
add an old_secret_key_base to
config/secrets.yml
and assign its value to that of the current secret you wish to rotate. -
generate a new secret at a terminal rooted in the (or any rails) repo using
rails secret
and assign the existingsecret_key_base
's value to be that of the new secret example:# config/secrets.yml development: old_secret_key_base: my-current-local-secret-key-base-secret secret_key_base: my-new-local-secret-key-base-secret .... production: old_secret_key_base: <%= ENV["OLD_SECRET_KEY_BASE"] %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
-
For production, as in the above example, you will need to add an OLD_SECRET_KEY_BASE env variable to each namespace/host, add the current secret to that and modify the existing SECRET_KEY_BASE to hold the new secret.
Note: secret env vars are kept in Kubernetes Secrets on the cluster They can be viewed by running
kubectl -n <namespace> get secrets cccd-secrets -o json
. -
Deploy the application, manually updating the Secrets in the cluster first.
-
Leave the rotation in place for x, where x is however long (days?!) we think it will take the majority of users to have visited the site and have their cookies rotated.
-
After x amount of time you should then delete the
old_secret_key_base
secret entry fromconfig/secrets.yml
and any matchingOLD_SECRET_KEY_BASE
env variable. You should do this at time of low traffic to minimise inconvenience for users.
The process above will add a fallback encryptor that uses
the old_secret_key_base
secret to verify session cookies
are valid if the "primary" encryptor (that uses secret_key_base
)
determines it to be invalid.
Nonetheless, a new session cookie will be generated using
the new secret_key_base
secret.
Anyone visiting the site using an old session cookie will not be considered invalid AND will transparently generate themselves a new session cookie using the new secret. Once the old key is deleted those who have visited the site in the interim will already have a valid session cookie in any event and those who do not will be logged out.
A futher mitigation of the impact would therefore be to do the deploy that deletes the old key at a time of low traffic to avoid inconveniencing as few people as possible. The inconvenience for these individuals would be, at best, a redirect to login, but could possibly cause form input loss if the input is not yet saved, and in some cases a 500 may occur. This last might typically happen if they are on a form page with validation errors displaying and they attempt to resubmit with more validation errors.
The cookie rotator that was used for updating the secret key base is copied here for future reference.
# config/initializers/cookie_rotator.rb
#
# It will only take action if there is an
# `old_secret_key_base` secret in `config/secrets.yml`.
#
# NOTE: any changes to rails `action_dispatch` encryption config will
# impact this functionality, rendering it in need of revisiting - manual
# testing.
#
return unless Settings.old_secret_key_base.present?
Rails.application.configure do
# Not technically necessary, as this is the current default,
# but it is to line up with the config below.
config.action_dispatch.use_authenticated_cookie_encryption = true
# Originally from:
# https://www.gitmemory.com/issue/rails/rails/39964/668147345
# This page has disappeared but it may have been a reference to:
# https://github.com/rails/rails/issues/39964
# The official documentation for rotating the cookies, with respect to
# upgrading to Rails 7:
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#key-generator-digest-class-changing-to-use-sha256
# --------------------------------------------------------------------------------
config.action_dispatch.cookies_rotations.tap do |cookies|
salt = config.action_dispatch.authenticated_encrypted_cookie_salt
signed_salt = config.action_dispatch.encrypted_signed_cookie_salt
cipher = config.action_dispatch.encrypted_cookie_cipher || 'aes-256-gcm'
old_secret_key_base = Settings.old_secret_key_base
generator = ActiveSupport::KeyGenerator.new(old_secret_key_base, iterations: 1000)
len = ActiveSupport::MessageEncryptor.key_len(cipher)
secret = generator.generate_key(salt, len)
sign_secret = generator.generate_key(signed_salt)
cookies.rotate :encrypted, secret, sign_secret
end
end