LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol),是一个开放的、中立的、工业标准的应用协议,经常用于身份验证以及存储关于用户、群组和应用程序的信息。LDAP Directory Server 是一个相对通用的数据存储,可在各种应用程序中使用。

为什么选择 LDAP

众所周知,数据存储有诸多类型,包括但不限于 NoSQL 类如 Redis、MongoDB 等,RDBMS 类如 MySQL、PostgreSQL 等,那么为什么要选择 LDAP 呢?

如果选择 NoSQL,基本上就将自己限定在了某种类型的数据库中,因为每种 NoSQL 数据库都有自己的协议。在 NoSQL 数据库中,包括多个种类,例如键值存储、文档数据库、列存储和图形数据库等。而每种类型的数据库都有自己独特的工作方式和协议。选择其中一种数据库意味着你将与这种数据库的特定协议绑定在一起,不同类型的 NoSQL 数据库之间的协议可能不兼容。

以 Redis 和 MongoDB 为例,Redis 使用一种简单而高效的文本协议与客户端进行通信,这个协议被称为 Redis 协议或 RESP(Redis Serialization Protocol)协议,MongoDB 则使用一种称为 MongoDB 协议的二进制协议与客户端进行通信。假设你的应用程序现在使用 Redis 作为数据存储,客户端与 Redis 之间的通信依赖于 Redis 的协议。如果你要将 Redis 服务器替换为 MongoDB 服务器,你需要确保所有的客户端应用程序都能够适应 MongoDB 的协议,否则它们没办法和 MongoDB 服务器进行通信。

为了解决这个问题,可以通过写一层 API 兼容层来兼容客户端应用程序与多种 NoSQL 服务器的通信,但不同的 NoSQL 存储的应用场景不同,写个兼容层还不如直接选择一个满足需求的存储来使用。

如果选择关系型数据库,虽然使用了 SQL,更加标准化,但仍然需要更新客户端应用程序来确保与新的数据库进行通信。此外,不同的关系型数据库的数据类型、特定函数、事务处理可能不同,而且大部分都对 SQL 有独特的解释和实现方式,所以在迁移数据库时可能还需要进行调整和优化

RFC 4511 明确定义了 LDAP 协议,其中包括客户端如何编码请求、服务端如何编码响应等,其中提供了很多种客户端和服务端的 API,所以不会绑死在一种客户端或服务端的 API 上。

LDAP 的特性

LDAP 经过多年的迭代,已经非常成熟,但它还在不断发展。最近的一个版本 LDAP v3 官方发行于 1997 年的十二月。

轻量

LDAP 是一个 X.500 的轻量级版本,使用 ASN.1BER 编码(一种用于高效编码和解码的紧凑的二进制格式),比起 HTTP 用的 JSON 或 XML 等还要更精简。

LDAP 使用持久连接与 directory server 通信,而许多现代基于 HTTP 的协议只是使用相对短暂的连接,LDAP 的连接却可以存活数小时或数天更长时间。基于 xxx,我们可以认为 LDAP 的连接更轻量级。

安全

LDAP Directory Server 通常被用作为认证库,和存储敏感信息,比如密码和账号信息等。LDAP 中包含了大量密码策略功能,如 strong envoding mechanisms 和 contraints,能够防止用户选择弱类型密码,也包括通过 SASL 对各种认证类型的支持,还包括通过 one-time passwords 等实现 two-factor 选项等。

此外,LDAP 还提供细粒度的访问控制支持,限制任何个人用户能够以哪些方式访问哪些 entries、attributes 和 values。基于 SQL 和 NoSQL 的应用通常用单个账户直接对数据存储操作,而 LDAP 应用程序通常作为终端用户来操作,能更加方便地限制访问和审计追踪。

基础概念

Directory Servers

一种网络数据库,以条目树形式存储数据。关系型数据库用的是由行列组成的表格,所以 Directory Servers 可以认为是一种 NoSQL 数据库。

虽然所有 Directory Servers 都支持 LDAP,但由于 LDAP 是一个开放的标准协议,所以有些服务器提供了对其他附加协议的支持,能够用于与数据进行交互,包括 X.500、命名服务协议如 DNS 和 NIS、基于 HTTP 的协议如 DSML 和 SCIM 以及其他专有的协议如 Novell 的 DNS。

Directory Servers - ldap.com

Entries

一个 LDAP 条目(entry)是关于一个实体信息的集合,每个条目由三个主要部分组成:一个分区名(distinguished name),一个属性集合(attributes)和一个对象类的集合(object classes)。

DNs 和 RDNs

一个条目的专有名称被称为DN,唯一表示该条目以及该条目在目录信息树DIT,directory information tree)中的位置,有点像文件系统中的文件路径。

一个 LDAP DN 由零个或多个元素组成,称为相对专有名称(RDN)。每个 RDN 由一个或多个属性 - 值对组成,如uid=john.doe表示一个 RDN 由一个叫 uid 的属性组成,它的值为 john.doe,如果有多个键值对组成,则用加号分开,如uid=john.doe+sn=Doe

不包含键值对的 DN 被称为 null DN,它引用了一种特殊类型的条目,称为根 DSE,提供关于 Directory Servers 的内容和能力的信息。

如果一个专有名称由多个相对专有名称组成,那么这些 RDN 的顺序决定了它们在目录信息树中的位置。RDN 之间用逗号分隔,每个 RDN 代表层次结构中的一个级别,按照从上到下的顺序(也就是离树根更近的位置)。如果从一个 DN 中移除一个 RDN,就相当于得到了该 DN 的父级 DN。举个🌰,DN“uid=john.doe,ou=People,dc=example,dc=com”包含了四个 RDN,其中父级 DN 是“ou=People,dc=example,dc=com”。

LDAP DNs and RDNs

Attributes

属性(Attributes)保存了一个条目的数据。每个属性都有一个属性类型(attribute type),零个或多个属性选项(attribute options),以及一组值(values)组成的实际数据。

属性类型是规定 LDAP 客户端和服务器如何处理属性的规则。属性类型必须有一个唯一的标识符(OID)和一个或多个名称来引用该属性。属性类型还定义了属性的数据类型和比较规则。它还可以指定属性是否可以有多个值,以及属性是用于保存用户数据还是服务器操作。

属性选项用于提供一些关于属性的额外信息,但并不经常使用。例如,属性选项可以用于在不同语言中提供属性值的不同版本。

Object Classes

对象类(Object classes)是一种模式元素,用来定义一组可能与特定类型的对象、过程或其他实体相关联的属性类型集合。每个条目都有一个主要对象类,它表示该条目所代表的对象类型(例如,人员信息、群组、设备、服务等),还可以有零个或多个附加对象类,用于提供该条目的其他特征。

就像属性类型一样,对象类也需要一个唯一的标识符,还可以有一个或多个名称。对象类还可以定义一组必需的属性类型(表示具有该对象类的条目必须包含这些属性)和/或一组可选的属性类型(表示具有该对象类的条目可以选择性地包含这些属性)。

想象一下,对象类就像一个描述对象类型及其属性的模板。每个对象都可以根据所属的对象类来确定应该具备哪些属性。

Object Identifiers (OIDs)

对象的唯一标识,是一个字符串。OID 由一系列用句点分隔的数字组成(例如,“1.2.840.113556.1.4.473”是表示服务器端排序请求控件的 OID)。在 LDAP 中,OID 用于标识模式元素(如属性类型、对象类、语法、匹配规则等)、控件以及扩展请求和响应。对于模式元素,还可以使用用户友好的名称来代替 OID。

简而言之,OID 是用于在 LDAP 协议和相关系统中唯一标识和区分不同组件和功能的特定标签。

Search Filters

搜索过滤器(Search Filters)用于定义标识包含特定信息的条目的条件。有很多种:

  • Presence filters,标识指定属性至少有一个值的条目
  • Equality filters,标识指定属性有特定值的条目
  • Substring filters,标识指定属性至少有一个值与被给定子字符串相匹配的条目
  • Greater-or-equal filters,标识指定属性至少有一个值被视为大于或等于给定值的条目
  • Less-or-equal filters,标识指定属性至少有一个值被视为小于或等于给定值的条目
  • Approximate match filters,标识指定属性的值与给定值近似相等的条目。近似相等的判断取决于服务器,可能是发音相等或其它的
  • Extensible match filters,用于提供更高级的匹配类型,包括使用自定义匹配规则和/或匹配条目 DN 中的匹配属性
  • AND filters,标识与 AND 内部封装的所有过滤器都匹配的条目
  • OR filters,标识与 OR 内部封装的至少一个过滤器匹配的条目
  • NOT filters,对封装的过滤器结果进行否定,相当于取反

匹配规则是用于执行匹配操作的逻辑,在属性类型定义中进行指定。不同的匹配规则可能使用不同的逻辑来进行确定。匹配规则定义了如何根据特定的逻辑规则进行匹配,以便在搜索和过滤操作中进行正确的比较和匹配。

Search Base DNs and Scopes

所有搜索请求都包括一个基本的 DN 元素,用于指定在哪个 DIT 部分查找匹配的条目,以及一个范围(scope),用于指定应考虑该子树的程度,不同的范围决定了搜索操作的深度和范围。定义的搜索范围包括:

  • baseObject 范围(base)表示只应考虑搜索基本 DN 指定的条目
  • singleLevel 范围(one/onelevel)表示只应考虑搜索基本 DN 的直接下级条目(但不包括基本条目本身)
  • wholeSubtree 范围(sub)表示应考虑搜索基本 DN 指定的条目以及所有在其下方的条目(到任意深度)
  • subordinateSubtree 范围表示应考虑搜索基本 DN 下方的所有条目(到任意深度),但不包括搜索基本条目本身

Modifications and Modification Types

修改请求用于向条目中添加、删除或替换属性值。每个修改都有一个类型,用于指定进行何种操作,例如添加、删除、替换或增量。通过修改请求,可以对 LDAP 服务器中的数据进行更新和修改。

  • 添加(add)修改类型表示应将一个或多个属性值添加到条目中。这可以用于添加全新的属性,或向现有属性添加新值。对于添加修改类型,必须至少指定一个属性值
  • 删除(delete)修改类型表示应从条目中删除一个或多个属性值,或者整个属性。如果删除修改包括一个或多个属性值,那么只会删除这些值。如果删除修改不包括任何值,则会删除整个属性
  • 替换(replace)修改类型表示应使用新集合(可能包含条目中已有的值)替换指定属性的值集合。如果替换修改有一个或多个属性值,则这些值将用于相关的属性。如果替换修改没有任何值,且如果存在该属性,它将从条目中移除。
  • 增量(increment)修改类型表示应将指定属性的整数值增加指定的数量(如果增量值为负,则减少)。

LDAP URLs

LDAP URL 是一种用于定位目录服务器和条目以及搜索条件的标识符,在 LDAP 协议中被广泛用于引用和链接到目录服务器中的数据。它封装了信息,可以用于引用目录服务器、特定条目和搜索标准,以便在目录服务器中识别匹配的条目。

Controls

控制项是 LDAP 请求或响应中的一段额外信息,用来提供更多关于请求或响应的信息,或者改变服务器(对于请求)或客户端(对于响应)对其的处理方式。就像我们可以在请求中附带一张“便条”,告诉服务器我们需要对数据进行特殊处理,或者在响应中附带一些额外的指示。比如,服务器端排序请求控制项可以告诉服务器在将搜索结果返回给我们之前,先按照特定的方式对结果进行排序。

控制项有三个元素:

  • 唯一标识符(OID),用于标识控制项类型
  • 关键性,用于指示控制项是否是请求的关键部分。关键性为“true”表示控制项是请求的关键部分,如果服务器无法支持控制项,应拒绝请求。关键性为“false”表示控制项更像是请求的“额外要求”部分,如果服务器无法支持控制项,则应继续处理操作,就好像没有包含该控制项一样。如果服务器确实支持请求范围内的控制,那么关键性就不会起作用
  • 可选值,用于提供控制项处理所需的附加信息。例如,对于服务器端排序请求控制项,控制值应指定期望的排序顺序。控制项的具体编码方式会根据控制项的类型而有所不同

Referrals

简单来说,引荐(referral)是一种 LDAP 响应,表示服务器无法处理所请求的操作,但建议在其他地方尝试(例如在不同的服务器或 DIT 中的不同位置),可能会成功。引荐可能出现的原因包括:

  • 客户端请求的操作针对的条目在连接的服务器中不存在,但服务器能够建议该条目可能存在的位置
  • 客户端请求的操作针对的条目在服务器中确实存在,但由于某种原因,服务器目前无法处理该请求。例如,客户端向只读副本发送写入请求,副本可以将请求重定向到可写服务器
  • 数据中包含一种特殊类型的引荐条目(有时称为“智能引荐”),每当客户端请求该条目或其下属内容时,服务器根据该条目的内容生成引荐

Alias Entries

简单来说,别名条目是一种特殊类型的条目,类似于符号链接在文件系统中指向另一个文件

别名条目主要用于搜索操作,它可以使位于 DIT 中的一个位置的条目在另一个位置上出现。在某些情况下很有用,例如,当一个条目在特定子树中的存在用于确定群组成员身份或表示某种授权目的时。搜索请求包括一个元素,指示如何处理搜索过程中遇到的任何别名。

针对别名条目的非搜索操作不会跟随别名进行操作。别名不能作为绑定操作的目标标识。别名必须是叶子条目,因为无法在别名条目下添加条目。

需要注意的是,并非所有目录服务器都支持别名。如果应用程序打算与广泛范围的目录服务器兼容,应避免使用别名。

LDAP 模式

在关系型数据库中,模式包含了数据库结构的信息,包括表结构的信息、关于每个表的列的信息以及每个列的数据类型和约束。在 LDAP 中,模式提供了很多类似的信息,但是由于信息的排列方式关系型数据库不同,表达方式也不同。

一个 LDAP 模式包含多个类型的元素,每个模式都必须包含以下几个:

  • Attribute Syntaxes 定义了能在目录服务器中表示的数据类型
  • Matching Rules 定义了能对 LDAP 数据进行比较的种类
  • Attribute Types 定义了可存储在条目中的命名信息单位
  • Object Classes 定义了可以在包含该对象类的条目中使用的属性类型的命名集合,以及这些属性类型哪些是可选的,哪些是必选的

一些额外的元素:

  • Name Forms,可用于限制可作为特定类型条目的命名属性的属性种类
  • DIT Content Rules,可用于增强对象类的定义,进一步指出必须、可选和不能出现在特定类型的条目中的属性种类
  • DIT Structure Rules,可用于定义服务器中允许存在的层次关系的信息
  • Matching Rule Uses,可用于对可使用特定匹配规则的属性种类加限制

操作类型

Bind

绑定操作(Bind)是用于让客户端在目录服务器上进行身份认证的方式。它确保客户端能够证明自己的身份,并建立一个用于后续操作的授权身份,并指定客户端将使用的 LDAP 协议版本。

认证通常包括两个部分:确定是谁进行认证以及提供一些证据来证明身份(通常是只有用户自己知道的密码、证书、硬件或软件令牌或生物特征信息等)。在进行绑定操作时,服务器可能还会执行其他步骤,如检查密码策略和满足其他限制条件,以确保绑定成功。

LDAP 绑定请求提供了两种认证方式:简单认证SASL 认证。简单认证是通过使用帐户的唯一标识(DN)密码来进行身份验证。密码以明文形式传输,因此强烈建议只在加密连接下使用简单认证。匿名简单绑定(anonymous simple bind)是通过提供空的 DN 和密码来进行的。

SASL 认证使用了一种名为简单认证和安全层(SASL)的标准。它是一个可扩展的框架,可以将各种认证机制集成到 LDAP 中。SASL 认证通过使用一种特定的机制和编码的凭据来完成。某些机制可能需要多次请求和响应来完成完整的认证过程。

LDAP 绑定请求包含三个要素:

  • 客户端希望使用的 LDAP 协议版本。目前大部分新的应用都是使用 v3,少部分非常老旧的客户端使用 v2。
  • 认证用户的 DN。对于匿名简单认证,这应该为空;对于 SASL 认证,由于大多数 SASL 机制在编码的凭据中标识目标帐户,因此通常为空。对于非匿名简单认证,它必须为非空值。
  • 认证用户的凭据(一般是密码)。对于简单认证,这是指定绑定 DN 的用户的密码(或匿名简单认证的空字符串)。对于 SASL 认证,这是一个编码值,其中包含SASL 机制名称可选的编码 SASL 凭据集

如果 LDAP 客户端在没有进行绑定的情况下发出其他类型的请求,则客户端将被视为未经身份验证。这与匿名简单绑定(使用空绑定 DN 和空密码)导致的身份验证状态相同,也是绑定操作失败导致的身份验证状态。

当简单绑定操作完成时,服务器将返回一个基本响应,其中包括结果代码、可选的匹配 DN、诊断消息、引荐和/或响应控制。SASL 绑定响应还可以包括编码的服务器 SASL 凭据,用于后续处理。对于需要多个请求/响应周期的 SASL 机制,除了最后一个响应之外,所有响应都将包含一个“SASL 绑定正在进行中”的结果代码,以指示身份验证过程尚未完成。

LDAP Search 操作能检索匹配给定条件的条目的数据。请求参数包括:

  • base DN:检索的起始点,必须提供,但可能是 null DN。操作将会在该指定子树检索。
  • search scope:目标子树的检索范围,支持以下几个选项
    • baseObject(base):搜索操作只会在指定的条目上进行,不会扩展到它的子条目。
    • singleLevel(one):搜索操作仅限于基础条目的直接子级,不包括它的其余子级和它自己。
    • wholeSubtree(sub):整个子树搜索将包括搜索基础条目以及其下的所有子孙级条目,但不包括根 DSE(如果搜索基础 DN 为空)。
    • subordinateSubtree(subordinates):从搜索基础的下一级开始,包括其所有子级和子孙级条目,但不包括搜索基础条目本身。
  • DerefAliases:别名解引用行为,指明了服务器在检索过程中应该如何处理遇到的别名。以下是支持的解引用行为:
    • neverDerefAliases:表示服务器在处理搜索时应解引用遇到的任何别名。
    • dereflnSearching:表示服务器不应尝试解引用搜索基础条目,但应解引用范围内遇到的任何别名。
    • derefFindingBaseObj:表示如果作为搜索基础的条目是一个别名,则服务器应解引用该别名,但不应解引用范围内遇到的任何别名。
    • derefAlways:表示如果作为搜索基础的条目是一个别名,则服务器应解引用该别名,并且应解引用范围内遇到的任何别名。
  • Size limit:限制检索结果中返回条目的最大数量。如果为 0 则说明不限制。服务端可能也会对检索结果进行限制,此时取二者较小的那个。
  • Time limit:检索过程的时间限制,相当于请求超时,单位为秒。服务端可能也对检索时间进行限制,此时同上取二者较小的那个。
  • typesOnly:一个 flag,如果设置为 true,则表示匹配搜索条件的条目应只返回包含属性描述的条目,而不包含这些属性的值。如果将其设置为 false,则表示返回的条目应包含属性值
  • search filter:过滤器,用于检索匹配条件,上文有提到过。
  • attributes:一个属性集合,返回的条目只包含这些属性。然后还有些特定值:

当目录服务器接收到一个有效且经过授权的搜索请求时,它将识别出在指定范围内与给定过滤器匹配的任何条目。所有这些条目(或者至少是请求者有权限检索的条目)将以搜索结果条目消息的形式返回给客户端。每个搜索结果条目消息将包含匹配条目的 DN,以及该条目中包含的零个或多个属性,这取决于搜索请求中请求的属性集合以及请求者有权限检索的属性集合。如果搜索请求的 typesOnly 值为 true,则这些属性将返回而不包含其值;否则,属性将返回与请求者有权限检索的所有值

比较有意思的是如果服务器在搜索过程中确定可能有其他服务器中与搜索条件匹配的条目,那么服务器还可以返回一个或多个搜索结果引用消息,其中包含引荐 URI,客户端可以选择是否跟随。

一旦服务器返回了所有适当的搜索结果条目和搜索结果引用消息,它将返回一个搜索结果完成消息,表示该搜索操作的所有处理已完成。这个搜索结果完成消息是一个基本的 LDAP 响应,包括一个结果代码,以及可选的匹配 DN诊断消息引荐和/或响应控制

实践

envoy-go-ldap-auth

GitHub - mosn/envoy-go-ldap-auth: Envoy golang filters that will be provided in golang hub

笔者近期在研究MOSN时,为MOSN社区贡献了一个基于MoE框架的LDAP过滤器,旨在为Envoy集成LDAP认证功能,感兴趣可以看一下。基于MoE框架的Go Filter有着以下几点好处:

  • 动态加载Go Filter,无需重新编译Envoy,节省开发与部署时间。
  • 可以使用Go的全部特性,享受Go生态带来的便利。
  • 保证了内存安全、兵法安全以及沙箱安全。

总结

本文介绍了 LDAP 协议,为什么选择 LDAP,以及 LDAP 的特性、基础概念、模式和操作类型等。LDAP 在各行各业中得到广泛应用,包括企业组织、教育机构、政府部门等。它被用于集中管理用户身份和权限,实现单点登录、身份验证和访问控制等功能,加上跨平台的特性使得 LDAP 成为跨多个系统和应用集成的标准选择。关于 LDAP 的内容还有很多本文没有介绍,详情可以见参考。

参考