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