Commit 3159beae authored by DJ Mountney's avatar DJ Mountney

Bring in changes from the ldap impelmentation MR

This adds:

- Path for storing encrypted settings in config
- optional base token generation
- Additional error handling for missing config and keys
parent 5fe1d092
......@@ -1042,6 +1042,10 @@ production: &base
shared:
# path: /mnt/gitlab # Default: shared
# Encrypted Settings configuration
encrypted_settings:
# path: /mnt/gitlab/encrypted_settings # Default: shared/encrypted_settings
# Gitaly settings
gitaly:
# Path to the directory containing Gitaly client executables.
......
......@@ -34,6 +34,9 @@ def create_tokens
openid_connect_signing_key: generate_new_rsa_private_key
}
# enc_settings_key_base is optional for now
defaults[:enc_settings_key_base] = generate_new_secure_token if ENV['GITLAB_GENERATE_ENC_SETTINGS_KEY_BASE']
missing_secrets = set_missing_keys(defaults)
write_secrets_yml(missing_secrets) unless missing_secrets.empty?
......
......@@ -3,6 +3,12 @@ require_relative '../object_store_settings'
require_relative '../smime_signature_settings'
# Default settings
Settings['shared'] ||= Settingslogic.new({})
Settings.shared['path'] = Settings.absolute(Settings.shared['path'] || "shared")
Settings['encrypted_settings'] ||= Settingslogic.new({})
Settings.encrypted_settings['path'] = Settings.absolute(Settings.encrypted_settings['path'] || File.join(Settings.shared['path'], "encrypted_settings"))
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
Settings.ldap['prevent_ldap_sign_in'] = false if Settings.ldap['prevent_ldap_sign_in'].blank?
......@@ -140,9 +146,6 @@ if Gitlab.ee? && Rails.env.test? && !saml_provider_enabled
Settings.omniauth.providers << Settingslogic.new({ 'name' => 'group_saml' })
end
Settings['shared'] ||= Settingslogic.new({})
Settings.shared['path'] = Settings.absolute(Settings.shared['path'] || "shared")
Settings['issues_tracker'] ||= {}
#
......
......@@ -153,8 +153,6 @@ class Settings < Settingslogic
end
def encrypted(path)
return Gitlab::EncryptedConfiguration.new unless Gitlab::Application.secrets.enc_settings_key_base
Gitlab::EncryptedConfiguration.new(
content_path: Settings.absolute(path),
base_key: Gitlab::Application.secrets.enc_settings_key_base,
......
......@@ -9,6 +9,18 @@ module Gitlab
CIPHER = "aes-256-gcm"
SALT = "GitLabEncryptedConfigSalt"
class MissingKeyError < RuntimeError
def initialize(msg = "Missing encryption key to encrypt/decrypt file with.")
super
end
end
class InvalidConfigError < RuntimeError
def initialize(msg = "Content was not a valid yml config file")
super
end
end
def self.generate_key(base_key)
# Because the salt is static, we want uniqueness to be coming from the base_key
# Error if the base_key is empty or suspiciously short
......@@ -23,8 +35,12 @@ module Gitlab
@previous_keys = previous_keys
end
def active?
content_path&.exist?
end
def read
if !key.nil? && content_path&.exist?
if active?
decrypt content_path.binread
else
""
......@@ -45,7 +61,13 @@ module Gitlab
end
def config
@config ||= deserialize(read).deep_symbolize_keys
return @config if @config
contents = deserialize(read)
raise InvalidConfigError.new unless contents.is_a?(Hash)
@config = contents.deep_symbolize_keys
end
def change(&block)
......@@ -61,10 +83,12 @@ module Gitlab
end
def encrypt(contents)
handle_missing_key!
encryptor.encrypt_and_sign contents
end
def decrypt(contents)
handle_missing_key!
encryptor.decrypt_and_verify contents
end
......@@ -89,5 +113,9 @@ module Gitlab
def deserialize(contents)
YAML.safe_load(contents, permitted_classes: [Symbol]).presence || {}
end
def handle_missing_key!
raise MissingKeyError.new if @key.nil?
end
end
end
......@@ -151,8 +151,7 @@ RSpec.describe Settings do
it 'returns empty encrypted config when a key has not been set' do
allow(Gitlab::Application.secrets).to receive(:enc_settings_key_base).and_return(nil)
expect(Gitlab::EncryptedConfiguration).to receive(:new).with(no_args)
Settings.encrypted('tmp/tests/test.enc')
expect(Settings.encrypted('tmp/tests/test.enc').read).to be_empty
end
end
end
......@@ -24,7 +24,7 @@ RSpec.describe 'create_tokens' do
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]
%w[secret_key_base otp_key_base db_key_base openid_connect_signing_key enc_settings_key_base rotated_enc_settings_key_base]
end
it 'does not allow to add a new secret without a proper handling' do
......@@ -90,6 +90,7 @@ RSpec.describe 'create_tokens' do
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
expect(new_secrets['openid_connect_signing_key']).to eq(secrets.openid_connect_signing_key)
expect(new_secrets['enc_settings_key_base']).to eq(secrets.enc_settings_key_base)
end
create_tokens
......@@ -106,6 +107,7 @@ RSpec.describe 'create_tokens' do
before do
secrets.db_key_base = 'db_key_base'
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
secrets.enc_settings_key_base = 'enc_settings_key_base'
allow(File).to receive(:exist?).with('.secret').and_return(true)
stub_file_read('.secret', content: 'file_key')
......@@ -158,6 +160,7 @@ RSpec.describe 'create_tokens' do
expect(secrets.otp_key_base).to eq('otp_key_base')
expect(secrets.db_key_base).to eq('db_key_base')
expect(secrets.openid_connect_signing_key).to eq('openid_connect_signing_key')
expect(secrets.enc_settings_key_base).to eq('enc_settings_key_base')
end
it 'deletes the .secret file' do
......@@ -208,12 +211,34 @@ RSpec.describe 'create_tokens' do
create_tokens
end
end
context 'when rotated_enc_settings_key_base does not exist' do
before do
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
secrets.enc_settings_key_base = 'enc_settings_key_base'
end
it 'does not warns about the missing secrets' do
expect(self).not_to receive(:warn_missing_secret).with('rotated_enc_settings_key_base')
create_tokens
end
it 'does not update secrets.yml' do
expect(File).not_to receive(:write)
create_tokens
end
end
end
context 'when db_key_base is blank but exists in secrets.yml' do
before do
secrets.otp_key_base = 'otp_key_base'
secrets.secret_key_base = 'secret_key_base'
secrets.enc_settings_key_base = 'enc_settings_key_base'
yaml_secrets = secrets.to_h.stringify_keys.merge('db_key_base' => '<%= an_erb_expression %>')
allow(File).to receive(:exist?).with('.secret').and_return(false)
......
......@@ -5,6 +5,12 @@ require "spec_helper"
RSpec.describe Gitlab::EncryptedConfiguration do
subject(:configuration) { described_class.new }
let!(:config_tmp_dir) { Dir.mktmpdir('config-') }
after do
FileUtils.rm_f(config_tmp_dir)
end
describe '#initialize' do
it 'accepts all args as optional fields' do
expect { configuration }.not_to raise_exception
......@@ -30,15 +36,24 @@ RSpec.describe Gitlab::EncryptedConfiguration do
end
end
context 'when provided a config file but no key' do
let(:config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
it 'throws an error when writing without a key' do
expect { described_class.new(content_path: config_path).write('test') }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
end
it 'throws an error when reading without a key' do
config = described_class.new(content_path: config_path)
File.write(config_path, 'test')
expect { config.read }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
end
end
context 'when provided key and config file' do
let!(:config_tmp_dir) { Dir.mktmpdir('config-') }
let(:credentials_config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
let(:credentials_key) { SecureRandom.hex(64) }
after do
FileUtils.rm_f(config_tmp_dir)
end
describe '#write' do
it 'encrypts the file using the provided key' do
encryptor = ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(credentials_key), cipher: 'aes-256-gcm')
......@@ -63,6 +78,13 @@ RSpec.describe Gitlab::EncryptedConfiguration do
config.write({ foo: { bar: true } }.to_yaml)
expect(config.foo[:bar]).to be true
end
it 'throws a custom error when deferencing an invalid key map config' do
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
config.write("stringcontent")
expect { config[:foo] }.to raise_error Gitlab::EncryptedConfiguration::InvalidConfigError
end
end
describe '#change' do
......@@ -80,16 +102,11 @@ RSpec.describe Gitlab::EncryptedConfiguration do
end
context 'when provided previous_keys for rotation' do
let!(:config_tmp_dir) { Dir.mktmpdir('config-') }
let(:credential_key_original) { SecureRandom.hex(64) }
let(:credential_key_latest) { SecureRandom.hex(64) }
let(:config_path_original) { File.join(config_tmp_dir, 'credentials-orig.yml.enc') }
let(:config_path_latest) { File.join(config_tmp_dir, 'credentials-latest.yml.enc') }
after do
FileUtils.rm_f(config_tmp_dir)
end
def encryptor(key)
ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(key), cipher: 'aes-256-gcm')
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment