Commit d26f091b authored by Alex Lossent's avatar Alex Lossent

Support Kerberos authentication for Git HTTP access

Add support for the Negotiate authentication protocol for Git HTTP access.
This enables Kerberos ticket-based authentication for users who have a
kerberos identity attached to their account.
parent 7359262f
......@@ -3,6 +3,7 @@ v 7.14
- Don't send "Added to group" notifications when group is LDAP synched
- Fix importing projects from GitHub Enterprise Edition.
- Automatic approver suggestions (based on an authority of the code)
- Support Kerberos ticket-based authentication for Git HTTP access
v7.13.3
- Merge community edition changes for version 7.13.3
......@@ -37,14 +38,13 @@ v 7.12.0
- Prevent LDAP group sync from removing a group's last owner
- Add Git hook to validate maximum file size.
- Project setting: approve merge request by N users before accept
- Add API support for adding and removing LDAP group links
v 7.11.4
- no changes specific to EE
v 7.11.3
- Fixed an issue with git annex
v 7.12.0 (unreleased)
- Add API support for adding and removing LDAP group links
v 7.11.2
- Fixed license upload and verification mechanism
......
......@@ -27,6 +27,7 @@ gem 'omniauth-bitbucket'
gem 'omniauth-saml'
gem 'doorkeeper', '2.1.3'
gem "rack-oauth2", "~> 1.0.5"
gem 'gssapi', group: :kerberos
# Two-factor authentication
gem 'devise-two-factor'
......
......@@ -308,6 +308,8 @@ GEM
grape-entity (0.4.2)
activesupport
multi_json (>= 1.3.2)
gssapi (1.2.0)
ffi (>= 1.0.1)
haml (4.0.5)
tilt
haml-rails (0.5.3)
......@@ -792,6 +794,7 @@ DEPENDENCIES
gon (~> 5.0.0)
grape (~> 0.6.1)
grape-entity (~> 0.4.2)
gssapi
haml-rails
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
......
......@@ -216,6 +216,28 @@ production: &base
# host:
# ....
## Kerberos settings
kerberos:
# Allow the HTTP Negotiate authentication method for Git clients
enabled: false
# Kerberos 5 keytab file. The keytab file must be readable by the GitLab user,
# and should be different from other keytabs in the system.
# (default: use default keytab from Krb5 config)
# keytab: /etc/http.keytab
# The Kerberos service name to be used by GitLab.
# (default: accept any service name in keytab file)
# service_principal_name: HTTP/gitlab.example.com@EXAMPLE.COM
# Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails.
# To support both Basic and Negotiate methods with older versions of Git, configure
# nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines
# to dedicate this port to Kerberos authentication. (default: false)
# use_dedicated_port: true
# port: 8443
# https: true
## OmniAuth settings
omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers
......
......@@ -229,6 +229,17 @@ Settings['satellites'] ||= Settingslogic.new({})
Settings.satellites['path'] = File.expand_path(Settings.satellites['path'] || "tmp/repo_satellites/", Rails.root)
Settings.satellites['timeout'] ||= 30
#
# Kerberos
#
Settings['kerberos'] ||= Settingslogic.new({})
Settings.kerberos['enabled'] = false if Settings.kerberos['enabled'].nil?
Settings.kerberos['keytab'] = nil if Settings.kerberos['keytab'].blank? #nil means use default keytab
Settings.kerberos['service_principal_name'] = nil if Settings.kerberos['service_principal_name'].blank? #nil means any SPN in keytab
Settings.kerberos['use_dedicated_port'] = false if Settings.kerberos['use_dedicated_port'].nil?
Settings.kerberos['https'] = Settings.gitlab.https if Settings.kerberos['https'].nil?
Settings.kerberos['port'] ||= Settings.kerberos.https ? 8443 : 8088
#
# Extra customization
#
......
......@@ -7,6 +7,8 @@ Kerberos integration can be enabled as a regular omniauth provider, edit [gitlab
{ 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.
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.
......@@ -16,6 +18,76 @@ User accounts will be created automatically when authentication was successful.
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:
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`.
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
```
Edit the kerberos section of [gitlab.yml (source installations)](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
kerberos:
# Allow the HTTP Negotiate authentication method for Git clients
enabled: true
# Kerberos 5 keytab file. The keytab file must be readable by the GitLab user,
# and should be different from other keytabs in the system.
# (default: use default keytab from Krb5 config)
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.
#### 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).
It is thus not possible to fall back to username/password (also known as `basic`) authentication if Kerberos authentication fails.
For GitLab users to be able to use either `basic` or `negotiate` authentication with older git versions, it is possible to offer Kerberos ticket-based authentication on a different port (e.g. 8443) while the standard port will keep offering only `basic` authentication. For source installations with HTTPS:
1. Edit the nginx configuration file for GitLab (e.g. `/etc/nginx/sites-available/gitlab-ssl`) and configure nginx to listen to port 8443 in addition to the standard HTTPS port
```yaml
server {
listen 0.0.0.0:443 ssl;
listen [::]:443 ipv6only=on ssl default_server;
listen 0.0.0.0:8443 ssl;
listen [::]:8443 ipv6only=on ssl;
```
1. Update the kerberos section of [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example)
```yaml
kerberos:
# Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails.
# To support both Basic and Negotiate methods with older versions of Git, configure
# nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines
# to dedicate this port to Kerberos authentication. (default: false)
use_dedicated_port: true
port: 8443
https: true
```
1. Restart nginx and gitlab
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
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.
https://help.ubuntu.com/community/Kerberos
......
......@@ -26,38 +26,88 @@ module Grack
auth!
if project && authorized_request?
if ENV['GITLAB_GRACK_AUTH_ONLY'] == '1'
response = if ENV['GITLAB_GRACK_AUTH_ONLY'] == '1'
# Tell gitlab-git-http-server the request is OK, and what the GL_ID is
render_grack_auth_ok
else
@app.call(env)
end
apply_negotiate_final_leg(response)
elsif @user.nil? && !@gitlab_ci
unauthorized
else
render_not_found
apply_negotiate_final_leg(render_not_found)
end
end
private
def allow_basic_auth?
return true unless Gitlab.config.kerberos.enabled &&
Gitlab.config.kerberos.use_dedicated_port &&
@env['SERVER_PORT'] == Gitlab.config.kerberos.port.to_s
end
def allow_kerberos_auth?
return false unless Gitlab.config.kerberos.enabled
return true unless Gitlab.config.kerberos.use_dedicated_port
# When using a dedicated port, allow Kerberos auth only if port matches the configured one
@env['SERVER_PORT'] == Gitlab.config.kerberos.port.to_s
end
def spnego_challenge
return "Negotiate" unless @auth.spnego_response_token
"Negotiate #{::Base64.strict_encode64(@auth.spnego_response_token)}"
end
def challenge
challenges = []
challenges << super if allow_basic_auth?
challenges << spnego_challenge if allow_kerberos_auth?
# Use \n separator to generate multiple WWW-Authenticate headers in case of multiple challenges
challenges.join("\n")
end
def apply_negotiate_final_leg(response)
return response unless allow_kerberos_auth? && @auth.spnego_response_token
# As per RFC4559, we may have a final WWW-Authenticate header to send in
# the response even if it's not a 401 status
status, headers, body = response
headers['WWW-Authenticate'] = spnego_challenge
return [status, headers, body]
end
def valid_auth_method?
(allow_basic_auth? && @auth.basic?) || (allow_kerberos_auth? && @auth.negotiate?)
end
def auth!
return unless @auth.provided?
return bad_request unless @auth.basic?
return bad_request unless valid_auth_method?
# Authentication with username and password
login, password = @auth.credentials
# Allow authentication for GitLab CI service
# if valid token passed
if gitlab_ci_request?(login, password)
@gitlab_ci = true
return
if @auth.negotiate?
# Authentication with Kerberos token
krb_principal = @auth.spnego_credentials!
return unless krb_principal
# Set @user if authentication succeeded
identity = ::Identity.find_by(provider: 'kerberos', extern_uid: krb_principal)
@user = identity.user if identity
else
# Authentication with username and password
login, password = @auth.credentials
# Allow authentication for GitLab CI service
# if valid token passed
if gitlab_ci_request?(login, password)
@gitlab_ci = true
return
end
@user = authenticate_user(login, password)
end
@user = authenticate_user(login, password)
if @user
Gitlab::ShellEnv.set_env(@user)
@env['REMOTE_USER'] = @auth.username
......@@ -186,5 +236,42 @@ module Grack
def render_not_found
[404, { "Content-Type" => "text/plain" }, ["Not Found"]]
end
class Request < Rack::Auth::Basic::Request
attr_reader :spnego_response_token
def negotiate?
parts.first && scheme == "negotiate"
end
def spnego_token
::Base64.strict_decode64(params)
end
def spnego_credentials!
require 'gssapi'
gss = GSSAPI::Simple.new(nil, nil, Gitlab.config.kerberos.keytab)
# the GSSAPI::Simple constructor transforms a nil service name into a default value, so
# pass service name to acquire_credentials explicitly to support the special meaning of nil
gss_service_name = if Gitlab.config.kerberos.service_principal_name.present?
gss.import_name(Gitlab.config.kerberos.service_principal_name)
else
nil # accept any valid service principal name from keytab
end
gss.acquire_credentials(gss_service_name) # grab credentials from keytab
# Decode token
gss_result = gss.accept_context(spnego_token)
# gss_result will be 'true' if nothing has to be returned to the client
@spnego_response_token = gss_result if gss_result && gss_result != true
# Return user principal name if authentication succeeded
gss.display_name
rescue GSSAPI::GssApiError => ex
Rails.logger.error "#{self.class.name}: failed to process Negotiate/Kerberos authentication: #{ex.message}"
false
end
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