Help us learn about your current experience with the documentation. Take the survey.

LDAP 故障排查

  • 版本:免费版、高级版、旗舰版
  • 提供方式:GitLab 自托管版

如果您是管理员,请使用以下信息排查 LDAP 问题。

常见问题与工作流

连接问题

连接被拒绝

尝试连接 LDAP 服务器时若收到 Connection Refused 错误,请检查 GitLab 使用的 LDAP 端口加密 设置。常见组合为 encryption: 'plain'port: 389,或 encryption: 'simple_tls'port: 636

连接超时

若 GitLab 无法访问您的 LDAP 端点,会显示类似信息:

无法从 Ldapmain 认证您,因为 "连接超时 - 用户指定超时"。

若配置的 LDAP 提供商和/或端点离线或 GitLab 无法访问,则所有 LDAP 用户均无法认证登录。GitLab 不会在 LDAP 服务中断时缓存或存储 LDAP 用户凭据。

遇到此错误请联系您的 LDAP 提供商或管理员。

引用错误

日志中若出现 LDAP search error: Referral,或在排查 LDAP 组同步时遇到此错误,可能表示配置问题。LDAP 配置文件 /etc/gitlab/gitlab.rb(Omnibus)或 config/gitlab.yml(源码)为 YAML 格式且对缩进敏感。检查 group_baseadmin_group 配置键是否在服务器标识符后缩进 2 个空格。默认标识符为 main,示例片段如下:

main: # 'main' 是此 LDAP 服务器的 GitLab 'provider ID'
  label: 'LDAP'
  host: 'ldap.example.com'
  # ...
  group_base: 'cn=my_group,ou=groups,dc=example,dc=com'
  admin_group: 'my_admin_group'

查询 LDAP

  • 版本:高级版、旗舰版
  • 提供方式:GitLab 自托管版

以下代码可通过 rails 控制台执行 LDAP 搜索。根据您的需求,可能更适合直接查询 用户,甚至直接使用 ldapsearch

adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain')
options = {
    # :base 是必需的
    # 使用 .base 或 .group_base
    base: adapter.config.group_base,

    # :filter 是可选的
    # 'cn' 在 :base 下查找所有 "cn"
    # '*' 是搜索字符串 - 此处为通配符
    filter: Net::LDAP::Filter.eq('cn', '*'),

    # :attributes 是可选的
    # 需要返回的属性
    attributes: %w(dn cn memberuid member submember uniquemember memberof)
}
adapter.ldap_search(options)

在过滤器中使用 OID 时,将 Net::LDAP::Filter.eq 替换为 Net::LDAP::Filter.construct

adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain')
options = {
    # :base 是必需的
    # 使用 .base 或 .group_base
    base: adapter.config.base,

    # :filter 是可选的
    # 此过滤器包含 OID 1.2.840.113556.1.4.1941
    # 将搜索 LDAP 目录中组 gitlab_grp 的所有直接和嵌套成员
    filter: Net::LDAP::Filter.construct("(memberOf:1.2.840.113556.1.4.1941:=CN=gitlab_grp,DC=example,DC=com)"),

    # :attributes 是可选的
    # 需要返回的属性
    attributes: %w(dn cn memberuid member submember uniquemember memberof)
}
adapter.ldap_search(options)

运行示例参考 Adapter 模块

用户登录

未找到用户

已确认 可建立 LDAP 连接但 GitLab 未显示 LDAP 用户,可能原因如下:

  • bind_dn 用户权限不足,无法遍历用户树。
  • 用户不在配置的 base 下。
  • 配置的 user_filter 阻止用户访问。

可通过 ldapsearch 使用 /etc/gitlab/gitlab.rb 中的现有配置确认具体原因。

用户无法登录

用户登录失败可能有多种原因。请先自查以下问题:

  • 用户是否在 LDAP 中位于配置的 base 下?用户必须在此 base 下才能登录。
  • 用户是否通过配置的 [user_filter](_index.md#set-up-ldap-user-filter)?若未配置则忽略;若已配置,用户必须通过此过滤器才能登录。

若以上问题均正常,请重现问题并检查日志:

  • 让用户尝试登录并观察失败过程。
  • 查看输出 中是否有登录相关的错误信息。若出现本页其他错误信息,对应章节可帮助解决问题。

若日志未定位问题,使用 rails 控制台 查询该用户,检查 GitLab 能否读取 LDAP 服务器上的用户信息。

也可 调试用户同步 进一步排查。

用户看到 “无效登录或密码” 错误

若用户看到此错误,可能是因为他们使用了 标准 登录表单而非 LDAP 登录表单。

解决方法:请用户在 LDAP 登录表单中输入其 LDAP 用户名和密码。

登录凭据无效

若 LDAP 上的登录凭据正确,请确认以下条件:

LDAP 账户访问被拒绝

存在一个 缺陷,可能影响具有 审计员级别权限 的用户。从高级版/旗舰版降级后,审计员用户尝试登录时可能看到以下消息:Access denied for your LDAP account

临时解决方案:切换受影响用户的访问级别:

  1. 在左侧边栏底部选择 管理员
  2. 选择 概览 > 用户
  3. 选择受影响用户的名称。
  4. 在右上角选择 编辑
  5. 将用户访问级别从 常规 更改为 管理员(或反之)。
  6. 在页面底部选择 保存更改
  7. 再次在右上角选择 编辑
  8. 恢复用户原始访问级别(常规管理员)并再次选择 保存更改

用户现在应能登录。

邮箱已被占用

用户使用正确的 LDAP 凭据登录被拒绝,且 production.log 显示类似错误:

(LDAP) 保存用户 <用户 DN> ([email protected]) 时出错:["邮箱已被占用"]

此错误指 LDAP 中的邮箱地址 [email protected]。邮箱地址在 GitLab 中必须唯一,且 LDAP 关联用户的主邮箱(而非可能的多个次级邮箱)。另一个用户(甚至同一用户)将 [email protected] 设置为次级邮箱,导致此错误。

可通过 rails 控制台 检查冲突邮箱来源:

# 在主邮箱和次级邮箱中搜索
user = User.find_by_any_email('[email protected]')
user.username

此命令显示拥有该邮箱的用户。必须执行以下步骤之一:

  • 为该用户创建新的 GitLab 用户名:移除次级邮箱消除冲突。
  • 使用现有 GitLab 用户名:将该邮箱从次级改为主邮箱,使 GitLab 将其关联到 LDAP 身份。

用户或管理员可在 其个人资料 中执行这些操作。

项目限制错误

以下错误表示激活了限制但关联数据字段为空:

  • 项目限制不能为空
  • 项目限制不是数字

解决方法:

  1. 在左侧边栏底部选择 管理员
  2. 选择 设置 > 常规
  3. 展开以下两项:
    • 账户和限制
    • 注册限制
  4. 检查 默认项目限制注册允许域名 等字段,确保配置了有效值。

调试 LDAP 用户过滤器

ldapsearch 可测试配置的 用户过滤器,确认返回预期用户:

ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt  -b "$base" "$user_filter" sAMAccountName
  • $ 开头的变量指配置文件 LDAP 部分的变量。
  • 若使用纯认证方式,将 ldaps:// 替换为 ldap://。端口 389ldap:// 默认端口,636ldaps:// 默认端口。
  • 假设 bind_dn 用户密码在 bind_dn_password.txt 中。

同步所有用户

  • 版本:高级版、旗舰版
  • 提供方式:GitLab 自托管版

手动 用户同步 的输出可展示 GitLab 尝试同步用户与 LDAP 的过程。进入 rails 控制台 并运行:

Rails.logger.level = Logger::DEBUG

LdapSyncWorker.new.perform

接下来,学习如何解读输出

用户同步后的控制台输出示例
  • 版本:高级版、旗舰版
  • 提供方式:GitLab 自托管版

手动用户同步 的输出非常详细,单个用户成功同步可能如下:

同步用户 John, [email protected]
  身份加载 (0.9ms)  SELECT  "identities".* FROM "identities" WHERE "identities"."user_id" = 20 AND (provider LIKE 'ldap%') LIMIT 1
使用 LDIF 实例化 Gitlab::Auth::Ldap::Person:
dn: cn=John Smith,ou=people,dc=example,dc=com
cn: John Smith
mail: [email protected]
memberof: cn=admin_staff,ou=people,dc=example,dc=com
uid: John

  用户同步属性元数据加载 (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
  命名空间加载 (1.0ms)  SELECT  "namespaces".* FROM "namespaces" WHERE "namespaces"."owner_id" = 20 AND "namespaces"."type" IS NULL LIMIT 1
  路由加载 (0.8ms)  SELECT  "routes".* FROM "routes" WHERE "routes"."source_id" = 27 AND "routes"."source_type" = 'Namespace' LIMIT 1
  CI 运行器加载 (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
  路由加载 (0.8ms)  SELECT "routes".* FROM "routes" WHERE (LOWER("routes"."path") = LOWER('John'))
  命名空间加载 (1.0ms)  SELECT  "namespaces".* FROM "namespaces" WHERE "namespaces"."id" = 27 LIMIT 1
  路由存在 (0.9ms)  SELECT  1 AS one FROM "routes" WHERE LOWER("routes"."path") = LOWER('John') AND "routes"."id" != 50 LIMIT 1
  用户更新 (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

输出信息量大,调试时重点关注:

  1. GitLab 首先查找所有曾通过 LDAP 登录的用户并遍历。每个用户同步以包含用户名和当前 GitLab 邮箱的行开始:
同步用户 John, [email protected]

若输出中未找到特定用户的 GitLab 邮箱,说明该用户尚未通过 LDAP 登录。

  1. GitLab 在 identities 表中查找用户与配置的 LDAP 提供商之间的现有链接:
  身份加载 (0.9ms)  SELECT  "identities".* FROM "identities" WHERE "identities"."user_id" = 20 AND (provider LIKE 'ldap%') LIMIT 1

身份对象包含 GitLab 在 LDAP 中查找用户时使用的 DN。若未找到 DN,则使用邮箱。此示例显示用户在 LDAP 中找到:

使用 LDIF 实例化 Gitlab::Auth::Ldap::Person:
dn: cn=John Smith,ou=people,dc=example,dc=com
cn: John Smith
mail: [email protected]
memberof: cn=admin_staff,ou=people,dc=example,dc=com
uid: John

若用户通过 DN 或邮箱均未在 LDAP 中找到,可能显示:

LDAP 搜索错误:无此类对象

此时用户被阻止:

  用户更新 (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]]

找到用户后,其余输出更新 GitLab 数据库中的变更。

查询 LDAP 中的用户

此操作测试 GitLab 能否访问 LDAP 并读取特定用户,可暴露连接和/或查询 LDAP 的潜在错误(这些错误在 GitLab UI 中可能静默失败):

Rails.logger.level = Logger::DEBUG

adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain') # 若 `main` 是 LDAP 提供商
Gitlab::Auth::Ldap::Person.find_by_uid('<uid>', adapter)

组成员资格

  • 版本:高级版、旗舰版
  • 提供方式:GitLab 自托管版

未授予成员资格

有时您认为特定用户应通过 LDAP 组同步添加到 GitLab 组,但未发生。可检查以下内容调试:

  • 确保 LDAP 配置指定了 group_base此配置 是组同步正常工作的必要条件。
  • 确保已将正确的 LDAP 组链接添加到 GitLab 组
  • 检查用户是否有 LDAP 身份:
    1. 以管理员身份登录 GitLab。
    2. 在左侧边栏底部选择 管理员
    3. 在左侧边栏选择 概览 > 用户
    4. 搜索用户。
    5. 选择用户名称打开其信息(不要选择 编辑)。
    6. 选择 身份 标签。应有包含 LDAP DN 作为 标识符 的 LDAP 身份。若无,说明用户尚未通过 LDAP 登录,必须先登录。
  • 您已等待一小时或 配置的间隔 进行组同步。为加快进程,可前往 GitLab 组 管理 > 成员 并按 立即同步(同步单个组),或 运行组同步 Rake 任务(同步所有组)。

若所有检查正常,可在 rails 控制台中深入调试:

  1. 进入 rails 控制台
  2. 选择一个已配置 LDAP 组链接的 GitLab 组进行测试。
  3. 启用调试日志,找到选定的 GitLab 组并 与 LDAP 同步
  4. 查看同步输出。参考 示例日志输出 了解如何解读。
  5. 若仍无法确定用户未被添加的原因,直接查询 LDAP 组 查看成员列表。
  6. 用户的 DN 或 UID 是否在查询组的列表中?此处某个 DN 或 UID 应与之前检查的 LDAP 身份中的 标识符 匹配。若不匹配,用户似乎不在 LDAP 组中。

启用 LDAP 同步时无法将服务账户用户添加到组

当组启用 LDAP 同步时,无法使用 “邀请” 对话框邀请新组成员。

在 GitLab 16.8 及更高版本中,可通过 组成员 API 端点 邀请和移除服务账户。

未授予管理员权限

管理员同步 已配置但用户未获得正确管理员权限时,确认以下条件:

  • 同时配置了 group_base
  • gitlab.rb 中的 admin_group 是 CN(而非 DN 或数组)。
  • 此 CN 位于配置的 group_base 范围内。
  • admin_group 的成员已使用 LDAP 凭据登录 GitLab。GitLab 仅向已连接到 LDAP 的用户授予管理员权限。

若以上条件均满足但用户仍未获得权限,在 rails 控制台中 运行手动组同步查看输出 了解 GitLab 同步 admin_group 时的行为。

UI 中 “立即同步” 按钮卡住

管理 > 成员 页面的 立即同步 按钮可能卡住。按钮在点击并刷新页面后卡住,无法再次选择。

按钮卡住可能有多种原因,需针对具体案例调试。以下是两种可能原因及解决方案:

无效成员资格

若组中某些成员或请求成员无效,立即同步 按钮可能卡住。可跟踪改进此问题可见性的 相关 issue。使用 Rails 控制台 确认是否导致按钮卡住:

# 查找相关组
group = Group.find_by(name: 'my_gitlab_group')

# 检查组本身的错误
group.valid?
group.errors.map(&:full_messages)

# 检查组成员和请求者的错误
group.requesters.map(&:valid?)
group.requesters.map(&:errors).map(&:full_messages)
group.members.map(&:valid?)
group.members.map(&:errors).map(&:full_messages)

显示的错误可定位问题并提供解决方案。例如,支持团队曾看到以下错误:

irb(main):018:0> group.members.map(&:errors).map(&:full_messages)
=> [["成员的邮箱地址不允许加入此组。请前往组的 **设置 > 常规** 页面,检查 **按邮箱域名限制成员**。"]]

此错误表明管理员选择了 按邮箱域名限制组访问,但域名有拼写错误。修复域名设置后,立即同步 按钮恢复正常。

Sidekiq 节点上缺少 LDAP 配置

当 GitLab 跨多个节点扩展且 运行 Sidekiq 的节点上缺少 /etc/gitlab/gitlab.rb 中的 LDAP 配置 时,立即同步 按钮可能卡住。此时 Sidekiq 任务似乎消失。

Sidekiq 节点需要 LDAP,因为 LDAP 有多个异步运行的任务,需要本地 LDAP 配置:

可通过在每个运行 Sidekiq 的节点上运行 LDAP 检查 Rake 任务 测试是否缺少 LDAP 配置。若节点上 LDAP 配置正确,将连接到 LDAP 服务器并返回用户。

解决方案:在 Sidekiq 节点上 配置 LDAP。配置后运行 LDAP 检查 Rake 任务 确认 GitLab 节点可连接 LDAP。

同步所有组

调试时无需手动同步所有组,请改用 Rake 任务

手动 组同步 的输出可展示 GitLab 同步 LDAP 组成员资格的过程。进入 rails 控制台 并运行:

Rails.logger.level = Logger::DEBUG

LdapAllGroupsSyncWorker.new.perform

接下来,学习如何解读输出

组同步后的控制台输出示例

与用户同步输出类似,手动组同步 的输出也非常详细,包含大量有用信息。

同步实际开始的标志:

开始同步 'ldapmain' 提供商的 'my_group'

以下条目显示 GitLab 在 LDAP 服务器中看到的所有用户 DN。这些 DN 是单个 LDAP 组的用户,而非 GitLab 组。若此 GitLab 组链接到多个 LDAP 组,会看到多个类似条目(每个 LDAP 组一个)。若此日志条目中未看到 LDAP 用户 DN,说明 LDAP 在查找时未返回该用户。请验证用户是否确实在 LDAP 组中。

'ldap_group_1' LDAP 组中的成员:["uid=john0,ou=people,dc=example,dc=com",
"uid=mary0,ou=people,dc=example,dc=com", "uid=john1,ou=people,dc=example,dc=com",
"uid=mary1,ou=people,dc=example,dc=com", "uid=john2,ou=people,dc=example,dc=com",
"uid=mary2,ou=people,dc=example,dc=com", "uid=john3,ou=people,dc=example,dc=com",
"uid=mary3,ou=people,dc=example,dc=com", "uid=john4,ou=people,dc=example,dc=com",
"uid=mary4,ou=people,dc=example,dc=com"]

每个条目后不久,会看到已解析成员访问级别的哈希值。此哈希值表示 GitLab 认为应有权访问该组的所有用户 DN 及其访问级别(角色)。此哈希值是累加的,可能根据额外的 LDAP 组查找添加更多 DN 或修改现有条目。最后一个条目应准确指示 GitLab 认为应添加到组中的用户。

10 为 访客,20 为 报告者,30 为 开发者,40 为 维护者,50 为 所有者

已解析 'my_group' 组成员访问:{"uid=john0,ou=people,dc=example,dc=com"=>30,
"uid=mary0,ou=people,dc=example,dc=com"=>30, "uid=john1,ou=people,dc=example,dc=com"=>30,
"uid=mary1,ou=people,dc=example,dc=com"=>30, "uid=john2,ou=people,dc=example,dc=com"=>30,
"uid=mary2,ou=people,dc=example,dc=com"=>30, "uid=john3,ou=people,dc=example,dc=com"=>30,
"uid=mary3,ou=people,dc=example,dc=com"=>30, "uid=john4,ou=people,dc=example,dc=com"=>30,
"uid=mary4,ou=people,dc=example,dc=com"=>30}

看到类似以下警告很常见。这些警告表示 GitLab 本会添加用户到组,但无法在 GitLab 中找到该用户。通常无需担心。

若认为特定用户应已存在于 GitLab 中,但看到此条目,可能是 GitLab 中存储的 DN 不匹配。参考 用户 DN 和邮箱已更改 更新用户的 LDAP 身份。

用户 DN `uid=john0,ou=people,dc=example,dc=com` 应有权访问 'my_group' 组,
但 GitLab 中无此身份的用户。用户首次登录时将更新成员资格。

最后,以下条目表示该组同步完成:

完成所有提供商的 'my_group' 组同步

当所有配置的组链接同步后,GitLab 会查找管理员或外部用户进行同步:

同步 'ldapmain' 提供商的管理员用户

输出与单个组同步类似,以下行表示同步完成:

完成 'ldapmain' 提供商的管理员用户同步

若未配置 管理员同步,会显示:

未为 'ldapmain' 提供商配置 'admin_group'。跳过

同步单个组

同步所有组 可能产生大量输出,干扰排查单个 GitLab 组成员资格。此时可仅同步该组并查看调试输出:

Rails.logger.level = Logger::DEBUG

# 查找 GitLab 组。
# 若输出为 `nil`,则未找到组。
# 若输出包含大量组属性,则成功找到组。
group = Group.find_by(name: 'my_gitlab_group')

# 将此组与 LDAP 同步
EE::Gitlab::Auth::Ldap::Sync::Group.execute_all_providers(group)

输出与 同步所有组 类似。

查询 LDAP 中的组

当需要确认 GitLab 能读取 LDAP 组及其所有成员时,可运行:

# 查找适配器和组本身
adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain') # 若 `main` 是 LDAP 提供商
ldap_group = EE::Gitlab::Auth::Ldap::Group.find_by_cn('group_cn_here', adapter)

# 查找 LDAP 组的成员
ldap_group.member_dns
ldap_group.member_uids

LDAP 同步未移除组创建者

LDAP 同步 应移除 LDAP 组的创建者(若该用户不在组中)。若运行 LDAP 同步未执行此操作:

  1. 将用户添加到 LDAP 组。
  2. 等待 LDAP 组同步完成。
  3. 从 LDAP 组中移除用户。

用户 DN 和邮箱已更改

若 LDAP 中用户的主邮箱 DN 均更改,GitLab 无法识别正确的 LDAP 记录,从而阻止该用户。为使 GitLab 能找到 LDAP 记录,请使用以下至少一项更新用户现有 GitLab 资料:

  • 新的主邮箱。
  • DN 值。

以下脚本更新所有提供用户的邮箱,避免其被阻止或无法访问账户。

以下脚本要求先删除具有新邮箱地址的任何新账户。邮箱地址在 GitLab 中必须唯一。

进入 rails 控制台 并运行:

# 每条记录必须包含旧用户名和新邮箱
emails = {
  '原始用户名' => '新邮箱地址',
  ...
}

emails.each do |username, email|
  user = User.find_by_username(username)
  user.email = email
  user.skip_reconfirmation!
  user.save!
end

然后可运行 用户同步 同步这些用户的最新 DN。

无法从 AzureActivedirectoryV2 认证,因为 “无效授权”

从 LDAP 转换为 SAML 时,Azure 中可能出现以下错误:

认证失败!invalid_credentials: OAuth2::Error, invalid_grant.

当以下两个条件同时满足时会发生此问题:

  • 用户配置 SAML 后仍存在 LDAP 身份。
  • 您为这些用户禁用了 LDAP。

日志中会同时包含 LDAP 和 Azure 元数据,导致 Azure 中出现错误。

单个用户的解决方法是:在 管理员 > 身份 中移除用户的 LDAP 身份。

要移除多个 LDAP 身份,使用 无法从 Ldapmain 认证,因为 "未知提供商" 错误 的任一临时解决方案。

无法从 Ldapmain 认证,因为 “未知提供商”

使用 LDAP 服务器认证时可能收到以下错误:

无法从 Ldapmain 认证,因为 "未知提供商 (ldapsecondary)。可用提供商:["ldapmain"]"。

此错误由以下情况引起:使用曾通过已重命名或从 GitLab 配置中移除的 LDAP 服务器认证的账户。例如:

  • 初始时,GitLab 配置中的 ldap_servers 设置了 mainsecondary
  • 移除或重命名 secondarymain
  • 尝试登录的用户拥有 secondaryidentity 记录,但该配置已不存在。

使用 Rails 控制台 列出受影响用户并检查其 LDAP 服务器身份:

ldap_identities = Identity.where(provider: "ldapsecondary")
ldap_identities.each do |identity|
  u=User.find_by_id(identity.user_id)
  ui=Identity.where(user_id: identity.user_id)
  puts "用户: #{u.username}\n   #{u.email}\n   最后活动时间: #{u.last_activity_on}\n   #{identity.provider} ID: #{identity.id} 外部: #{identity.extern_uid}"
  puts "   所有身份:"
  ui.each do |alli|
    puts "    - #{alli.provider} ID: #{alli.id} 外部: #{alli.extern_uid}"
  end
end;nil

可通过两种方式解决此错误。

重命名 LDAP 服务器引用

当 LDAP 服务器互为副本,且受影响用户应能通过配置的 LDAP 服务器登录时,此方案适用。例如,若现在使用负载均衡器管理 LDAP 高可用性,不再需要单独的次要登录选项。

若 LDAP 服务器互不为副本,此方案将阻止受影响用户登录。

重命名不再配置的 LDAP 服务器引用,运行:

sudo gitlab-rake gitlab:ldap:rename_provider[ldapsecondary,ldapmain]

移除与已移除 LDAP 服务器相关的 identity 记录

前提条件:

  • 确保 auto_link_ldap_user 已启用。

此方案下,删除身份后,受影响用户可通过配置的 LDAP 服务器登录,GitLab 会创建新的 identity 记录。

由于移除的 LDAP 服务器是 ldapsecondary,在 Rails 控制台 中删除所有 ldapsecondary 身份:

ldap_identities = Identity.where(provider: "ldapsecondary")
ldap_identities.each do |identity|
  puts "删除身份: #{identity.id} #{identity.provider}: #{identity.extern_uid}"
  identity.destroy!
rescue => e
  puts '删除身份时生成错误:\n ' + e.to_s
end; nil

过期许可证导致多 LDAP 服务器错误

使用 多个 LDAP 服务器 需要有效许可证。过期许可证可能导致:

  • Web 界面出现 502 错误。
  • 日志中出现以下错误(实际策略名称取决于 /etc/gitlab/gitlab.rb 中配置的名称):
    无法找到名称为 `Ldapsecondary' 的策略。请确保需要它或使用 :strategy_class 选项显式设置。(Devise::OmniAuth::StrategyNotFound)

解决此错误,必须在不使用 Web 界面的情况下为 GitLab 实例应用新许可证:

  1. 移除或注释掉所有非主要 LDAP 服务器的 GitLab 配置行。
  2. 重新配置 GitLab 使其临时仅使用一个 LDAP 服务器。
  3. 进入 Rails 控制台并添加许可证密钥
  4. 在 GitLab 配置中重新启用额外的 LDAP 服务器并再次重新配置 GitLab。

用户被反复移除和添加到组

若用户在组同步中被添加,又在下一次同步中被移除,且反复发生,请确保用户没有多个或冗余的 LDAP 身份。

若其中某个身份是为不再使用的旧 LDAP 提供商添加的,移除与已移除 LDAP 服务器相关的 identity 记录

调试工具

LDAP 检查

LDAP 检查 Rake 任务 是帮助确定 GitLab 能否成功连接 LDAP 并读取用户的重要工具。

若无法建立连接,可能是配置问题或防火墙阻止连接:

  • 确保没有防火墙阻止连接,且 LDAP 服务器可被 GitLab 主机访问。
  • 查看 Rake 检查输出中的错误信息,可能指向您的 LDAP 配置,确认配置值(特别是 hostportbind_dnpassword)是否正确。
  • 查看 日志 中的 错误 进一步调试连接失败。

若 GitLab 成功连接 LDAP 但未返回用户,参考未找到用户时的处理方法

GitLab 日志

若用户账户因 LDAP 配置被阻止或解除阻止,消息会 记录到 application_json.log

若 LDAP 查找期间出现意外错误(配置错误、超时),登录被拒绝并记录消息到 production.log

ldapsearch

ldapsearch 是允许查询 LDAP 服务器的工具。可用于测试 LDAP 设置并确保设置获得预期结果。

使用 ldapsearch 时,请使用已在 gitlab.rb 配置中指定的相同设置,以确认使用这些确切设置时的行为。

在 GitLab 主机上运行此命令也有助于确认 GitLab 主机和 LDAP 之间无障碍。

例如,考虑以下 GitLab 配置:

gitlab_rails['ldap_servers'] = YAML.load <<-'EOS' # 记得在下面用 'EOS' 关闭此块
   main: # 'main' 是此 LDAP 服务器的 GitLab 'provider ID'
     label: 'LDAP'
     host: '127.0.0.1'
     port: 389
     uid: 'uid'
     encryption: 'plain'
     bind_dn: 'cn=admin,dc=ldap-testing,dc=example,dc=com'
     password: 'Password1'
     active_directory: true
     allow_username_or_email_login: false
     block_auto_created_users: false
     base: 'dc=ldap-testing,dc=example,dc=com'
     user_filter: ''
     attributes:
       username: ['uid', 'userid', 'sAMAccountName']
       email:    ['mail', 'email', 'userPrincipalName']
       name:       'cn'
       first_name: 'givenName'
       last_name:  'sn'
     group_base: 'ou=groups,dc=ldap-testing,dc=example,dc=com'
     admin_group: 'gitlab_admin'
EOS

您将运行以下 ldapsearch 查找 bind_dn 用户:

ldapsearch -D "cn=admin,dc=ldap-testing,dc=example,dc=com" \
  -w Password1 \
  -p 389 \
  -h 127.0.0.1 \
  -b "dc=ldap-testing,dc=example,dc=com"

bind_dnpasswordporthostbase 均与 gitlab.rb 中的配置一致。

使用 start_tls 加密的 ldapsearch

上述示例在端口 389 上执行明文 LDAP 测试。若使用 start_tls 加密,在 ldapsearch 命令中包含:

  • -Z 标志。
  • LDAP 服务器的 FQDN。

必须包含这些,因为 TLS 协商期间会评估 LDAP 服务器的 FQDN 与其证书的匹配:

ldapsearch -D "cn=admin,dc=ldap-testing,dc=example,dc=com" \
  -w Password1 \
  -p 389 \
  -h "testing.ldap.com" \
  -b "dc=ldap-testing,dc=example,dc=com" -Z

使用 simple_tls 加密的 ldapsearch

若使用 simple_tls 加密(通常在端口 636),在 ldapsearch 命令中包含:

  • 使用 -H 标志和端口指定 LDAP 服务器 FQDN。
  • 完整构造的 URI。
ldapsearch -D "cn=admin,dc=ldap-testing,dc=example,dc=com" \
  -w Password1 \
  -H "ldaps://testing.ldap.com:636" \
  -b "dc=ldap-testing,dc=example,dc=com"

更多信息请参阅官方 ldapsearch 文档

使用 AdFind (Windows)

您可使用 AdFind 工具(基于 Windows 的系统)测试 LDAP 服务器是否可访问且认证是否正常工作。AdFind 是由 Joe Richards 构建的免费工具。

返回所有对象 可使用过滤器 objectclass=* 返回所有目录对象:

adfind -h ad.example.org:636 -ssl -u "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -up Password1 -b "OU=GitLab INT,DC=GitLab,DC=org" -f (objectClass=*)

使用过滤器返回单个对象 也可通过 指定 对象名称或完整 DN 检索单个对象。本示例仅指定对象名称 CN=Leroy Fox

adfind -h ad.example.org:636 -ssl -u "CN=GitLabSRV,CN=Users,DC=GitLab,DC=org" -up Password1 -b "OU=GitLab INT,DC=GitLab,DC=org" -f "(&(objectcategory=person)(CN=Leroy Fox))"

Rails 控制台

在 rails 控制台中创建、读取、修改和销毁数据非常容易。请务必完全按列出的命令执行。

Rails 控制台是调试 LDAP 问题的重要工具。它允许您通过运行命令直接与应用程序交互,观察 GitLab 的响应。

关于如何使用 rails 控制台的说明,请参考此 指南

启用调试输出

此操作提供调试输出,显示 GitLab 的操作对象。此值不会持久化,仅在本次 Rails 控制台会话中启用。

要在 rails 控制台中启用调试输出,进入 rails 控制台 并运行:

Rails.logger.level = Logger::DEBUG

获取与组、子组、成员和请求者相关的所有错误消息

收集与组、子组、成员和请求者相关的错误消息。这可捕获 Web 界面中可能未显示的错误信息,对排查 LDAP 组同步 及用户在组和子组中的意外行为特别有帮助。

# 查找组和子组
group = Group.find_by_full_path("parent_group")
subgroup = Group.find_by_full_path("parent_group/child_group")

# 组和子组错误
group.valid?
group.errors.map(&:full_messages)

subgroup.valid?
subgroup.errors.map(&:full_messages)

# 组和子组的成员及请求者错误
group.requesters.map(&:valid?)
group.requesters.map(&:errors).map(&:full_messages)
group.members.map(&:valid?)
group.members.map(&:errors).map(&:full_messages)
group.members_and_requesters.map(&:errors).map(&:full_messages)

subgroup.requesters.map(&:valid?)
subgroup.requesters.map(&:errors).map(&:full_messages)
subgroup.members.map(&:valid?)
subgroup.members.map(&:errors).map(&:full_messages)
subgroup.members_and_requesters.map(&:errors).map(&:full_messages)