LabHub
学习 学习路径 课程

企业认证对接

LDAP 树与搜索过滤器,以及组织架构的陷阱

在 LabHub 中继续学习

一句话总结

LDAP 是一棵树,而组指向人员,还是人员指向组,决定了查询方向。方向选错,登录耗时就会随用户数量增长。

分层图: 组指向人员,还是人员指向组 · 100 个用户时无人察觉,增长到 1 万人后,登录就需要 3 秒。 · DIT(Directory Information Tree) · DN(Distinguished Name)

为什么需要它

“接入企业账户”最终通常会落到 LDAP 查询。但如果过滤器写得粗糙,禁用账户与服务账户都会混入用户列表;如果不指定返回属性,从十万人的目录里取回所有属性,还会同时拖累服务器与网络。

更隐蔽的问题是方向。这个目录由组通过 member 指向人员,因此无论怎样查看用户条目,都看不到所属组。不理解这一点,就可能写出每次登录都扫描全部组的代码。100 个用户时无人察觉,增长到 1 万人后,登录就需要 3 秒。

DIT——目录是一棵树

LDAP 数据存放在称为 **DIT(Directory Information Tree)**的树中。每个节点(条目)都由 **DN(Distinguished Name)**唯一标识。

dc=labhub,dc=co,dc=kr                         ← 루트 (Base DN)
├── ou=People
│    ├── uid=hong,ou=People,dc=labhub,dc=co,dc=kr
│    └── uid=kim,ou=People,dc=labhub,dc=co,dc=kr
├── ou=Groups
│    ├── cn=dev-team,ou=Groups,...
│    └── cn=role-admin,ou=Groups,...
└── ou=Disabled                               ← 퇴사자 격리 구역

DN 要从下往上读。uid=hong,ou=People,dc=labhub,dc=co,dc=kr 表示“labhub.co.kr 组织中 People 部门的 hong”。它与文件路径方向相反,初学时容易混淆。

掌握以下主要属性缩写,就足以开始。

缩写 含义 示例
dc domainComponent dc=labhub,dc=co,dc=kr
ou organizationalUnit ou=People
cn commonName cn=홍길동, cn=dev-team
uid userid uid=hong
sn / givenName 姓/名 sn=홍, givenName=길동
mail 邮件 hong@labhub.co.kr
title 职级/职务 title=과장
member 组成员(DN) member=uid=hong,ou=People,...

搜索过滤器——RFC 4515 的前缀表示法

LDAP 过滤器采用操作符在前的括号表达式。起初不熟悉,但规则很简单。

(uid=hong)                          단순 일치
(cn=홍*)                            앞부분 일치 (와일드카드)
(&(A)(B))                           A 그리고 B
(|(A)(B))                           A 또는 B
(!(A))                              A 가 아님
(objectClass=*)                     해당 속성이 존재

组合后如下。

(&(objectClass=inetOrgPerson)(ou=개발팀)(title=과장))
(&(objectClass=inetOrgPerson)(|(title=부장)(title=차장))(!(ou=총무팀)))

使用 ldapsearch 时可以这样写。

ldapsearch -x -H ldap://127.0.0.1:1389 \
  -D "cn=admin,dc=labhub,dc=co,dc=kr" -w labhub123 \
  -b "dc=labhub,dc=co,dc=kr" \
  "(&(objectClass=inetOrgPerson)(title=과장))" uid cn ou

还应了解搜索范围(-s):base(只查自身)、one(只查直接子节点)、sub(查全部后代,默认)。大型目录中过度使用 sub 会很慢。

组——存在两个方向

成员关系有两种表达方式,这一差异会直接影响性能与同步。

  1. 组指向人员groupOfNamesmember 属性)
    • 组条目保存成员 DN 列表
    • 查询“这个组有哪些成员”很快
    • 查询“这个人属于哪些组”则必须扫描全部组
  2. 人员指向组memberOf 属性)
    • 用户条目保存所属组 DN 列表
    • AD 会自动维护,OpenLDAP 需要启用 overlay
    • 查询“这个人属于哪些组”很快,适合登录时判断权限

实际陷阱是:目录里member,却没有 memberOf,于是代码在每次登录时扫描全部组。100 个用户时看不出来,1 万人时登录就需要 3 秒。

AD 与 OpenLDAP 的实际差异

项目 Active Directory OpenLDAP
登录 ID 属性 sAMAccountName(或 userPrincipalName 通常是 uid
不变标识符 objectGUID entryUUID
组反向引用 默认提供 memberOf 需要 memberof overlay
密码属性 unicodePwd(不能通过明文 LDAP 修改) userPassword
账户状态 userAccountControl 自定义约定

为什么不变标识符很重要。 人可能因结婚改名,调动后 DN 也会改变。如果用 uid 或 DN 作为同步键,同一个人会被创建成两个新用户。 必须使用 objectGUIDentryUUID 这样的不变值作为关联键。设计错误会造成重复账户,而且可能静默积累数月。

userAccountControl 是位标志。 使用 AD 时,下面三项值得记住。

0x0002  ACCOUNTDISABLE        계정 비활성
0x0010  LOCKOUT               잠김
0x10000 DONT_EXPIRE_PASSWD    비밀번호 만료 없음

512   = 정상 계정
514   = 정상 + 비활성 (512 + 2)
66048 = 정상 + 비밀번호 만료 없음 (512 + 65536)

因此,只查询“活跃用户”的 AD 过滤器如下。

(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

中间的 OID 是“位 AND”匹配规则,含义是排除 userAccountControl 中第 2 位被置位的账户。不使用这个过滤器,禁用账户与计算机账户也会进入用户列表。

同步的两个陷阱

从目录定期同步用户到我们的系统(或 IdP)时,必然会遇到以下问题。

  1. 增量同步无法发现删除。 拉取“最近变更条目”的方式无法看见已经消失的条目,因此离职账户会继续留在本系统。这是幽灵账户的常见来源。 → 增量同步要频繁执行(每小时),同时还要定期执行全量同步(每周一次)
  2. 变更时间取自目录服务器的时钟。 只要一台服务器的 NTP 有偏差,那个区间的变更就会静默遗漏。症状是“偶尔有一个人没有同步”,定位非常困难。

明文 389 会被审计指出

通过 ldap:// 明文连接执行 bind,密码会以明文经过网络。 生产环境必须使用 ldaps://(636)或 StartTLS;明文配置会在安全审计中立即被指出。

(本实验受 capability 限制,无法监听 1024 以下端口,因此使用明文 1389。这是教学配置,不是生产配置。)