diff --git a/app/controllers/jwks_controller.rb b/app/controllers/jwks_controller.rb index 2574f6e14d51a1d5c0aba5a53ccc1fbbb285db14..e4e00ecfb79053e36cb77ec005653c9fd4f5e77e 100644 --- a/app/controllers/jwks_controller.rb +++ b/app/controllers/jwks_controller.rb @@ -11,7 +11,7 @@ def keys def payload [ - Rails.application.secrets.openid_connect_signing_key, + Rails.application.credentials.openid_connect_signing_key, Gitlab::CurrentSettings.ci_jwt_signing_key ].compact.map do |key_data| OpenSSL::PKey::RSA.new(key_data) diff --git a/config/initializers/00_deprecations.rb b/config/initializers/00_deprecations.rb index eeed495a2972a05c9a2651c0ed1b027ebbb948ba..7fcf50e90b93145df8151a5ad3861dd95ecb312c 100644 --- a/config/initializers/00_deprecations.rb +++ b/config/initializers/00_deprecations.rb @@ -18,7 +18,6 @@ deprecators.silenced = silenced ignored_warnings = [ - /`Rails.application.secrets` is deprecated in favor of `Rails.application.credentials`/, /Your `secret_key_base` is configured in `Rails.application.secrets`, which is deprecated in favor of/, /Please pass the (coder|class) as a keyword argument/ ] diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb index bb13869c963dd182209de008880182c599bf7bb7..886b6916e5f3b309c44fcb212e3a9ae742e7f8d5 100644 --- a/config/initializers/01_secret_token.rb +++ b/config/initializers/01_secret_token.rb @@ -10,43 +10,45 @@ require 'securerandom' -# Transition material in .secret to the secret_key_base key in config/secrets.yml. -# Historically, ENV['SECRET_KEY_BASE'] takes precedence over .secret, so we maintain that -# behavior. -# -# It also used to be the case that the key material in ENV['SECRET_KEY_BASE'] or .secret -# was used to encrypt OTP (two-factor authentication) data so if present, we copy that key -# material into config/secrets.yml under otp_key_base. -# -# Finally, if we have successfully migrated all secrets to config/secrets.yml, delete the -# .secret file to avoid confusion. -# def create_tokens - secret_file = Rails.root.join('.secret') - file_secret_key = File.read(secret_file).chomp if File.exist?(secret_file) - env_secret_key = ENV['SECRET_KEY_BASE'] + # Inspired by https://github.com/rails/rails/blob/v7.0.8.4/railties/lib/rails/secrets.rb#L25-L36 + raw_secrets = begin + YAML.safe_load(File.read(Rails.root.join('config/secrets.yml'))) + rescue Errno::ENOENT, Psych::SyntaxError + {} + end + raw_secrets ||= {} - # Ensure environment variable always overrides secrets.yml. - Rails.application.secrets.secret_key_base = env_secret_key if env_secret_key.present? + secrets = {} + secrets.merge!(raw_secrets["shared"].deep_symbolize_keys) if raw_secrets["shared"] + secrets.merge!(raw_secrets[Rails.env].deep_symbolize_keys) if raw_secrets[Rails.env] + + # Copy secrets into credentials since Rails.application.secrets is populated from config/secrets.yml + # Later, once config/secrets.yml won't be read automatically, we'll need to do it manually, and set + secrets.each do |key, value| + Rails.application.credentials[key] = value + end + + # Historically, ENV['SECRET_KEY_BASE'] takes precedence over secrets.yml, so we maintain that + # behavior by ensuring the environment variable always overrides secrets.yml. + env_secret_key = ENV['SECRET_KEY_BASE'] + Rails.application.credentials.secret_key_base = env_secret_key if env_secret_key.present? defaults = { - secret_key_base: file_secret_key || generate_new_secure_token, - otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token, + secret_key_base: generate_new_secure_token, + otp_key_base: generate_new_secure_token, db_key_base: generate_new_secure_token, openid_connect_signing_key: generate_new_rsa_private_key } # encrypted_settings_key_base is optional for now - defaults[:encrypted_settings_key_base] = generate_new_secure_token if ENV['GITLAB_GENERATE_ENCRYPTED_SETTINGS_KEY_BASE'] + if ENV['GITLAB_GENERATE_ENCRYPTED_SETTINGS_KEY_BASE'] + defaults[:encrypted_settings_key_base] = + generate_new_secure_token + end missing_secrets = set_missing_keys(defaults) write_secrets_yml(missing_secrets) unless missing_secrets.empty? - - begin - File.delete(secret_file) if file_secret_key - rescue StandardError => e - warn "Error deleting useless .secret file: #{e}" - end end def generate_new_secure_token @@ -60,15 +62,15 @@ def generate_new_rsa_private_key def warn_missing_secret(secret) return if Rails.env.test? - warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml." + warn "Missing Rails.application.credentials.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml." end def set_missing_keys(defaults) defaults.stringify_keys.each_with_object({}) do |(key, default), missing| - next if Rails.application.secrets[key].present? + next if Rails.application.credentials.public_send(key).present? warn_missing_secret(key) - missing[key] = Rails.application.secrets[key] = default + missing[key] = Rails.application.credentials[key] = default end end @@ -79,26 +81,8 @@ def write_secrets_yml(missing_secrets) secrets ||= {} secrets[rails_env] ||= {} - secrets[rails_env].merge!(missing_secrets) do |key, old, new| - # Previously, it was possible this was set to the literal contents of an Erb - # expression that evaluated to an empty value. We don't want to support that - # specifically, just ensure we don't break things further. - # - if old.present? - warn <<EOM -Rails.application.secrets.#{key} was blank, but the literal value in config/secrets.yml was: - #{old} - -This probably isn't the expected value for this secret. To keep using a literal Erb string in config/secrets.yml, replace `<%` with `<%%`. -EOM - - exit 1 # rubocop:disable Rails/Exit - end - - new - end - - File.write(secrets_yml, YAML.dump(secrets), mode: 'w', perm: 0600) + secrets[rails_env].merge!(missing_secrets) + File.write(secrets_yml, YAML.dump(secrets), mode: 'w', perm: 0o600) end create_tokens diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb index 1520cbb4ac237a05ab0d139342478c53230c336e..67228539db9acd9280b4da880f0c51fa59f32eaa 100644 --- a/spec/initializers/secret_token_spec.rb +++ b/spec/initializers/secret_token_spec.rb @@ -11,20 +11,19 @@ let(:rsa_key) { /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m } before do - allow(Rails).to receive_message_chain(:application, :secrets).and_return(secrets) + allow(Rails).to receive_message_chain(:application, :credentials).and_return(secrets) allow(Rails).to receive_message_chain(:root, :join) { |string| string } allow(File).to receive(:write).and_call_original allow(File).to receive(:write).with(Rails.root.join('config/secrets.yml')) - allow(File).to receive(:delete).and_call_original - allow(File).to receive(:delete).with(Rails.root.join('.secret')) allow(self).to receive(:warn) allow(self).to receive(:exit) end describe 'ensure acknowledged secrets in any installations' do let(:acknowledged_secrets) do - %w[secret_key_base otp_key_base db_key_base openid_connect_signing_key encrypted_settings_key_base rotated_encrypted_settings_key_base] + %w[secret_key_base otp_key_base db_key_base openid_connect_signing_key encrypted_settings_key_base + rotated_encrypted_settings_key_base] end it 'does not allow to add a new secret without a proper handling' do @@ -50,7 +49,6 @@ context 'when none of the secrets exist' do before do stub_env('SECRET_KEY_BASE', nil) - allow(File).to receive(:exist?).with('.secret').and_return(false) allow(File).to receive(:exist?).with('config/secrets.yml').and_return(false) allow(self).to receive(:warn_missing_secret) end @@ -95,12 +93,6 @@ create_tokens end - - it 'does not write a .secret file' do - expect(File).not_to receive(:write).with('.secret') - - create_tokens - end end context 'when the other secrets all exist' do @@ -108,9 +100,6 @@ secrets.db_key_base = 'db_key_base' secrets.openid_connect_signing_key = 'openid_connect_signing_key' secrets.encrypted_settings_key_base = 'encrypted_settings_key_base' - - allow(File).to receive(:exist?).with('.secret').and_return(true) - stub_file_read('.secret', content: 'file_key') end context 'when secret_key_base exists in the environment and secrets.yml' do @@ -162,12 +151,6 @@ expect(secrets.openid_connect_signing_key).to eq('openid_connect_signing_key') expect(secrets.encrypted_settings_key_base).to eq('encrypted_settings_key_base') end - - it 'deletes the .secret file' do - expect(File).to receive(:delete).with('.secret') - - create_tokens - end end context 'when secret_key_base and otp_key_base do not exist' do @@ -177,21 +160,6 @@ allow(self).to receive(:warn_missing_secret) end - it 'uses the file secret' do - expect(File).to receive(:write) do |filename, contents, options| - new_secrets = YAML.safe_load(contents)[Rails.env] - - expect(new_secrets['secret_key_base']).to eq('file_key') - expect(new_secrets['otp_key_base']).to eq('file_key') - expect(new_secrets['db_key_base']).to eq('db_key_base') - expect(new_secrets['openid_connect_signing_key']).to eq('openid_connect_signing_key') - end - - create_tokens - - expect(secrets.otp_key_base).to eq('file_key') - end - it 'keeps the other secrets as they were' do create_tokens @@ -204,12 +172,6 @@ create_tokens end - - it 'deletes the .secret file' do - expect(File).to receive(:delete).with('.secret') - - create_tokens - end end context 'when rotated_encrypted_settings_key_base does not exist' do @@ -253,15 +215,6 @@ create_tokens end - it 'warns about the blank value existing in secrets.yml and exits' do - expect(self).to receive(:warn) do |warning| - expect(warning).to include('db_key_base') - expect(warning).to include('<%= an_erb_expression %>') - end - - create_tokens - end - it 'does not update secrets.yml' do expect(self).to receive(:exit).with(1).and_call_original expect(File).not_to receive(:write) diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 1f0f6d44a1d00f9c33b4e17f9301d52349cfdbc1..88a52b250f4b0df7eb9f7bc847c14f0d33a5e0e3 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -192,7 +192,8 @@ def request_user_info! end it 'does not include any unknown properties' do - expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy name nickname preferred_username email email_verified website profile picture groups_direct] + expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy name nickname preferred_username email + email_verified website profile picture groups_direct] end it 'does include groups' do