Commit d0262925 authored by Alex Lossent's avatar Alex Lossent

Import Kerberos identities automatically from Active Directory

When Kerberos authentication is enabled (either via Omniauth or
for passwordless Git HTTP) together with an LDAP that is Active
Directory (AD), we can automatically import Kerberos identities
from Active Directory for LDAP users. These users can then authenticate
to GitLab with Kerberos without having to attach their Kerberos
identity beforehand via the Omniauth provider.

This behavior is automatically enabled. It seems unlikely that
one would enable Kerberos in an Active Directory environament and not
want this behavior.
parent 6e87c3f9
...@@ -3,6 +3,8 @@ v 8.3.0 (unreleased) ...@@ -3,6 +3,8 @@ v 8.3.0 (unreleased)
- Show Kerberos clone url when Kerberos enabled and url different than HTTP url (Borja Aparicio) - Show Kerberos clone url when Kerberos enabled and url different than HTTP url (Borja Aparicio)
- Fix bug with negative approvals required - Fix bug with negative approvals required
- Add group contribution statistics page - Add group contribution statistics page
- Automatically import Kerberos identities from Active Directory when Kerberos is enabled (Alex Lossent)
- Canonicalization of Kerberos identities to always include realm (Alex Lossent)
v 8.2.3 v 8.2.3
- No EE-specific changes - No EE-specific changes
...@@ -18,7 +20,6 @@ v 8.2.2 ...@@ -18,7 +20,6 @@ v 8.2.2
v 8.2.1 v 8.2.1
- Forcefully update builds that didn't want to update with state machine - Forcefully update builds that didn't want to update with state machine
- Fix: saving GitLabCiService as Admin Template - Fix: saving GitLabCiService as Admin Template
- Canonicalization of Kerberos identities to always include realm (Alex Lossent)
v 8.2.0 v 8.2.0
- Invalidate stored jira password if the endpoint URL is changed - Invalidate stored jira password if the endpoint URL is changed
...@@ -47,8 +48,6 @@ v 8.1.0 ...@@ -47,8 +48,6 @@ v 8.1.0
- Added an issues template (Hannes Rosenögger) - Added an issues template (Hannes Rosenögger)
- Add documentation for "Share project with group" API call - Add documentation for "Share project with group" API call
- Ability to disable 'Share with Group' feature (via UI and API) - Ability to disable 'Share with Group' feature (via UI and API)
- Add documentation for "Share project with group" API call
- Abiliy to disable 'Share with Group' feature (via UI and API)
v 8.0.6 v 8.0.6
- No EE-specific changes - No EE-specific changes
......
...@@ -72,6 +72,8 @@ module Gitlab ...@@ -72,6 +72,8 @@ module Gitlab
update_ssh_keys update_ssh_keys
end end
update_kerberos_identity if import_kerberos_identities?
# Skip updating group permissions # Skip updating group permissions
# if instance does not use group_base setting # if instance does not use group_base setting
return true unless group_base.present? return true unless group_base.present?
...@@ -118,6 +120,21 @@ module Gitlab ...@@ -118,6 +120,21 @@ module Gitlab
end end
end end
# Update user Kerberos identity with Kerberos principal name from Active Directory
def update_kerberos_identity
# there can be only one Kerberos identity in GitLab; if the user has a Kerberos identity in AD,
# replace any existing Kerberos identity for the user
return unless ldap_user.kerberos_principal.present?
kerberos_identity = user.identities.where(provider: :kerberos).first
return if kerberos_identity && kerberos_identity.extern_uid == ldap_user.kerberos_principal
kerberos_identity ||= Identity.new(provider: :kerberos, user: user)
kerberos_identity.extern_uid = ldap_user.kerberos_principal
unless kerberos_identity.save
Rails.logger.error "#{self.class.name}: failed to add Kerberos principal #{principal} to #{user.name} (#{user.id})\n"\
"error messages: #{new_identity.errors.messages}"
end
end
# Update user email if it changed in LDAP # Update user email if it changed in LDAP
def update_email def update_email
return false unless ldap_user.try(:email) return false unless ldap_user.try(:email)
...@@ -183,6 +200,11 @@ module Gitlab ...@@ -183,6 +200,11 @@ module Gitlab
ldap_config.sync_ssh_keys? ldap_config.sync_ssh_keys?
end end
def import_kerberos_identities?
# Kerberos may be enabled for Git HTTP access and/or as an Omniauth provider
ldap_config.active_directory && (Gitlab.config.kerberos.enabled || AuthHelper.kerberos_enabled? )
end
def group_base def group_base
ldap_config.group_base ldap_config.group_base
end end
......
...@@ -57,6 +57,25 @@ module Gitlab ...@@ -57,6 +57,25 @@ module Gitlab
end end
end end
def kerberos_principal
# The following is only meaningful for Active Directory
return unless entry.respond_to?(:sAMAccountName)
entry[:sAMAccountName].first + '@' + windows_domain_name.upcase
end
def windows_domain_name
# The following is only meaningful for Active Directory
require 'net/ldap/dn'
dn_components=[]
Net::LDAP::DN.new(dn).each_pair { |name, value| dn_components << { name: name, value: value } }
dn_components.
reverse.
take_while { |rdn| rdn[:name].upcase=='DC' }. # Domain Component
map { |rdn| rdn[:value] }.
reverse.
join('.')
end
private private
def entry def entry
......
...@@ -93,32 +93,79 @@ describe Gitlab::LDAP::Access do ...@@ -93,32 +93,79 @@ describe Gitlab::LDAP::Access do
subject { access.update_permissions } subject { access.update_permissions }
it "syncs ssh keys if enabled by configuration" do it "syncs ssh keys if enabled by configuration" do
allow(access).to receive_messages(sync_ssh_keys?: 'sshpublickey') allow(access).to receive_messages(group_base: '', sync_ssh_keys?: 'sshpublickey', import_kerberos_identities?: false)
expect(access).to receive(:update_ssh_keys).once expect(access).to receive(:update_ssh_keys).once
subject subject
end end
it "does update group permissions with a group base configured" do it "does update group permissions with a group base configured" do
allow(access).to receive_messages(group_base: 'my-group-base') allow(access).to receive_messages(group_base: 'my-group-base', sync_ssh_keys?: false, import_kerberos_identities?: false)
expect(access).to receive(:update_ldap_group_links) expect(access).to receive(:update_ldap_group_links)
subject subject
end end
it "does not update group permissions without a group base configured" do it "does not update group permissions without a group base configured" do
allow(access).to receive_messages(group_base: '') allow(access).to receive_messages(group_base: '', sync_ssh_keys?: false, import_kerberos_identities?: false)
expect(access).not_to receive(:update_ldap_group_links) expect(access).not_to receive(:update_ldap_group_links)
subject subject
end end
it "does update admin group permissions if admin group is configured" do it "does update admin group permissions if admin group is configured" do
allow(access).to receive_messages(admin_group: 'my-admin-group', update_ldap_group_links: nil) allow(access).to receive_messages(admin_group: 'my-admin-group', update_ldap_group_links: nil, sync_ssh_keys?: false, import_kerberos_identities?: false)
expect(access).to receive(:update_admin_status) expect(access).to receive(:update_admin_status)
subject subject
end end
it "does update Kerberos identities if Kerberos is enabled and the LDAP server is Active Directory" do
allow(access).to receive_messages(group_base: '', sync_ssh_keys?: false, import_kerberos_identities?: true)
expect(access).to receive(:update_kerberos_identity)
subject
end
end
describe :update_kerberos_identity do
let(:entry) do
Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com")
end
before do
allow(access).to receive_messages(ldap_user: Gitlab::LDAP::Person.new(entry, user.ldap_identity.provider))
end
it "should add a Kerberos identity if it is in Active Directory but not in GitLab" do
allow_any_instance_of(Gitlab::LDAP::Person).to receive_messages(kerberos_principal: "mylogin@FOO.COM")
expect{ access.update_kerberos_identity }.to change(user.identities.where(provider: :kerberos), :count).from(0).to(1)
expect(user.identities.where(provider: "kerberos").last.extern_uid).to eq("mylogin@FOO.COM")
end
it "should update existing Kerberos identity in GitLab if Active Directory has a different one" do
allow_any_instance_of(Gitlab::LDAP::Person).to receive_messages(kerberos_principal: "otherlogin@BAR.COM")
user.identities.build(provider: "kerberos", extern_uid: "mylogin@FOO.COM").save
expect{ access.update_kerberos_identity }.not_to change(user.identities.where(provider: "kerberos"), :count)
expect(user.identities.where(provider: "kerberos").last.extern_uid).to eq("otherlogin@BAR.COM")
end
it "should not remove Kerberos identities from GitLab if they are none in the LDAP provider" do
allow_any_instance_of(Gitlab::LDAP::Person).to receive_messages(kerberos_principal: nil)
user.identities.build(provider: "kerberos", extern_uid: "otherlogin@BAR.COM").save
expect{ access.update_kerberos_identity }.not_to change(user.identities.where(provider: "kerberos"), :count)
expect(user.identities.where(provider: "kerberos").last.extern_uid).to eq("otherlogin@BAR.COM")
end
it "should not modify identities in GitLab if they are no kerberos principal in the LDAP provider" do
allow_any_instance_of(Gitlab::LDAP::Person).to receive_messages(kerberos_principal: nil)
expect{ access.update_kerberos_identity }.not_to change(user.identities, :count)
end
end end
describe :update_ssh_keys do describe :update_ssh_keys do
......
...@@ -2,6 +2,35 @@ require "spec_helper" ...@@ -2,6 +2,35 @@ require "spec_helper"
describe Gitlab::LDAP::Person do describe Gitlab::LDAP::Person do
describe "#kerberos_principal" do
let(:entry) do
ldif = "dn: cn=foo, dc=bar, dc=com\n"
ldif += "sAMAccountName: #{sam_account_name}\n" if sam_account_name
Net::LDAP::Entry.from_single_ldif_string(ldif)
end
subject { Gitlab::LDAP::Person.new(entry, 'ldapmain') }
context "when sAMAccountName is not defined (non-AD LDAP server)" do
let(:sam_account_name) { nil }
it "returns nil" do
expect(subject.kerberos_principal).to be_nil
end
end
context "when sAMAccountName is defined (AD server)" do
let(:sam_account_name) { "mylogin" }
it "returns the principal combining sAMAccountName and DC components of the distinguishedName" do
expect(subject.kerberos_principal).to eq("mylogin@BAR.COM")
end
end
end
describe "#ssh_keys" do describe "#ssh_keys" do
let(:ssh_key) { "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrSQHff6a1rMqBdHFt+FwIbytMZ+hJKN3KLkTtOWtSvNIriGhnTdn4rs+tjD/w+z+revytyWnMDM9dS7J8vQi006B16+hc9Xf82crqRoPRDnBytgAFFQY1G/55ql2zdfsC5yvpDOFzuwIJq5dNGsojS82t6HNmmKPq130fzsenFnj5v1pl3OJvk513oduUyKiZBGTroWTn7H/eOPtu7s9MD7pAdEjqYKFLeaKmyidiLmLqQlCRj3Tl2U9oyFg4PYNc0bL5FZJ/Z6t0Ds3i/a2RanQiKxrvgu3GSnUKMx7WIX373baL4jeM7cprRGiOY/1NcS+1cAjfJ8oaxQF/1dYj" } let(:ssh_key) { "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrSQHff6a1rMqBdHFt+FwIbytMZ+hJKN3KLkTtOWtSvNIriGhnTdn4rs+tjD/w+z+revytyWnMDM9dS7J8vQi006B16+hc9Xf82crqRoPRDnBytgAFFQY1G/55ql2zdfsC5yvpDOFzuwIJq5dNGsojS82t6HNmmKPq130fzsenFnj5v1pl3OJvk513oduUyKiZBGTroWTn7H/eOPtu7s9MD7pAdEjqYKFLeaKmyidiLmLqQlCRj3Tl2U9oyFg4PYNc0bL5FZJ/Z6t0Ds3i/a2RanQiKxrvgu3GSnUKMx7WIX373baL4jeM7cprRGiOY/1NcS+1cAjfJ8oaxQF/1dYj" }
......
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