Add support for using ActiveRecord::Encryption
What does this MR do and why?
The original MR was reverted by !175143 (merged). I have marked this MR as dependent of gitlab-com/gl-infra/k8s-workloads/gitlab-com!4014 (merged) to prevent an incident similar to what the original MR caused.
I took inspiration from !127186 (closed).
This reads & write (if missing) ActiveRecord::Encryption
keys from/in config/secrets.yml
.
- Support for generating the keys in Omnibus is added by omnibus-gitlab!8026 (merged)
- Support for generating the keys in Charts is added by gitlab-org/charts/gitlab!3988 (merged)
To start using ActiveRecord::Encryption
, all you need is a migration to add a JSONB column, e.g.
# frozen_string_literal: true
class AddOpenidConnectSigningKeyToApplicationSettings < Gitlab::Database::Migration[2.2]
milestone '17.5'
def change
add_column :application_settings, :openid_connect_signing_key, :jsonb
add_column :application_settings, :encrypted_identity_verification_secrets, :jsonb
end
end
and a new encrypts :field
line, e.g.
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 55f01e328451..4a3e0b303fba 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -57,6 +57,8 @@ class ApplicationSetting < ApplicationRecord
add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :required # rubocop:todo -- https://gitlab.com/gitlab-org/gitlab/-/issues/439292
add_authentication_token_field :error_tracking_access_token, encrypted: :required # rubocop:todo -- https://gitlab.com/gitlab-org/gitlab/-/issues/439292
+ encrypts :openid_connect_signing_key
+ jsonb_accessor :encrypted_identity_verification_secrets,
+ some_setting: [:string, { default: "some_setting_default" }]
+ encrypts :encrypted_identity_verification_secrets
+
belongs_to :push_rule
belongs_to :web_ide_oauth_application, class_name: 'Doorkeeper::Application'
Encrypting hash attributes (defined with jsonb_accessor
above) is also supported:
> application_settings = ApplicationSetting.last
> application_settings.some_setting = "foo"
> application_settings.save!
ApplicationSetting Update (0.7ms) UPDATE "application_settings" SET "encrypted_identity_verification_secrets" = '{"p":"PKkplntKWXf43ckBARi0UR+jfDXlckZ5wJxEuExI","h":{"iv":"TWwldXXA5VcP/AmA","at":"OTJEscJNUiWt+HbB/Bbeqw=="}}', "updated_at" = '2024-10-08 07:31:41.788513' WHERE "application_settings"."id" = 1
# Reload the record, just in case
> application_settings = ApplicationSetting.last
# The attribute is a hash
> application_settings.encrypted_identity_verification_secrets
=> {"some_setting"=>"foo"}
# Individual accessors also work
> application_settings.some_setting
=> "foo"
# Nested attribute can also be set via the hash, with a small caveat...
> application_settings.encrypted_identity_verification_secrets["some_setting"] = 'bar'
=> "bar"
# its value isn't reflected in the individual accessor...
> application_settings.some_setting
=> "baz" # we'd expect "bar" here!
# but it's value is correct in the hash attribute
> application_settings.encrypted_identity_verification_secrets
=> {"some_setting"=>"bar"}
> application_settings.save!
# Reload the record, just in case
> application_settings = ApplicationSetting.last
# The new nested setting value is saved...
> application_settings.encrypted_identity_verification_secrets
=> {"some_setting"=>"bar"}
# And reflected in the individual accessor after save/reload
> application_settings.some_setting
=> "bar"
By using ActiveRecord::Encryption
, we'll be able to know what key was used to encrypt a specific field, e.g.
> legacy_key_id = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key.first).encryption_key.id
=> "08ae"
> ApplicationSetting.where("openid_connect_signing_key->'h'->'i' ? :value", value: ::Base64.strict_encode64(legacy_key_id)).count
=> 1
so that later we can rotate keys easily (this is already supported by ActiveRecord::Encryption
but we'll need a process to automatize the re-encryption of data).
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
Before | After |
---|---|
How to set up and validate locally
Numbered steps to set up and validate the change are strongly suggested.
Related to #490590 (closed).