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 ...@@ -1042,6 +1042,10 @@ production: &base
shared: shared:
# path: /mnt/gitlab # Default: shared # path: /mnt/gitlab # Default: shared
# Encrypted Settings configuration
encrypted_settings:
# path: /mnt/gitlab/encrypted_settings # Default: shared/encrypted_settings
# Gitaly settings # Gitaly settings
gitaly: gitaly:
# Path to the directory containing Gitaly client executables. # Path to the directory containing Gitaly client executables.
......
...@@ -34,6 +34,9 @@ def create_tokens ...@@ -34,6 +34,9 @@ def create_tokens
openid_connect_signing_key: generate_new_rsa_private_key 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) missing_secrets = set_missing_keys(defaults)
write_secrets_yml(missing_secrets) unless missing_secrets.empty? write_secrets_yml(missing_secrets) unless missing_secrets.empty?
......
...@@ -3,6 +3,12 @@ require_relative '../object_store_settings' ...@@ -3,6 +3,12 @@ require_relative '../object_store_settings'
require_relative '../smime_signature_settings' require_relative '../smime_signature_settings'
# Default 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'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil? Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
Settings.ldap['prevent_ldap_sign_in'] = false if Settings.ldap['prevent_ldap_sign_in'].blank? 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 ...@@ -140,9 +146,6 @@ if Gitlab.ee? && Rails.env.test? && !saml_provider_enabled
Settings.omniauth.providers << Settingslogic.new({ 'name' => 'group_saml' }) Settings.omniauth.providers << Settingslogic.new({ 'name' => 'group_saml' })
end end
Settings['shared'] ||= Settingslogic.new({})
Settings.shared['path'] = Settings.absolute(Settings.shared['path'] || "shared")
Settings['issues_tracker'] ||= {} Settings['issues_tracker'] ||= {}
# #
......
...@@ -153,8 +153,6 @@ class Settings < Settingslogic ...@@ -153,8 +153,6 @@ class Settings < Settingslogic
end end
def encrypted(path) def encrypted(path)
return Gitlab::EncryptedConfiguration.new unless Gitlab::Application.secrets.enc_settings_key_base
Gitlab::EncryptedConfiguration.new( Gitlab::EncryptedConfiguration.new(
content_path: Settings.absolute(path), content_path: Settings.absolute(path),
base_key: Gitlab::Application.secrets.enc_settings_key_base, base_key: Gitlab::Application.secrets.enc_settings_key_base,
......
...@@ -9,6 +9,18 @@ module Gitlab ...@@ -9,6 +9,18 @@ module Gitlab
CIPHER = "aes-256-gcm" CIPHER = "aes-256-gcm"
SALT = "GitLabEncryptedConfigSalt" 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) def self.generate_key(base_key)
# Because the salt is static, we want uniqueness to be coming from the 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 # Error if the base_key is empty or suspiciously short
...@@ -23,8 +35,12 @@ module Gitlab ...@@ -23,8 +35,12 @@ module Gitlab
@previous_keys = previous_keys @previous_keys = previous_keys
end end
def active?
content_path&.exist?
end
def read def read
if !key.nil? && content_path&.exist? if active?
decrypt content_path.binread decrypt content_path.binread
else else
"" ""
...@@ -45,7 +61,13 @@ module Gitlab ...@@ -45,7 +61,13 @@ module Gitlab
end end
def config 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 end
def change(&block) def change(&block)
...@@ -61,10 +83,12 @@ module Gitlab ...@@ -61,10 +83,12 @@ module Gitlab
end end
def encrypt(contents) def encrypt(contents)
handle_missing_key!
encryptor.encrypt_and_sign contents encryptor.encrypt_and_sign contents
end end
def decrypt(contents) def decrypt(contents)
handle_missing_key!
encryptor.decrypt_and_verify contents encryptor.decrypt_and_verify contents
end end
...@@ -89,5 +113,9 @@ module Gitlab ...@@ -89,5 +113,9 @@ module Gitlab
def deserialize(contents) def deserialize(contents)
YAML.safe_load(contents, permitted_classes: [Symbol]).presence || {} YAML.safe_load(contents, permitted_classes: [Symbol]).presence || {}
end end
def handle_missing_key!
raise MissingKeyError.new if @key.nil?
end
end end
end end
...@@ -151,8 +151,7 @@ RSpec.describe Settings do ...@@ -151,8 +151,7 @@ RSpec.describe Settings do
it 'returns empty encrypted config when a key has not been set' 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) allow(Gitlab::Application.secrets).to receive(:enc_settings_key_base).and_return(nil)
expect(Gitlab::EncryptedConfiguration).to receive(:new).with(no_args) expect(Settings.encrypted('tmp/tests/test.enc').read).to be_empty
Settings.encrypted('tmp/tests/test.enc')
end end
end end
end end
...@@ -24,7 +24,7 @@ RSpec.describe 'create_tokens' do ...@@ -24,7 +24,7 @@ RSpec.describe 'create_tokens' do
describe 'ensure acknowledged secrets in any installations' do describe 'ensure acknowledged secrets in any installations' do
let(:acknowledged_secrets) 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 end
it 'does not allow to add a new secret without a proper handling' do it 'does not allow to add a new secret without a proper handling' do
...@@ -90,6 +90,7 @@ RSpec.describe 'create_tokens' 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['otp_key_base']).to eq(secrets.otp_key_base)
expect(new_secrets['db_key_base']).to eq(secrets.db_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['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 end
create_tokens create_tokens
...@@ -106,6 +107,7 @@ RSpec.describe 'create_tokens' do ...@@ -106,6 +107,7 @@ RSpec.describe 'create_tokens' do
before do before do
secrets.db_key_base = 'db_key_base' secrets.db_key_base = 'db_key_base'
secrets.openid_connect_signing_key = 'openid_connect_signing_key' 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) allow(File).to receive(:exist?).with('.secret').and_return(true)
stub_file_read('.secret', content: 'file_key') stub_file_read('.secret', content: 'file_key')
...@@ -158,6 +160,7 @@ RSpec.describe 'create_tokens' do ...@@ -158,6 +160,7 @@ RSpec.describe 'create_tokens' do
expect(secrets.otp_key_base).to eq('otp_key_base') expect(secrets.otp_key_base).to eq('otp_key_base')
expect(secrets.db_key_base).to eq('db_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.openid_connect_signing_key).to eq('openid_connect_signing_key')
expect(secrets.enc_settings_key_base).to eq('enc_settings_key_base')
end end
it 'deletes the .secret file' do it 'deletes the .secret file' do
...@@ -208,12 +211,34 @@ RSpec.describe 'create_tokens' do ...@@ -208,12 +211,34 @@ RSpec.describe 'create_tokens' do
create_tokens create_tokens
end end
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 end
context 'when db_key_base is blank but exists in secrets.yml' do context 'when db_key_base is blank but exists in secrets.yml' do
before do before do
secrets.otp_key_base = 'otp_key_base' secrets.otp_key_base = 'otp_key_base'
secrets.secret_key_base = 'secret_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 %>') yaml_secrets = secrets.to_h.stringify_keys.merge('db_key_base' => '<%= an_erb_expression %>')
allow(File).to receive(:exist?).with('.secret').and_return(false) allow(File).to receive(:exist?).with('.secret').and_return(false)
......
...@@ -5,6 +5,12 @@ require "spec_helper" ...@@ -5,6 +5,12 @@ require "spec_helper"
RSpec.describe Gitlab::EncryptedConfiguration do RSpec.describe Gitlab::EncryptedConfiguration do
subject(:configuration) { described_class.new } subject(:configuration) { described_class.new }
let!(:config_tmp_dir) { Dir.mktmpdir('config-') }
after do
FileUtils.rm_f(config_tmp_dir)
end
describe '#initialize' do describe '#initialize' do
it 'accepts all args as optional fields' do it 'accepts all args as optional fields' do
expect { configuration }.not_to raise_exception expect { configuration }.not_to raise_exception
...@@ -30,15 +36,24 @@ RSpec.describe Gitlab::EncryptedConfiguration do ...@@ -30,15 +36,24 @@ RSpec.describe Gitlab::EncryptedConfiguration do
end end
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 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_config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
let(:credentials_key) { SecureRandom.hex(64) } let(:credentials_key) { SecureRandom.hex(64) }
after do
FileUtils.rm_f(config_tmp_dir)
end
describe '#write' do describe '#write' do
it 'encrypts the file using the provided key' do it 'encrypts the file using the provided key' do
encryptor = ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(credentials_key), cipher: 'aes-256-gcm') encryptor = ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(credentials_key), cipher: 'aes-256-gcm')
...@@ -63,6 +78,13 @@ RSpec.describe Gitlab::EncryptedConfiguration do ...@@ -63,6 +78,13 @@ RSpec.describe Gitlab::EncryptedConfiguration do
config.write({ foo: { bar: true } }.to_yaml) config.write({ foo: { bar: true } }.to_yaml)
expect(config.foo[:bar]).to be true expect(config.foo[:bar]).to be true
end 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 end
describe '#change' do describe '#change' do
...@@ -80,16 +102,11 @@ RSpec.describe Gitlab::EncryptedConfiguration do ...@@ -80,16 +102,11 @@ RSpec.describe Gitlab::EncryptedConfiguration do
end end
context 'when provided previous_keys for rotation' do 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_original) { SecureRandom.hex(64) }
let(:credential_key_latest) { 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_original) { File.join(config_tmp_dir, 'credentials-orig.yml.enc') }
let(:config_path_latest) { File.join(config_tmp_dir, 'credentials-latest.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) def encryptor(key)
ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(key), cipher: 'aes-256-gcm') ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(key), cipher: 'aes-256-gcm')
end 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