main: # 'main' is the GitLab 'provider ID' of this LDAP server
label: 'LDAP'
host: 'ldap.example.com'
group_base: 'cn=my_group,ou=groups,dc=example,dc=com'
admin_group: 'my_admin_group'
The following allows you to perform a search in LDAP using the rails console.
Depending on what you’re trying to do, it may make more sense to query a user
or a group directly, or even use ldapsearch
instead.
adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain')
options = {
# :base is required
# use .base or .group_base
base: adapter.config.group_base,
# :filter is optional
# 'cn' looks for all "cn"s under :base
# '*' is the search string - here, it's a wildcard
filter: Net::LDAP::Filter.eq('cn', '*'),
# :attributes is optional
# the attributes we want to get returned
attributes: %w(dn cn memberuid member submember uniquemember memberof)
adapter.ldap_search(options)
When using OIDs in the filter, replace Net::LDAP::Filter.eq
with Net::LDAP::Filter.construct
:
adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain')
options = {
# :base is required
# use .base or .group_base
base: adapter.config.base,
# :filter is optional
# This filter includes OID 1.2.840.113556.1.4.1941
# It will search for all direct and nested members of the group gitlab_grp in the LDAP directory
filter: Net::LDAP::Filter.construct("(memberOf:1.2.840.113556.1.4.1941:=CN=gitlab_grp,DC=example,DC=com)"),
# :attributes is optional
# the attributes we want to get returned
attributes: %w(dn cn memberuid member submember uniquemember memberof)
adapter.ldap_search(options)
For examples of how this is run,
review the Adapter
module.
User sign-ins
If you’ve confirmed that a connection to LDAP can be
established but GitLab doesn’t show you LDAP users in the output, one of the
following is most likely true:
The bind_dn
user doesn’t have enough permissions to traverse the user tree.
The users don’t fall under the configured base
.
The configured user_filter
blocks access to the users.
In this case, you con confirm which of the above is true using
ldapsearch with the existing LDAP configuration in your
/etc/gitlab/gitlab.rb
.
Does the user fall under the configured base
in
LDAP? The user must fall under this base
to sign in.
Does the user pass through the configured user_filter
?
If one is not configured, this question can be ignored. If it is, then the
user must also pass through this filter to be allowed to sign in.
Refer to our documentation on debugging the user_filter
.
If the above are both okay, the next place to look for the problem is
the logs themselves while reproducing the issue.
Ask the user to sign in and let it fail.
Look through the output for any errors or other
messages about the sign-in. You may see one of the other error messages on
this page, in which case that section can help resolve the issue.
If the logs don’t lead to the root of the problem, use the
rails console to query this user
to see if GitLab can read this user on the LDAP server.
It can also be helpful to
debug a user sync to
investigate further.
Run an LDAP check command to make sure that the LDAP settings
are correct and GitLab can see your users.
Access denied for your LDAP account
There is a bug that
may affect users with Auditor level access. When
downgrading from Premium/Ultimate, Auditor users who try to sign in
may see the following message: Access denied for your LDAP account
.
We have a workaround, based on toggling the access level of affected users:
On the left sidebar, at the bottom, select Admin Area.
Select Overview > Users.
Select the name of the affected user.
In the upper-right corner, select Edit.
Change the user’s access level from Regular
to Administrator
(or vice versa).
At the bottom of the page, select Save changes.
In the upper-right corner, select Edit again.
Restore the user’s original access level (Regular
or Administrator
)
and select Save changes again.
The user should now be able to sign in.
Email has already been taken
A user tries to sign in with the correct LDAP credentials, is denied access,
and the production.log shows an error that looks like this:
(LDAP) Error saving user <USER DN> (email@example.com): ["Email has already been taken"]
This error is referring to the email address in LDAP, email@example.com
. Email
addresses must be unique in GitLab and LDAP links to a user’s primary email (as opposed
to any of their possibly-numerous secondary emails). Another user (or even the
same user) has the email email@example.com
set as a secondary email, which
is throwing this error.
We can check where this conflicting email address is coming from using the
rails console. In the console, run the following:
# This searches for an email among the primary AND secondary emails
user = User.find_by_any_email('email@example.com')
user.username
This shows you which user has this email address. One of two steps must be taken here:
To create a new GitLab user/username for this user when signing in with LDAP,
remove the secondary email to remove the conflict.
To use an existing GitLab user/username for this user to use with LDAP,
remove this email as a secondary email and make it a primary one so GitLab
associates this profile to the LDAP identity.
The user can do either of these steps
in their profile or an administrator can do it.
Check, for example, the Default projects limit or Allowed domains for sign-ups
fields and ensure that a relevant value is configured.
ldapsearch
allows you to test your configured
user filter
to confirm that it returns the users you expect it to return.
ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$base" "$user_filter" sAMAccountName
Variables beginning with a $
refer to a variable from the LDAP section of
your configuration file.
Replace ldaps://
with ldap://
if you are using the plain authentication method.
Port 389
is the default ldap://
port and 636
is the default ldaps://
port.
We are assuming the password for the bind_dn
user is in bind_dn_password.txt
.
Sync all users
The output from a manual user sync can show you what happens when
GitLab tries to sync its users against LDAP. Enter the rails console
and then run:
Rails.logger.level = Logger::DEBUG
LdapSyncWorker.new.perform
Next, learn how to read the output.
Example console output after a user sync
The output from a manual user sync is very verbose, and a
single user’s successful sync can look like this:
Syncing user John, email@example.com
Identity Load (0.9ms) SELECT "identities".* FROM "identities" WHERE "identities"."user_id" = 20 AND (provider LIKE 'ldap%') LIMIT 1
Instantiating Gitlab::Auth::Ldap::Person with LDIF:
dn: cn=John Smith,ou=people,dc=example,dc=com
cn: John Smith
mail: email@example.com
memberof: cn=admin_staff,ou=people,dc=example,dc=com
uid: John
UserSyncedAttributesMetadata Load (0.9ms) SELECT "user_synced_attributes_metadata".* FROM "user_synced_attributes_metadata" WHERE "user_synced_attributes_metadata"."user_id" = 20 LIMIT 1
(0.3ms) BEGIN
Namespace Load (1.0ms) SELECT "namespaces".* FROM "namespaces" WHERE "namespaces"."owner_id" = 20 AND "namespaces"."type" IS NULL LIMIT 1
Route Load (0.8ms) SELECT "routes".* FROM "routes" WHERE "routes"."source_id" = 27 AND "routes"."source_type" = 'Namespace' LIMIT 1
Ci::Runner Load (1.1ms) SELECT "ci_runners".* FROM "ci_runners" INNER JOIN "ci_runner_namespaces" ON "ci_runners"."id" = "ci_runner_namespaces"."runner_id" WHERE "ci_runner_namespaces"."namespace_id" = 27
(0.7ms) COMMIT
(0.4ms) BEGIN
Route Load (0.8ms) SELECT "routes".* FROM "routes" WHERE (LOWER("routes"."path") = LOWER('John'))
Namespace Load (1.0ms) SELECT "namespaces".* FROM "namespaces" WHERE "namespaces"."id" = 27 LIMIT 1
Route Exists (0.9ms) SELECT 1 AS one FROM "routes" WHERE LOWER("routes"."path") = LOWER('John') AND "routes"."id" != 50 LIMIT 1
User Update (1.1ms) UPDATE "users" SET "updated_at" = '2019-10-17 14:40:59.751685', "last_credential_check_at" = '2019-10-17 14:40:59.738714' WHERE "users"."id" = 20
There’s a lot here, so let’s go over what could be helpful when debugging.
First, GitLab looks for all users that have previously
signed in with LDAP and iterate on them. Each user’s sync starts with
the following line that contains the user’s username and email, as they
exist in GitLab now:
Syncing user John, email@example.com
If you don’t find a particular user’s GitLab email in the output, then that
user hasn’t signed in with LDAP yet.
Next, GitLab searches its identities
table for the existing
link between this user and the configured LDAP providers:
Identity Load (0.9ms) SELECT "identities".* FROM "identities" WHERE "identities"."user_id" = 20 AND (provider LIKE 'ldap%') LIMIT 1
The identity object has the DN that GitLab uses to look for the user
in LDAP. If the DN isn’t found, the email is used instead. We can see that
this user is found in LDAP:
Instantiating Gitlab::Auth::Ldap::Person with LDIF:
dn: cn=John Smith,ou=people,dc=example,dc=com
cn: John Smith
mail: email@example.com
memberof: cn=admin_staff,ou=people,dc=example,dc=com
uid: John
If the user wasn’t found in LDAP with either the DN or email, you may see the
following message instead:
LDAP search error: No Such Object
…in which case the user is blocked:
User Update (0.4ms) UPDATE "users" SET "state" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["state", "ldap_blocked"], ["updated_at", "2019-10-18 15:46:22.902177"], ["id", 20]]
After the user is found in LDAP, the rest of the output updates the GitLab
database with any changes.