LDAP[轻型目录访问协议](Lightweight Directory Access Protocol)(属于X.500) 是一种目录服务(Directory service)标准。
目录是一个特殊的数据库,它的数据经常被查询,但是不经常更新。不像普通的数据库,目录不包括对事件(transaction)的支持也不包括回滚特性。目录是很容易被复制的,以便增加它的可用性和可靠性。当目录被复制时,临时的数据不一致情况是允许出现的,只要最终这些数据得到同步即可。
LDAP由互联网工程任务组(IETF)的文档RFC定义,使用了描述语言ASN.1定义。最新的版本是版本3,由RFC 4511所定义。
轻型目录访问协议(Lightweight Directory Access Protocol) 议是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。 目录服务在开发内部网和与互联网程序共享用户、系统、网络、服务和应用的过程中占据了重要地位。
OpenLDAP 是一个基于标准实现的使用最多的 LDAP Server。
BTW,微软的 Active Directory Domain Services(AD DS) 也提供目录服务,支持 LDAP 协议,不过具体的属性跟 OpenLDAP 有很大的区别。
phpLDAPadmin
phpLDAPadmin 是 LDAP 的一个 Web GUI。
需要注意大部分的文档都是 v1 的,v2 进行了重构,有很大的区别。并且不能用 rootdn 登录
就个人来说,并不太喜欢 v2,虽说直接使用 rootdn 有安全性问题,但是直接不让用就很尬。具体见Issue
名词解析
Distinguished Name (DN)
DN:每个 LDAP 条目在目录树中的唯一标识,类似文件系统中的完整路径;由若干层级的 RDN(Relative Distinguished Name,相对可分辨名称)组成,每个 RDN 为属性–值对。
RDN 示例:uid=john.doe 或 cn=Users,多属性 RDN 如 givenName=John+sn=Doe
rootdn
rootdn(Root Distinguished Name)是 LDAP 数据库中的超级管理员 DN,具备对该数据库条目的完全控制权限,不受常规访问控制列表(ACL)限制。
超级权限
rootdn 对应的用户拥有忽略所有 ACL 的完全访问权限,可执行添加、删除、修改、查询等任意操作。
配置示例
1 | database mdb |
SASL 绑定
也可以指定 SASL 身份作为 rootdn,例如:
1 | rootdn "uid=root,cn=example.com,cn=digest-md5,cn=auth" |
OU(Organizational Unit)
是 LDAP 中用于构建目录层次结构、组织条目、应用策略和委派管理的基本构建块。它像文件夹一样,帮助你将目录中的信息整理得井井有条,使其更易于管理和理解。
OU 的名字是可以自定义的,不过因为一些教程和预定义设置的原因的原因,很多人可能会使用比较常见的例如ou=People
和ou=Group
或ou=users
和ou=groups
,如 bitnami 的 OpenLDAP 的 docker 预制就是后者,而 dokuwiki 的过滤器中默认则用前者过滤。
AD 中也有相同的概念,不过有些管理可能会把 OU 当部门使用,然后把不同的用户塞到不同的 OU。总之,这些跟后面的过滤器是强相关的,因为使用起来五花八门,所以过滤器这块基本都是可以自定义的。
LDIF 文件格式介绍
LDIF(LDAP Data Interchange Format,LDAP 数据交换格式)是一种基于文本的格式,用于表示 LDAP(轻量目录访问协议)目录中的数据。LDIF 文件通常用于:
- 导入(import)或导出(export)LDAP 条目;
- 批量添加、删除或修改 LDAP 数据;
- 与 ''ldapadd'', ''ldapmodify'', ''ldapdelete'' 等命令行工具配合使用。
LDIF 文件由一系列的“条目(entry)”组成。每个条目表示 LDAP 目录中的一个对象,并由若干“属性(attribute)”组成。条目之间用空行分隔。
示例:
1 | dn: cn=John Doe,dc=example,dc=com |
LDAP 的 ACL(访问控制列表)
LDAP 的访问控制列表(ACL)用于对目录中的条目或属性进行授权管理,最终以 Access Control Instruction(ACI,访问控制指令)的形式存储和评估。ACL 的核心组成包括目标(target)、权限(permission)、主体(subject)及版本号与名称,并按顺序依次检查直到命中或默认拒绝。
LDAP ACL 提供了一种机制,可对目录中的不同分支、条目或属性进行精细化的读写、添加、删除等权限控制。它既可在导入时通过 LDIF(LDAP Data Interchange Format)配置,也可在运行时结合 ''ldapmodify''、''ldapadd'' 等工具动态调整。ACI 的格式和支持的关键字在不同目录服务器(如 OpenLDAP、Oracle Directory、IBM Directory、PingDirectory、ForgeRock DS 等)中大同小异,便于跨平台迁移与统一管理。
ACL 的评估顺序
- LDAP 服务器会按照配置文件或目录中 ACL 条目的先后顺序逐条检查,第一个匹配的指令即决定最终授权结果;
- ACL 通常按“最具体→最通用”的原则排序(如先匹配精确 DN,再匹配 subtree、再匹配全局);
- 若无任何 ACL 命中,则采用默认拒绝。
ACI 语法结构
- targets:
dn.exact="..."
、dn.subtree="..."
等; - version:如
3.0
; - acl "name":描述性名称;
- permission:
allow
/deny
; - subjects:匹配用户、组或网络。
1 | aci: (target="...")(version 3.0;acl "Read People";allow (read, search) userdn="ldap:///uid=reader,ou=People,dc=example,dc=com";) |
LDAP 权限验证
-Y EXTERNAL -H ldapi:///
本地 root 权限绑定,仅限ldapserver本地使用,也就是要在docker内执行命令(权限级别与使用rootdn一样)
EXTERNAL 是一种 SASL(Simple Authentication and Security Layer)机制,用于通过已有的外部身份验证方式(例如 Unix socket 权限)来认证你的 LDAP 操作。
SASL 是一种认证框架,OpenLDAP 支持多种 SASL 机制,例如:
- PLAIN
- DIGEST-MD5
- GSSAPI
- EXTERNAL
EXTERNAL 是其中一种最常见的机制,通常用于本地系统用户以 root 权限访问 OpenLDAP 的配置数据库(cn=config),不需要输入用户名和密码。
当你通过 EXTERNAL 使用 Unix socket 登录时,OpenLDAP 会通过 socket 的所有者判断是谁发起的连接,比如:
- 如果你是 root 用户,就会映射为 gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth。
- 通常系统会在 olcAccess 中授予该身份读写权限。
LDAP 集成
本页面主要有两种配置,一个是对 AD 的集成,还有一个则是对 OpenLDAP 的集成。
需要注意的是,AD 虽然底层也符合标准,但跟 OpenLDAP 的各项属性有很大区别,很多服务会特别的专门适配 AD,但有些会以 OpenLDAP 为标准,连接 AD 会需要自行修改配置项。
bindn
通常 AD 下使用具有域控管理员权限的账号,不过出于安全性考虑可以降低权限(普通 domain user 没有读取其他用户邮箱的权限),目前都使用的管理员权限的账号做bind
OpenLDAP 中则一般使用 rootdn (也可以不使用,不过需要创建具有特殊权限的账号)
Base DN
定义:LDAP 搜索操作(例如 ldapsearch)使用的“起始 DN”,决定了查询范围的顶端节点。
格式:通常为域组件(DC,Domain Component),如 dc=example,dc=com;也可为组织单元(OU,如 ou=Users,dc=example,dc=com)或通用名称(CN,如 cn=Administrator,cn=Users,dc=example,dc=com)。
在客户端或应用配置中填入 Base DN 后,后续的搜索操作会从该节点及其子树进行。例如,若 Base DN 为 ou=Users,dc=example,dc=com,那么只有位于该 OU 下的用户才会被索引。
上面加粗了,因为实际上大多会使用 Users 或 People ,主要是查找用户
不过你需要注意下有些服务是不需要同步 LDAP 组的,例如 dokuwiki 里就没有设basedn,他会让你设用户过滤和组的过滤条件。
LDAP 过滤器
LDAP 过滤器 (LDAP Filter) 在与 LDAP/AD 集成的应用程序(如 DokuWiki 的 authad 插件 或 Snipe-IT)中扮演着至关重要的角色。它定义了如何查找和验证用户。
简单来说,LDAP 过滤器就像是你在 Active Directory 这个大数据库里进行搜索时使用的搜索条件。你需要告诉应用程序:当用户尝试登录时,应该使用哪些标准来找到 AD 中对应的用户对象,并确认这个对象是有效的、允许登录的。
一个可供参考的问题见 Issue
LDAP 过滤器的基本语法
- 使用括号 ''()'' 包裹整个过滤器。
- 基本条件通常是 ''(属性名=值)'' 的形式。
- 可以使用逻辑运算符将多个条件组合起来:
* &:与 (AND) - 所有条件都必须满足。格式:''(&(条件1)(条件2)...)''
* |:或 (OR) - 至少一个条件满足。格式:''(|(条件1)(条件2)...)''
* !:非 (NOT) - 条件不满足。格式:''(!(条件))'' - 值可以是具体的值,也可以是通配符 '''',或者是一个*占位符,应用程序会在运行时将其替换为用户输入的内容(通常是用户名)。
常用的 AD 用户属性及过滤器构建块
- ''objectCategory=person'':查找对象类别为 "person" 的对象(通常包含用户)。
- ''objectClass=user'':查找对象类别为 "user" 的对象。通常与 ''objectCategory=person'' 一起使用,更精确地定位用户。
- ''sAMAccountName'':用户的登录名(通常是 Windows 2000 之前的短名称,例如 ''johndoe'')。这是最常用的登录名字段。
- ''userPrincipalName'' (UPN):用户的邮箱风格登录名(例如 ''johndoe@yourdomain.local'')。
- ''mail'':用户的电子邮件地址。有时也用作登录名。
- ''userAccountControl'':一个包含用户账户状态(如是否禁用)的位掩码。查找未禁用的用户通常使用位操作符: ''(!(userAccountControl:1.2.840.113556.1.4.803:=2))''。这里的 ''2'' 代表 ''ADS_UF_ACCOUNTDISABLE'' 标志位,'':1.2.840.113556.1.4.803:='' 是进行位与 (Bitwise AND) 比较的 LDAP 匹配规则。整个表达式的意思是“userAccountControl 属性中没有设置值为 2 的那个位”。
- ''memberOf'':用于检查用户是否属于某个特定的组。值需要是组的完整 Distinguished Name (DN),例如 ''CN=AppUsers,OU=Groups,DC=yourdomain,DC=local''。
常见的 LDAP 用户过滤器示例
注意: 以下示例使用占位符 ''{username}'' 代表用户输入的用户名。请务必查阅你所使用的应用程序(如 DokuWiki authad 插件)的文档,确认它实际使用的占位符是什么,可能是 ''@USER@'', ''%user%'', ''$user'' 等。
最基本:使用 sAMAccountName 查找未禁用的用户 (推荐作为起点)
下面最外层的括号或许需要取消
1 | (&(objectCategory=person)(objectClass=user)(sAMAccountName={username})(!(userAccountControl:1.2.840.113556.1.4.803:=2))) |
- ''&'': 所有条件都需满足。
- ''objectCategory=person'' 和 ''objectClass=user'': 确保找到的是用户对象。
- ''sAMAccountName={username}'': 用户的 ''sAMAccountName'' 属性必须等于登录时输入的用户名。
- ''(!(userAccountControl:1.2.840.113556.1.4.803:=2))'': 确保账户未被禁用。(部分服务会同步已禁用账号,然后在本地显示账号禁用,如果不需要可以直接通过过滤器排除掉)
OpenLDAP
因为大部分都会以 OpenLDAP 为主,所以基本没什么好写的,你只要注意不要因为自己设的不同名字的 OU 被坑了就行
1 | (&(objectClass=posixGroup)(|(memberUid=%{uid})(gidNumber=%{gid}))) |
- ''objectCategory=posixGroup'': 确保找到的是组对象。
- ''|(memberUid=%{uid})(gidNumber=%{gid})'': 用户主组是gidNumber,memberUid是次组