diff --git a/doc/maintenance/index.md b/doc/maintenance/index.md index da34c9ee0d5f75b319bf81b7590aafecea237222_ZG9jL21haW50ZW5hbmNlL2luZGV4Lm1k..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZG9jL21haW50ZW5hbmNlL2luZGV4Lm1k 100644 --- a/doc/maintenance/index.md +++ b/doc/maintenance/index.md @@ -181,7 +181,7 @@ 1. Recommended. Extract your current secrets for `gitlab_rails`. Save the output because you need this later: ```shell - sudo grep "secret_key_base\|db_key_base\|otp_key_base\|encrypted_settings_key_base\|openid_connect_signing_key" /etc/gitlab/gitlab-secrets.json + sudo grep "secret_key_base\|db_key_base\|otp_key_base\|encrypted_settings_key_base\|openid_connect_signing_key\|active_record_encryption_primary_key\|active_record_encryption_deterministic_key\|active_record_encryption_key_derivation_salt" /etc/gitlab/gitlab-secrets.json ``` 1. Move your current secrets file to a different location: diff --git a/doc/update/convert_to_omnibus.md b/doc/update/convert_to_omnibus.md index da34c9ee0d5f75b319bf81b7590aafecea237222_ZG9jL3VwZGF0ZS9jb252ZXJ0X3RvX29tbmlidXMubWQ=..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZG9jL3VwZGF0ZS9jb252ZXJ0X3RvX29tbmlidXMubWQ= 100644 --- a/doc/update/convert_to_omnibus.md +++ b/doc/update/convert_to_omnibus.md @@ -47,7 +47,8 @@ `gitlab.yml`, `puma.rb`, and `smtp_settings.rb`. For all available options, see the [`gitlab.rb` template](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template). 1. Copy the secrets from the old self-compiled installation to the new Linux package installation: - 1. Restore secrets related to Rails. Copy the values of `db_key_base`, `secret_key_base` and `otp_key_base` from + 1. Restore secrets related to Rails. Copy the values of `db_key_base`, `secret_key_base`, + `otp_key_base`, `encrypted_settings_key_base`, `openid_connect_signing_key`, and `active_record_encryption` from `/home/git/gitlab/config/secrets.yml` (self-compiled installation) to the equivalent ones in `/etc/gitlab/gitlab-secrets.json` (Linux package installation). 1. Copy the contents of `/home/git/gitlab-shell/.gitlab_shell_secret` (self-compiled installation) to `secret_token` @@ -65,6 +66,11 @@ "secret_key_base": "...", "db_key_base": "...", "otp_key_base": "...", + "encrypted_settings_key_base": "...", + "openid_connect_signing_key": "...", + "active_record_encryption_primary_key": [ "..."], + "active_record_encryption_deterministic_key": ["..."], + "active_record_encryption_key_derivation_salt": "...", } ... } diff --git a/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb b/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb index da34c9ee0d5f75b319bf81b7590aafecea237222_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2dpdGxhYl9yYWlscy5yYg==..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2dpdGxhYl9yYWlscy5yYg== 100644 --- a/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb +++ b/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb @@ -78,6 +78,19 @@ Gitlab['gitlab_rails']['encrypted_settings_key_base'] ||= SecretsHelper.generate_hex(64) Gitlab['gitlab_rails']['openid_connect_signing_key'] ||= SecretsHelper.generate_rsa(4096).to_pem + # 1. We set the following two keys as an array to support keys rotation. + # The last key in the array is always used to encrypt data: + # https://github.com/rails/rails/blob/v7.0.8.4/activerecord/lib/active_record/encryption/key_provider.rb#L21 + # while all the keys are used (in the order they're defined) to decrypt data: + # https://github.com/rails/rails/blob/v7.0.8.4/activerecord/lib/active_record/encryption/cipher.rb#L26. + # This allows to rotate keys by adding a new key as the last key, and start a re-encryption process that + # runs in the background: https://gitlab.com/gitlab-org/gitlab/-/issues/494976 + # 2. We use the same method and length as Rails' defaults: + # https://github.com/rails/rails/blob/v7.0.8.4/activerecord/lib/active_record/railties/databases.rake#L537-L540 + Gitlab['gitlab_rails']['active_record_encryption_primary_key'] ||= [SecretsHelper.generate_alphanumeric(32)] + Gitlab['gitlab_rails']['active_record_encryption_deterministic_key'] ||= [SecretsHelper.generate_alphanumeric(32)] + Gitlab['gitlab_rails']['active_record_encryption_key_derivation_salt'] ||= SecretsHelper.generate_alphanumeric(32) + return unless Gitlab['gitlab_rails']['initial_root_password'].nil? Gitlab['gitlab_rails']['initial_root_password'] = SecretsHelper.generate_base64(32) diff --git a/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb b/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb index da34c9ee0d5f75b319bf81b7590aafecea237222_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMucmI=..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItcmFpbHMucmI= 100644 --- a/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb +++ b/files/gitlab-cookbooks/gitlab/recipes/gitlab-rails.rb @@ -217,7 +217,10 @@ 'secret_key_base' => node['gitlab']['gitlab_rails']['secret_key_base'], 'otp_key_base' => node['gitlab']['gitlab_rails']['otp_key_base'], 'encrypted_settings_key_base' => node['gitlab']['gitlab_rails']['encrypted_settings_key_base'], - 'openid_connect_signing_key' => node['gitlab']['gitlab_rails']['openid_connect_signing_key'] + 'openid_connect_signing_key' => node['gitlab']['gitlab_rails']['openid_connect_signing_key'], + 'active_record_encryption_primary_key' => node['gitlab']['gitlab_rails']['active_record_encryption_primary_key'], + 'active_record_encryption_deterministic_key' => node['gitlab']['gitlab_rails']['active_record_encryption_deterministic_key'], + 'active_record_encryption_key_derivation_salt' => node['gitlab']['gitlab_rails']['active_record_encryption_key_derivation_salt'] } }) dependent_services.each { |svc| notifies :restart, svc } end diff --git a/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb b/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb index da34c9ee0d5f75b319bf81b7590aafecea237222_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL3NlY3JldHNfaGVscGVyLnJi..6acfae1d0666073f35bc22a32acef95ae4fb2514_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9oZWxwZXJzL3NlY3JldHNfaGVscGVyLnJi 100644 --- a/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb +++ b/files/gitlab-cookbooks/package/libraries/helpers/secrets_helper.rb @@ -17,6 +17,10 @@ SecureRandom.urlsafe_base64(bytes) end + def self.generate_alphanumeric(chars) + SecureRandom.alphanumeric(chars) + end + def self.generate_rsa(bits) OpenSSL::PKey::RSA.new(bits) end @@ -81,7 +85,10 @@ 'db_key_base' => Gitlab['gitlab_rails']['db_key_base'], 'otp_key_base' => Gitlab['gitlab_rails']['otp_key_base'], 'encrypted_settings_key_base' => Gitlab['gitlab_rails']['encrypted_settings_key_base'], - 'openid_connect_signing_key' => Gitlab['gitlab_rails']['openid_connect_signing_key'] + 'openid_connect_signing_key' => Gitlab['gitlab_rails']['openid_connect_signing_key'], + 'active_record_encryption_primary_key' => Gitlab['gitlab_rails']['active_record_encryption_primary_key'], + 'active_record_encryption_deterministic_key' => Gitlab['gitlab_rails']['active_record_encryption_deterministic_key'], + 'active_record_encryption_key_derivation_salt' => Gitlab['gitlab_rails']['active_record_encryption_key_derivation_salt'] }, 'gitlab_pages' => { 'gitlab_secret' => Gitlab['gitlab_pages']['gitlab_secret'], diff --git a/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb b/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb index da34c9ee0d5f75b319bf81b7590aafecea237222_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9nZW5lcmF0ZV9zZWNyZXRzX3NwZWMucmI=..6acfae1d0666073f35bc22a32acef95ae4fb2514_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9nZW5lcmF0ZV9zZWNyZXRzX3NwZWMucmI= 100644 --- a/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb +++ b/spec/chef/cookbooks/gitlab/recipes/generate_secrets_spec.rb @@ -15,5 +15,6 @@ optional_path = '/etc/mygitlab/mysecrets.json'.freeze hex_key = /\h{128}/.freeze rsa_key = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze + alphanumeric_key_regex = /\A[A-Za-z0-9]{32}\Z/m.freeze default_secrets_error_regexp = %r{You have enabled writing to the default secrets file location with package\['generate_secrets_json_file'].*} @@ -18,19 +19,5 @@ default_secrets_error_regexp = %r{You have enabled writing to the default secrets file location with package\['generate_secrets_json_file'].*} - def stub_gitlab_secrets_json(secrets) - allow(File).to receive(:read).with(SecretsHelper::SECRETS_FILE).and_return(JSON.generate(secrets)) - end - - def stub_check_secrets - rails_keys = new_secrets['gitlab_rails'] - hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base') - rsa_keys = rails_keys.values_at('openid_connect_signing_key') - - expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a) - expect(hex_keys).to all(match(hex_key)) - expect(rsa_keys).to all(match(rsa_key)) - end - before do allow(File).to receive(:directory?).and_call_original allow(File).to receive(:exist?).and_call_original @@ -80,7 +67,12 @@ it 'writes new secrets to the file, with different values for each' do rails_keys = new_secrets['gitlab_rails'] hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base') + alphanumeric_keys = rails_keys.values_at( + 'active_record_encryption_primary_key', + 'active_record_encryption_deterministic_key', + 'active_record_encryption_key_derivation_salt' + ).flatten rsa_keys = rails_keys.values_at('openid_connect_signing_key') expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a) expect(hex_keys).to all(match(hex_key)) @@ -83,7 +75,8 @@ rsa_keys = rails_keys.values_at('openid_connect_signing_key') expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a) expect(hex_keys).to all(match(hex_key)) + expect(alphanumeric_keys.flatten).to all(match(alphanumeric_key_regex)) expect(rsa_keys).to all(match(rsa_key)) end end diff --git a/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb b/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb index da34c9ee0d5f75b319bf81b7590aafecea237222_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZWNyZXRzX2hlbHBlcl9zcGVjLnJi..6acfae1d0666073f35bc22a32acef95ae4fb2514_c3BlYy9jaGVmL2Nvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZWNyZXRzX2hlbHBlcl9zcGVjLnJi 100644 --- a/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb +++ b/spec/chef/cookbooks/package/libraries/secrets_helper_spec.rb @@ -5,6 +5,7 @@ let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::default') } HEX_KEY = /\h{128}/.freeze + ALPHANUMERIC_KEY = /\A[A-Za-z0-9]{32}\Z/m.freeze RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze def stub_gitlab_secrets_json(secrets) @@ -50,7 +51,12 @@ it 'writes new secrets to the file, with different values for each' do rails_keys = new_secrets['gitlab_rails'] hex_keys = rails_keys.values_at('db_key_base', 'otp_key_base', 'secret_key_base', 'encrypted_settings_key_base') + alphanumeric_keys = rails_keys.values_at( + 'active_record_encryption_primary_key', + 'active_record_encryption_deterministic_key', + 'active_record_encryption_key_derivation_salt' + ).flatten rsa_keys = rails_keys.values_at('openid_connect_signing_key') expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a) expect(hex_keys).to all(match(HEX_KEY)) @@ -53,7 +59,8 @@ rsa_keys = rails_keys.values_at('openid_connect_signing_key') expect(rails_keys.to_a.uniq).to eq(rails_keys.to_a) expect(hex_keys).to all(match(HEX_KEY)) + expect(alphanumeric_keys.flatten).to all(match(ALPHANUMERIC_KEY)) expect(rsa_keys).to all(match(RSA_KEY)) end @@ -224,7 +231,10 @@ gitlab_rails: { secret_token: 'json_rails_secret_token', jws_private_key: 'json_rails_jws_private_key', - encrypted_settings_key_base: 'encrypted_settings_key_base' + encrypted_settings_key_base: 'encrypted_settings_key_base', + active_record_encryption_primary_key: ['primary_key'], + active_record_encryption_deterministic_key: ['deterministic_key'], + active_record_encryption_key_derivation_salt: 'key_derivation_salt' } ) @@ -259,7 +269,10 @@ 'secret_key_base' => 'rb_ci_db_key_base', 'otp_key_base' => 'json_rails_secret_token', 'encrypted_settings_key_base' => 'encrypted_settings_key_base', - 'openid_connect_signing_key' => 'json_rails_jws_private_key' + 'openid_connect_signing_key' => 'json_rails_jws_private_key', + 'active_record_encryption_primary_key' => ['primary_key'], + 'active_record_encryption_deterministic_key' => ['deterministic_key'], + 'active_record_encryption_key_derivation_salt' => 'key_derivation_salt' } } )