TT Lab
开始
学习 学习路径 课程

企业认证对接

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

在 TT Lab 中继续学习

一句话总结

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

为什么需要它

“接入企业账户”最终通常会落到 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. 组指向人员(groupOfNames 的 member 属性)
    • 组条目保存成员 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 作为同步键,同一个人会被创建成两个新用户。 必须使用 objectGUID/entryUUID 这样的不变值作为关联键。设计错误会造成重复账户,而且可能静默积累数月。

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。这是教学配置,不是生产配置。)