Commit de6d6c5a authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ldap-secret-command' into 'master'

Add encrypted ldap secrets support

See merge request gitlab-org/gitlab!45712
parents 9b00092e 01275f09
---
title: Add encrypted ldap secrets support
merge_request: 45712
author:
type: added
......@@ -620,6 +620,9 @@ production: &base
enabled: false
prevent_ldap_sign_in: false
# File location to read encrypted secrets from
# secret_file: /mnt/gitlab/ldap.yaml.enc # Default: shared/encrypted_settings/ldap.yaml.enc
# This setting controls the number of seconds between LDAP permission checks
# for each user. After this time has expired for a given user, their next
# interaction with GitLab (a click in the web UI, a git pull, etc.) will be
......
......@@ -13,6 +13,7 @@ Settings.encrypted_settings['path'] = Settings.absolute(Settings.encrypted_setti
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?
Settings.ldap['secret_file'] = Settings.absolute(Settings.ldap['secret_file'] || File.join(Settings.encrypted_settings['path'], "ldap.yaml.enc"))
Gitlab.ee do
Settings.ldap['sync_time'] = 3600 if Settings.ldap['sync_time'].nil?
......
......@@ -53,6 +53,10 @@ module Gitlab
raise InvalidProvider.new("Unknown provider (#{provider}). Available providers: #{providers}")
end
def self.encrypted_secrets
Settings.encrypted(Gitlab.config.ldap.secret_file)
end
def initialize(provider)
if self.class.valid_provider?(provider)
@provider = provider
......@@ -89,8 +93,8 @@ module Gitlab
if has_auth?
opts.merge!(
bind_dn: options['bind_dn'],
password: options['password']
bind_dn: auth_username,
password: auth_password
)
end
......@@ -155,7 +159,7 @@ module Gitlab
end
def has_auth?
options['password'] || options['bind_dn']
auth_password || auth_username
end
def allow_username_or_email_login
......@@ -267,12 +271,32 @@ module Gitlab
{
auth: {
method: :simple,
username: options['bind_dn'],
password: options['password']
username: auth_username,
password: auth_password
}
}
end
def secrets
@secrets ||= self.class.encrypted_secrets[@provider.delete_prefix('ldap').to_sym]
rescue => e
Gitlab::AppLogger.error "LDAP encrypted secrets are invalid: #{e.inspect}"
nil
end
def auth_password
return options['password'] if options['password']
secrets&.fetch(:password, nil)&.chomp
end
def auth_username
return options['bind_dn'] if options['bind_dn']
secrets&.fetch(:bind_dn, nil)&.chomp
end
def omniauth_user_filter
uid_filter = Net::LDAP::Filter.eq(uid, '%{username}')
......
# frozen_string_literal: true
# rubocop:disable Rails/Output
module Gitlab
class EncryptedLdapCommand
class << self
def write(contents)
encrypted = Gitlab::Auth::Ldap::Config.encrypted_secrets
return unless validate_config(encrypted)
validate_contents(contents)
encrypted.write(contents)
puts "File encrypted and saved."
rescue Interrupt
puts "Aborted changing file: nothing saved."
rescue ActiveSupport::MessageEncryptor::InvalidMessage
puts "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
end
def edit
encrypted = Gitlab::Auth::Ldap::Config.encrypted_secrets
return unless validate_config(encrypted)
if ENV["EDITOR"].blank?
puts 'No $EDITOR specified to open file. Please provide one when running the command:'
puts 'gitlab-rake gitlab:ldap:secret:edit EDITOR=vim'
return
end
temp_file = Tempfile.new(File.basename(encrypted.content_path), File.dirname(encrypted.content_path))
contents_changed = false
encrypted.change do |contents|
contents = encrypted_file_template unless File.exist?(encrypted.content_path)
File.write(temp_file.path, contents)
system(ENV['EDITOR'], temp_file.path)
changes = File.read(temp_file.path)
contents_changed = contents != changes
validate_contents(changes)
changes
end
puts "Contents were unchanged." unless contents_changed
puts "File encrypted and saved."
rescue Interrupt
puts "Aborted changing file: nothing saved."
rescue ActiveSupport::MessageEncryptor::InvalidMessage
puts "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
ensure
temp_file&.unlink
end
def show
encrypted = Gitlab::Auth::Ldap::Config.encrypted_secrets
return unless validate_config(encrypted)
puts encrypted.read.presence || "File '#{encrypted.content_path}' does not exist. Use `gitlab-rake gitlab:ldap:secret:edit` to change that."
rescue ActiveSupport::MessageEncryptor::InvalidMessage
puts "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
end
private
def validate_config(encrypted)
dir_path = File.dirname(encrypted.content_path)
unless File.exist?(dir_path)
puts "Directory #{dir_path} does not exist. Create the directory and try again."
return false
end
if encrypted.key.nil?
puts "Missing encryption key encrypted_settings_key_base."
return false
end
true
end
def validate_contents(contents)
begin
config = YAML.safe_load(contents, permitted_classes: [Symbol])
error_contents = "Did not include any key-value pairs" unless config.is_a?(Hash)
rescue Psych::Exception => e
error_contents = e.message
end
puts "WARNING: Content was not a valid LDAP secret yml file. #{error_contents}" if error_contents
contents
end
def encrypted_file_template
<<~YAML
# main:
# password: '123'
# user_dn: 'gitlab-adm'
YAML
end
end
end
end
# rubocop:enable Rails/Output
......@@ -36,5 +36,23 @@ namespace :gitlab do
puts "Successfully updated #{plural_updated_count} out of #{plural_id_count} total"
end
end
namespace :secret do
desc 'GitLab | LDAP | Secret | Write LDAP secrets'
task write: [:environment] do
content = STDIN.tty? ? STDIN.gets : STDIN.read
Gitlab::EncryptedLdapCommand.write(content)
end
desc 'GitLab | LDAP | Secret | Edit LDAP secrets'
task edit: [:environment] do
Gitlab::EncryptedLdapCommand.edit
end
desc 'GitLab | LDAP | Secret | Show LDAP secrets'
task show: [:environment] do
Gitlab::EncryptedLdapCommand.show
end
end
end
end
......@@ -13,3 +13,112 @@ RSpec.describe 'gitlab:ldap:rename_provider rake task' do
run_rake_task('gitlab:ldap:rename_provider', 'ldapmain', 'ldapfoo')
end
end
RSpec.describe 'gitlab:ldap:secret rake tasks' do
let(:ldap_secret_file) { 'tmp/tests/ldapenc/ldap_secret.yaml.enc' }
before do
Rake.application.rake_require 'tasks/gitlab/ldap'
stub_env('EDITOR', 'cat')
stub_warn_user_is_not_gitlab
FileUtils.mkdir_p('tmp/tests/ldapenc/')
allow(Gitlab.config.ldap).to receive(:secret_file).and_return(ldap_secret_file)
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
end
after do
FileUtils.rm_rf(Rails.root.join('tmp/tests/ldapenc'))
end
describe ':show' do
it 'displays error when file does not exist' do
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/File .* does not exist. Use `gitlab-rake gitlab:ldap:secret:edit` to change that./).to_stdout
end
it 'displays error when key does not exist' do
Settings.encrypted(ldap_secret_file).write('somevalue')
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
end
it 'displays error when key is changed' do
Settings.encrypted(ldap_secret_file).write('somevalue')
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stdout
end
it 'outputs the unencrypted content when present' do
encrypted = Settings.encrypted(ldap_secret_file)
encrypted.write('somevalue')
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/somevalue/).to_stdout
end
end
describe 'edit' do
it 'creates encrypted file' do
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/File encrypted and saved./).to_stdout
expect(File.exist?(ldap_secret_file)).to be true
value = Settings.encrypted(ldap_secret_file)
expect(value.read).to match(/password: '123'/)
end
it 'displays error when key does not exist' do
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
end
it 'displays error when key is changed' do
Settings.encrypted(ldap_secret_file).write('somevalue')
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stdout
end
it 'displays error when write directory does not exist' do
FileUtils.rm_rf(Rails.root.join('tmp/tests/ldapenc'))
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Directory .* does not exist./).to_stdout
end
it 'shows a warning when content is invalid' do
Settings.encrypted(ldap_secret_file).write('somevalue')
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/WARNING: Content was not a valid LDAP secret yml file/).to_stdout
value = Settings.encrypted(ldap_secret_file)
expect(value.read).to match(/somevalue/)
end
it 'displays error when $EDITOR is not set' do
stub_env('EDITOR', nil)
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/No \$EDITOR specified to open file. Please provide one when running the command/).to_stdout
end
end
describe 'write' do
before do
allow(STDIN).to receive(:tty?).and_return(false)
allow(STDIN).to receive(:read).and_return('testvalue')
end
it 'creates encrypted file from stdin' do
expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/File encrypted and saved./).to_stdout
expect(File.exist?(ldap_secret_file)).to be true
value = Settings.encrypted(ldap_secret_file)
expect(value.read).to match(/testvalue/)
end
it 'displays error when key does not exist' do
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
end
it 'displays error when write directory does not exist' do
FileUtils.rm_rf('tmp/tests/ldapenc/')
expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Directory .* does not exist./).to_stdout
end
it 'shows a warning when content is invalid' do
Settings.encrypted(ldap_secret_file).write('somevalue')
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/WARNING: Content was not a valid LDAP secret yml file/).to_stdout
value = Settings.encrypted(ldap_secret_file)
expect(value.read).to match(/somevalue/)
end
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