Gitlab LDAP authentication without querying account or anonymous binding

Note: This tutorial was last tested with gitlab 8.5 installed from source.

This tutorial aims to describe how to modify a Gitlab installation to use the users credentials to authenticate with the LDAP server. By default Gitlab relies on anonymous binding or a special querying user to ask the LDAP server about the existence of a user before authenticating her with her own credentials. For security reasons, however, many administrators disable anonymous binding and forbid the creation of special querying LDAP users.

In this tutorial we assume that we have a gitlab setup at gitlab.example.com and an LDAP server running on ldap.example.com, and users have a DN of the following form: CN=username,OU=Users,OU=division,OU=department,DC=example,DC=com.

Patching

To make Gitlab work in such cases we need to partly modify its authentication mechanism regarding LDAP.

First, we replace the omniauth-ldap module with this derivation. To achieve this we apply the following patch to gitlab/Gemfile:

diff --git a/Gemfile b/Gemfile
index 1171eeb..f25bc60 100644
--- a/Gemfile
+++ b/Gemfile
@@ -44,4 +44,5 @@ gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
 # LDAP Auth
 # GitLab fork with several improvements to original library. For full list of changes
 # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
-gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
+#gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', :git => 'https://github.com/zakkak/omniauth-ldap.git', require: 'net-ldap', require: "omniauth-ldap"

Now, we need to perform the following actions:

  1. sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
  2. sudo -u git -H bundle install --deployment --without development test mysql aws

These commands will fetch the modified omniauth-ldap module in gitlab/vendor/bundle/ruby/2.x.x/bundler/gems. Now that the module is fetched, we need to modify it to use the DN our LDAP server expects. We achieve this by patching lib/omniauth/strategies/ldap.rb in gitlab/vendor/bundle/ruby/2.x.x/bundler/gems/omniauth-ldap with:

diff --git a/lib/omniauth-ldap/adaptor.rb b/lib/omniauth-ldap/adaptor.rb
index d4fb678..e9615ae 100644
--- a/lib/omniauth-ldap/adaptor.rb
+++ b/lib/omniauth-ldap/adaptor.rb
@@ -41,7 +41,7 @@ module OmniAuth
         Adaptor.validate(configuration)
         @configuration = configuration.dup
         # The HACK!  FIXME: do it in a more generic/configurable way
-        @configuration[:bind_dn]  = "CN=#{@configuration[:login]},OU=Test,DC=my,DC=example,DC=com"
+        @configuration[:bind_dn]  = "CN=#{@configuration[:login]},OU=Users,OU=division,OU=department,DC=example,DC=com"
         @configuration[:allow_anonymous] ||= false
         @logger = @configuration.delete(:logger)
         VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|

With this module, gitlab uses the user's credentials to bind to the LDAP server and query it, as well as, to authenticate the user herself.

This however will only work as long as the users do not use ssh-keys to authenticate with Gitlab. When authenticating through an ssh-key, by default Gitlab queries the LDAP server to find out whether the corresponding user is (still) a valid user or not. At this point, we cannot use the user credentials to query the LDAP server, since the user did not provide them to us. As a result we disable this mechanism, essentially allowing users with registered ssh-keys but removed from the LDAP server to still use our Gitlab setup. To prevent such users from being able to still use your Gitlab setup, you will have to manually delete their ssh-keys from any accounts in your setup.

To disable this mechanism we patch gitlab/lib/gitlab/ldap/access.rb with:

diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 16ff03c..9ebaeb6 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -14,15 +14,16 @@ module Gitlab
       end

       def self.allowed?(user)
-        self.open(user) do |access|
-          if access.allowed?
-            user.last_credential_check_at = Time.now
-            user.save
-            true
-          else
-            false
-          end
-        end
+        true
+        # self.open(user) do |access|
+        #   if access.allowed?
+        #     user.last_credential_check_at = Time.now
+        #     user.save
+        #     true
+        #   else
+        #     false
+        #   end
+        # end
       end

       def initialize(user, adapter=nil)
@@ -32,20 +33,21 @@ module Gitlab
       end

def allowed?
-        if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
-          return true unless ldap_config.active_directory
+        true
+        # if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
+        #   return true unless ldap_config.active_directory

-          # Block user in GitLab if he/she was blocked in AD
-          if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
-            user.block unless user.blocked?
-            false
-          else
-            user.activate if user.blocked? && !ldap_config.block_auto_created_users
-            true
-          end
-        else
-          false
-        end
+        #   # Block user in GitLab if he/she was blocked in AD
+        #   if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
+        #     user.block unless user.blocked?
+        #     false
+        #   else
+        #     user.activate if user.blocked? && !ldap_config.block_auto_created_users
+        #     true
+        #   end
+        # else
+        #   false
+        # end
rescue
false
end

Note that to enable clone/push/pull over https we also need to patch lib/gitlab/ldap/authentication.rb with:

diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb
index bad683c..1818cf0 100644
--- a/lib/gitlab/ldap/authentication.rb
+++ b/lib/gitlab/ldap/authentication.rb
@@ -34,15 +34,18 @@ module Gitlab
       end

       def login(login, password)
-        @ldap_user = adapter.bind_as(
+        @ldap_user = adapter(login, password).bind_as(
           filter: user_filter(login),
           size: 1,
           password: password
         )
       end

-      def adapter
-        OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys)
+      def adapter(login, password)
+        options = config.options.symbolize_keys
+        options[:login]    = login
+        options[:password] = password
+        OmniAuth::LDAP::Adaptor.new(options)
       end

       def config

Configuration

In gitlab.yml use something like the following (modify to your needs):

  #
  # 2. Auth settings
  # ==========================

  ## LDAP settings
  # You can inspect a sample of the LDAP users with login access by running:
  #   bundle exec rake gitlab:ldap:check RAILS_ENV=production
  ldap:
    enabled: true
    servers:
      ##########################################################################
      #
      # Since GitLab 7.4, LDAP servers get ID's (below the ID is 'main'). GitLab
      # Enterprise Edition now supports connecting to multiple LDAP servers.
      #
      # If you are updating from the old (pre-7.4) syntax, you MUST give your
      # old server the ID 'main'.
      #
      ##########################################################################
      main: # 'main' is the GitLab 'provider ID' of this LDAP server
        ## label
        #
        # A human-friendly name for your LDAP server. It is OK to change the label later,
        # for instance if you find out it is too large to fit on the web page.
        #
        # Example: 'Paris' or 'Acme, Ltd.'
        label: 'LDAP_EXAMPLE_COM'

        host: ldap.example.com
        port: 636
        uid: 'sAMAccountName'
        method: 'ssl' # "tls" or "ssl" or "plain"
        bind_dn: ''
        password: ''

        # This setting specifies if LDAP server is Active Directory LDAP server.
        # For non AD servers it skips the AD specific queries.
        # If your LDAP server is not AD, set this to false.
        active_directory: true

        # If allow_username_or_email_login is enabled, GitLab will ignore everything
        # after the first '@' in the LDAP username submitted by the user on login.
        #
        # Example:
        # - the user enters '[email protected]' and 'p@ssw0rd' as LDAP credentials;
        # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
        #
        # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
        # disable this setting, because the userPrincipalName contains an '@'.
        allow_username_or_email_login: false

        # To maintain tight control over the number of active users on your GitLab installation,
        # enable this setting to keep new users blocked until they have been cleared by the admin
        # (default: false).
        block_auto_created_users: false

        # Base where we can search for users
        #
        #   Ex. ou=People,dc=gitlab,dc=example
        #
        base: 'OU=Users,OU=division,OU=department,DC=example,DC=com'

        # Filter LDAP users
        #
        #   Format: RFC 4515 http://tools.ietf.org/search/rfc4515
        #   Ex. (employeeType=developer)
        #
        #   Note: GitLab does not support omniauth-ldap's custom filter syntax.
        #
        user_filter: '(&(objectclass=user)(objectclass=person))'
Previous
Next