Commit 15c4ebf9 authored by Bob Van Landuyt's avatar Bob Van Landuyt

TLS Client auth for external authorization service

This allows an admin to provide a client certificate and private key
to perform TLS client authentication on the external authorization
service.

Optionally a passphrase for the key can be provided.
parent 2bfdd26f
......@@ -57,7 +57,10 @@ module EE
:external_authorization_service_enabled,
:external_authorization_service_url,
:external_authorization_service_default_label,
:external_authorization_service_timeout
:external_authorization_service_timeout,
:external_auth_client_cert,
:external_auth_client_key,
:external_auth_client_key_pass
]
end
......
......@@ -51,6 +51,20 @@ module EE
validates :external_authorization_service_timeout,
numericality: { greater_than: 0, less_than_or_equal_to: 10 },
if: :external_authorization_service_enabled?
validates :external_auth_client_key,
presence: true,
if: ->(setting) { setting.external_auth_client_cert.present? }
attr_encrypted :external_auth_client_key,
mode: :per_attribute_iv,
key: ::Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-gcm'
attr_encrypted :external_auth_client_key_pass,
mode: :per_attribute_iv,
key: ::Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-gcm'
end
module ClassMethods
......
......@@ -23,6 +23,17 @@
= f.number_field :external_authorization_service_timeout, class: 'form-control', min: 0.001, max: 10, step: 0.001
%span.help-block
= external_authorization_timeout_help_text
= f.label :external_auth_client_cert, _('Client authentication certificate'), class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :external_auth_client_cert, class: 'form-control'
.form-group
= f.label :external_auth_client_key, _('Client authentication key'), class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :external_auth_client_key, class: 'form-control'
.form-group
= f.label :external_auth_client_key_pass, _('Client authentication key password'), class: 'control-label col-sm-2'
.col-sm-10
= f.password_field :external_auth_client_key_pass, class: 'form-control'
.form-group
= f.label :external_authorization_service_default_label, _('Default classification label'), class: 'control-label col-sm-2'
.col-sm-10
......
---
title: Authenticate using TLS certificate for requests to external authorization service
merge_request: 5028
author:
type: added
module EE
module Gitlab
module ExternalAuthorization
extend Config
RequestFailed = Class.new(StandardError)
def self.access_allowed?(user, label)
......@@ -26,28 +28,6 @@ module EE
EE::Gitlab::ExternalAuthorization::Access.new(user, label).load!
end
end
def self.enabled?
::Gitlab::CurrentSettings
.current_application_settings
.external_authorization_service_enabled?
end
def self.perform_check?
enabled? && service_url.present?
end
def self.service_url
::Gitlab::CurrentSettings
.current_application_settings
.external_authorization_service_url
end
def self.timeout
::Gitlab::CurrentSettings
.current_application_settings
.external_authorization_service_timeout
end
end
end
end
......@@ -29,7 +29,7 @@ module EE
end
def load_from_service
response = Client.build(@user, @label).request_access
response = Client.new(@user, @label).request_access
@access = response.successful?
@reason = response.reason
@loaded_at = Time.now
......
......@@ -2,32 +2,21 @@ module EE
module Gitlab
module ExternalAuthorization
class Client
include Config
REQUEST_HEADERS = {
'Content-Type' => 'application/json',
'Accept' => 'application/json'
}.freeze
def self.build(user, label)
new(
::EE::Gitlab::ExternalAuthorization.service_url,
::EE::Gitlab::ExternalAuthorization.timeout,
user,
label
)
end
def initialize(url, timeout, user, label)
@url, @timeout, @user, @label = url, timeout, user, label
def initialize(user, label)
@user, @label = user, label
end
def request_access
response = Excon.post(
@url,
headers: REQUEST_HEADERS,
body: body.to_json,
connect_timeout: @timeout,
read_timeout: @timeout,
write_timeout: @timeout
service_url,
post_params
)
EE::Gitlab::ExternalAuthorization::Response.new(response)
rescue Excon::Error => e
......@@ -36,6 +25,22 @@ module EE
private
def post_params
params = { headers: REQUEST_HEADERS,
body: body.to_json,
connect_timeout: timeout,
read_timeout: timeout,
write_timeout: timeout }
if has_tls?
params[:client_cert_data] = client_cert
params[:client_key_data] = client_key
params[:client_key_pass] = client_key_pass
end
params
end
def body
@body ||= begin
body = {
......
module EE
module Gitlab
module ExternalAuthorization
module Config
extend self
def timeout
application_settings.external_authorization_service_timeout
end
def service_url
application_settings.external_authorization_service_url
end
def enabled?
application_settings.external_authorization_service_enabled?
end
def perform_check?
enabled? && service_url.present?
end
def client_cert
application_settings.external_auth_client_cert
end
def client_key
application_settings.external_auth_client_key
end
def client_key_pass
application_settings.external_auth_client_key_pass
end
def has_tls?
client_cert.present? && client_key.present?
end
private
def application_settings
::Gitlab::CurrentSettings.current_application_settings
end
end
end
end
end
......@@ -86,7 +86,10 @@ describe Admin::ApplicationSettingsController do
external_authorization_service_enabled: true,
external_authorization_service_url: 'https://custom.service/',
external_authorization_service_default_label: 'default',
external_authorization_service_timeout: 3
external_authorization_service_timeout: 3,
external_auth_client_cert: "certificate content",
external_auth_client_key: "certificate key",
external_auth_client_key_pass: "certificate key password"
}
end
let(:feature) { :external_authorization_service }
......
......@@ -39,7 +39,7 @@ describe EE::Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do
before do
allow(access).to receive(:load_from_cache)
allow(fake_client).to receive(:request_access).and_return(fake_response)
allow(EE::Gitlab::ExternalAuthorization::Client).to receive(:build) { fake_client }
allow(EE::Gitlab::ExternalAuthorization::Client).to receive(:new) { fake_client }
end
context 'when loading from the webservice' do
......
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe EE::Gitlab::ExternalAuthorization::Client do
let(:user) { build(:user, email: 'dummy_user@example.com') }
let(:dummy_url) { 'https://dummy.net/' }
subject(:client) { described_class.build(user, 'dummy_label') }
subject(:client) { described_class.new(user, 'dummy_label') }
before do
stub_application_setting(external_authorization_service_url: dummy_url)
......@@ -28,7 +28,9 @@ describe EE::Gitlab::ExternalAuthorization::Client do
end
it 'respects the the timeout' do
allow(EE::Gitlab::ExternalAuthorization).to receive(:timeout).and_return(3)
stub_application_setting(
external_authorization_service_timeout: 3
)
expect(Excon).to receive(:post).with(dummy_url,
hash_including(
......@@ -40,6 +42,23 @@ describe EE::Gitlab::ExternalAuthorization::Client do
client.request_access
end
it 'adds the mutual tls params when they are present' do
stub_application_setting(
external_auth_client_cert: 'the certificate data',
external_auth_client_key: 'the key data',
external_auth_client_key_pass: 'open sesame'
)
expected_params = {
client_cert_data: 'the certificate data',
client_key_data: 'the key data',
client_key_pass: 'open sesame'
}
expect(Excon).to receive(:post).with(dummy_url, hash_including(expected_params))
client.request_access
end
it 'returns an expected response' do
expect(Excon).to receive(:post)
......
......@@ -36,6 +36,12 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value(nil).for(:external_authorization_service_default_label) }
it { is_expected.not_to allow_value(11).for(:external_authorization_service_timeout) }
it { is_expected.not_to allow_value(0).for(:external_authorization_service_timeout) }
it 'requires a client key when a certificate is set' do
setting.external_auth_client_cert = 'certificate'
expect(setting).not_to allow_value(nil).for(:external_auth_client_key)
end
end
end
......
......@@ -84,7 +84,10 @@ describe API::Settings, 'EE Settings' do
external_authorization_service_enabled: true,
external_authorization_service_url: 'https://custom.service/',
external_authorization_service_default_label: 'default',
external_authorization_service_timeout: 9.99
external_authorization_service_timeout: 9.99,
external_auth_client_cert: "certificate content",
external_auth_client_key: "certificate key",
external_auth_client_key_pass: "certificate key password"
}
end
let(:feature) { :external_authorization_service }
......
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-23 20:21+0100\n"
"PO-Revision-Date: 2018-03-23 20:21+0100\n"
"POT-Creation-Date: 2018-03-26 15:11+0200\n"
"PO-Revision-Date: 2018-03-26 15:11+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -864,6 +864,15 @@ msgstr ""
msgid "Click to expand text"
msgstr ""
msgid "Client authentication certificate"
msgstr ""
msgid "Client authentication key"
msgstr ""
msgid "Client authentication key password"
msgstr ""
msgid "Clone repository"
msgstr ""
......@@ -4710,6 +4719,15 @@ msgstr[1] ""
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr ""
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
msgstr ""
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
msgstr ""
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
msgstr ""
msgid "mrWidget|Add approval"
msgstr ""
......@@ -4755,18 +4773,27 @@ msgstr ""
msgid "mrWidget|Closes"
msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
msgid "mrWidget|Did not close"
msgstr ""
msgid "mrWidget|Email patches"
msgstr ""
msgid "mrWidget|Failed to load deployment statistics"
msgstr ""
msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
msgstr ""
msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
msgstr ""
msgid "mrWidget|Loading deployment statistics"
msgstr ""
msgid "mrWidget|Mentions"
msgstr ""
......
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