Commit 7d79a5c1 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'omniauth-kerberos-spnego' into 'master'

Add ticket-based Kerberos sign-ins

Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/745

This is meant as a replacement for omniauth-kerberos. This MR does not rip out or disable omniauth-kerberos, however, because that has the risk of locking out all Kerberos users on Kerberos-enabled GitLab server that does not have a service keytab yet.

On GitLab installations that have a keytab, namely those which have 'git access via SPNEGO' enabled, this change will automatically enable ticket-based sign-ins. 

See merge request !548
parents ca261caf ad13b958
......@@ -78,6 +78,16 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
handle_omniauth
end
def kerberos_spnego
# The internal kerberos_spnego provider is a replacement for
# omniauth-kerberos. Here we re-use the 'kerberos' provider name to ease
# the transition. In time (in GitLab 9.0?) we should remove the
# omniauth-kerberos gem and rename the internal 'kerberos_spnego'
# provider to plain 'kerberos' and remove this special method.
oauth['provider'] = 'kerberos'
handle_omniauth
end
private
def handle_omniauth
......
require 'omniauth/strategies/kerberos_spnego'
class OmniauthKerberosSpnegoController < ApplicationController
include KerberosSpnegoHelper
skip_before_action :authenticate_user!
def negotiate
if spnego_provided? && (krb_principal = spnego_credentials!(spnego_token))
session[OmniAuth::Strategies::KerberosSpnego::SESSION_KEY] = krb_principal
send_final_spnego_response
redirect_to user_kerberos_spnego_omniauth_callback_path
return
end
# When the browser is Kerberos-aware, this response will make it try to
# get a Kerberos ticket and present it to us via an SPNEGO token.
#
# When the browser does not know Kerberos, or if it tried to
# authenticate with Kerberos but failed, it shows the special 'Kerberos
# denied' 401 page to the user.
#
# We cannot redirect the user to the sign-in page because we do not know
# when the browser has given up.
#
headers['Www-Authenticate'] = spnego_challenge
render 'errors/kerberos_denied.html.haml', layout: 'errors', status: 401
end
end
......@@ -42,7 +42,6 @@ module KerberosSpnegoHelper
end
def find_kerberos_user
spnego_token = Base64.strict_decode64(auth_param(request))
krb_principal = spnego_credentials!(spnego_token)
return unless krb_principal
......@@ -77,4 +76,8 @@ module KerberosSpnegoHelper
Rails.logger.error "#{self.class.name}: failed to process Negotiate/Kerberos authentication: #{ex.message}"
false
end
def spnego_token
Base64.strict_decode64(auth_param(request))
end
end
- page_title "Kerberos access denied"
%h1 401
%h3 Kerberos authentication failed
%hr
%p
Return to the
= succeed "." do
= link_to 'sign-in page', new_user_session_path
......@@ -447,6 +447,10 @@ Settings.kerberos['use_dedicated_port'] = false if Settings.kerberos['use_dedica
Settings.kerberos['https'] = Settings.gitlab.https if Settings.kerberos['https'].nil?
Settings.kerberos['port'] ||= Settings.kerberos.https ? 8443 : 8088
if Settings.kerberos['enabled'] && !Settings.omniauth.providers.map(&:name).include?('kerberos_spnego')
Settings.omniauth.providers << Settingslogic.new({ 'name' => 'kerberos_spnego' })
end
#
# Extra customization
#
......
......@@ -23,9 +23,7 @@ OmniAuth.config.before_request_phase do |env|
end
if Gitlab.config.omniauth.enabled
Gitlab.config.omniauth.providers.each do |provider|
if provider['name'] == 'kerberos'
require 'omniauth-kerberos'
end
end
provider_names = Gitlab.config.omniauth.providers.map(&:name)
require 'omniauth-kerberos' if provider_names.include?('kerberos')
require 'omniauth/strategies/kerberos_spnego' if provider_names.include?('kerberos_spnego')
end
......@@ -499,6 +499,7 @@ Rails.application.routes.draw do
devise_scope :user do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
get '/users/almost_there' => 'confirmations#almost_there'
get '/users/auth/kerberos_spnego/negotiate' => 'omniauth_kerberos_spnego#negotiate'
end
root to: "root#index"
......
# Kerberos integration
GitLab can be configured to allow your users to sign with their Kerberos credentials.
Kerberos integration can be enabled as a regular omniauth provider, edit [gitlab.rb (omnibus-gitlab)](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template) or [gitlab.yml (source installations)](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) on your GitLab server and restart GitLab. You only need to specify the provider name. For example for GitLab omnibus add the following:
```
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['kerberos']
gitlab_rails['omniauth_providers'] = [
{"name" => "kerberos"}
]
```
NB: for source installations, make sure the `kerberos` gem group [has been installed](../install/installation.md#install-gems).
You still need to configure your system for Kerberos usage, such as specifying realms. GitLab will make use of the system's Kerberos settings.
The Administrative user can navigate to **Admin > Users > Example User > Identities** and attach a Kerberos account.
Existing GitLab users can go to profile > account and attach a Kerberos account. if you want to allow users without a GitLab account to login you should enable the option `omniauth_allow_single_sign_on` in config file (default: false). Then, the first time a user signs in with Kerberos credentials, GitLab will create a new GitLab user associated with the email, which is built from the kerberos username and realm.
User accounts will be created automatically when authentication was successful.
## Configuration
### HTTP git access
For GitLab to offer Kerberos token-based authentication, perform the
following prerequisites. You still need to configure your system for
Kerberos usage, such as specifying realms. GitLab will make use of the
system's Kerberos settings.
A linked Kerberos account enables you to `git pull` and `git push` using your Kerberos account, as well as your standard GitLab credentials.
### HTTP git access with Kerberos token (passwordless authentication)
GitLab users with a linked Kerberos account can also `git pull` and `git push` using Kerberos tokens, i.e. without having to send their password with each operation.
For GitLab to offer Kerberos token-based authentication, perform the following prerequisites:
### GitLab keytab
1. Create a Kerberos Service Principal for the HTTP service on your GitLab server. If your GitLab server is gitlab.example.com and your Kerberos realm EXAMPLE.COM, create a Service Principal `HTTP/gitlab.example.com@EXAMPLE.COM` in your Kerberos database.
1. Create a keytab for the above Service Principal, e.g. `/etc/http.keytab`.
1. Create a keytab on the GitLab server for the above Service Principal, e.g. `/etc/http.keytab`.
The keytab is a sensitive file and must be readable by the GitLab user. Set ownership and protect the file appropriately:
```
$ sudo chown git /etc/http.keytab
$ sudo chmod 0700 /etc/http.keytab
$ sudo chmod 0600 /etc/http.keytab
```
#### Installations from source
### Installations from source
NB: for source installations, make sure the `kerberos` gem group [has been installed](../install/installation.md#install-gems).
Edit the kerberos section of [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) to enable Kerberos ticket-based authentication. In most cases, you only need to enable Kerberos and specify the location of the keytab:
```yaml
omniauth:
enabled: true
allow_single_sign_on: ['kerberos']
kerberos:
# Allow the HTTP Negotiate authentication method for Git clients
enabled: true
......@@ -55,13 +44,15 @@ Edit the kerberos section of [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-c
keytab: /etc/http.keytab
```
Restart GitLab to apply the changes. GitLab will now offer the `negotiate` authentication method for HTTP git access, enabling git clients that support this authentication protocol to authenticate with Kerberos tokens.
Restart GitLab to apply the changes. GitLab will now offer the `negotiate` authentication method for signing in and HTTP git access, enabling git clients that support this authentication protocol to authenticate with Kerberos tokens.
##### Omnibus package installations
### Omnibus package installations
In `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['kerberos']
gitlab_rails['kerberos_enabled'] = true
gitlab_rails['kerberos_keytab'] = "/etc/http.keytab"
......@@ -69,6 +60,21 @@ gitlab_rails['kerberos_keytab'] = "/etc/http.keytab"
and run `sudo gitlab-ctl reconfigure` for changes to take effect.
## Creating and linking Kerberos accounts
The Administrative user can navigate to **Admin > Users > Example User > Identities** and attach a Kerberos account.
Existing GitLab users can go to profile > account and attach a Kerberos account. if you want to allow users without a GitLab account to login you should enable the option `omniauth_allow_single_sign_on` in config file (default: false). Then, the first time a user signs in with Kerberos credentials, GitLab will create a new GitLab user associated with the email, which is built from the kerberos username and realm.
User accounts will be created automatically when authentication was successful.
## HTTP git access
A linked Kerberos account enables you to `git pull` and `git push` using your Kerberos account, as well as your standard GitLab credentials.
GitLab users with a linked Kerberos account can also `git pull` and `git push` using Kerberos tokens, i.e. without having to send their password with each operation.
### HTTP git access with Kerberos token (passwordless authentication)
#### Support for Git before 2.4
Until version 2.4, the `git` command uses only the `negotiate` authentication method if the HTTP server offers it, even if this method fails (such as when the client does not have a Kerberos token).
......@@ -115,11 +121,11 @@ and run `sudo gitlab-ctl reconfigure` for changes to take effect.
Git remote URLs will have to be updated to `https://gitlab.example.com:8443/mygroup/myproject.git` in order to use Kerberos ticket-based authentication.
#### Support for Active Directory Kerberos environments
## Support for Active Directory Kerberos environments
When using Kerberos ticket-based authentication in an Active Directory domain, it may be necessary to increase the maximum header size allowed by nginx, as extensions to the Kerberos protocol may result in HTTP authentication headers larger than the default size of 8kB. Configure `large_client_header_buffers` to a larger value in [the nginx configuration](http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers).
### Helpful links to setup development kerberos environment.
## Helpful links to setup development kerberos environment.
https://help.ubuntu.com/community/Kerberos
......
require 'omniauth'
module OmniAuth
module Strategies
class KerberosSpnego
include OmniAuth::Strategy
include Gitlab::Routing.url_helpers
SESSION_KEY = :kerberos_spnego_principal_name
option :name, 'kerberos_spnego'
uid { principal_name }
info do
{ username: username, email: email }
end
def username
principal_name.split('@')[0]
end
def email
username + '@' + principal_name.split('@')[1].downcase
end
def principal_name
return @principal_name if defined?(@principal_name)
@principal_name = session.delete(SESSION_KEY)
end
def request_phase
redirect users_auth_kerberos_spnego_negotiate_path
end
end
end
end
require 'spec_helper'
require 'omniauth/strategies/kerberos_spnego'
describe OmniAuth::Strategies::KerberosSpnego do
subject { described_class.new(:app) }
let(:session) { Hash.new }
before do
allow(subject).to receive(:session).and_return(session)
end
it 'uses the principal name as the "uid"' do
session[:kerberos_spnego_principal_name] = 'Janedoe@FOOBAR.COM'
expect(subject.uid).to eq('Janedoe@FOOBAR.COM')
end
it 'extracts the username' do
session[:kerberos_spnego_principal_name] = 'Janedoe@FOOBAR.COM'
expect(subject.username).to eq('Janedoe')
end
it 'turns the principal name into an email address' do
session[:kerberos_spnego_principal_name] = 'Janedoe@FOOBAR.COM'
expect(subject.email).to eq('Janedoe@foobar.com')
end
it 'clears its special session key' do
session[:kerberos_spnego_principal_name] = 'Janedoe@FOOBAR.COM'
subject.username
expect(session).to eq({})
end
end
require 'spec_helper'
require 'omniauth/strategies/kerberos_spnego'
describe 'OmniAuth Kerberos SPNEGO', lib: true do
let(:path) { '/users/auth/kerberos_spnego/negotiate' }
let(:controller_class) { OmniauthKerberosSpnegoController }
before do
# In production user_kerberos_spnego_omniauth_callback_path is defined
# dynamically early when the app boots. Because this is hard to set up
# during testing we stub out this path helper on the controller.
allow_any_instance_of(controller_class).to receive(:user_kerberos_spnego_omniauth_callback_path).
and_return(OmniAuth::Strategies::KerberosSpnego.new(:app).callback_path)
end
it 'asks for an SPNEGO token' do
get path
expect(response.status).to eq(401)
expect(response.header['Www-Authenticate']).to eq('Negotiate')
end
context 'when an SPNEGO token is provided' do
it 'passes the token to spnego_negotiate!' do
expect_any_instance_of(controller_class).to receive(:spnego_credentials!).
with('fake spnego token')
get path, {}, spnego_header
end
end
context 'when the final SPNEGO token is provided' do
before do
expect_any_instance_of(controller_class).to receive(:spnego_credentials!).
with('fake spnego token').and_return('janedoe@EXAMPLE.COM')
end
it 'redirects to the omniauth callback' do
get path, {}, spnego_header
expect(response).to redirect_to('/users/auth/kerberos_spnego/callback')
end
it 'stores the users principal name in the session' do
get path, {}, spnego_header
expect(session[:kerberos_spnego_principal_name]).to eq('janedoe@EXAMPLE.COM')
end
it 'send the final SPNEGO response' do
allow_any_instance_of(controller_class).to receive(:spnego_response_token).
and_return("it's the final token")
get path, {}, spnego_header
expect(response.header['Www-Authenticate']).to eq(
"Negotiate #{Base64.strict_encode64("it's the final token")}"
)
end
end
def spnego_header
{ 'HTTP_AUTHORIZATION' => "Negotiate #{Base64.strict_encode64('fake spnego token')}" }
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