# 32.5.轻型目录访问协议（LDAP）

轻量级目录访问协议（LDAP）是一种应用层协议，用于访问、修改和验证对象，使用分布式目录信息服务。可以将其看作是一个电话簿或记录本，存储多个层级的同类信息。它用于 Active Directory 和 OpenLDAP 网络，允许用户通过单个账户访问多个层级的内部信息。例如，电子邮件认证、拉取员工联系信息和内部网站认证都可以利用 LDAP 服务器的记录库中的单个用户账户。

本节提供了在 FreeBSD 系统上配置 LDAP 服务器的快速入门指南。假设管理员已经有了一个设计方案，其中包括要存储的信息类型、这些信息的用途、哪些用户应有权访问这些信息，以及如何保护这些信息不受未经授权的访问。

## 32.5.1. LDAP 术语和结构

在开始配置之前，应该理解 LDAP 使用的一些术语。所有目录条目都由一组 *属性* 组成。每个属性集都包含一个唯一标识符，称为 *区分名称*（DN），通常由其他一些属性（如常见名称或 *相对区分名称*（RDN））构建。类似于目录有绝对路径和相对路径，可以将 DN 看作是绝对路径，将 RDN 看作是相对路径。

一个 LDAP 条目的示例如下。此示例搜索指定用户账户（`uid`）、组织单位（`ou`）和组织（`o`）的条目：

```sh
% ldapsearch -xb "uid=trhodes,ou=users,o=example.com"
# extended LDIF
#
# LDAPv3
# base <uid=trhodes,ou=users,o=example.com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# trhodes, users, example.com
dn: uid=trhodes,ou=users,o=example.com
mail: trhodes@example.com
cn: Tom Rhodes
uid: trhodes
telephoneNumber: (123) 456-7890

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
```

此示例条目显示了 `dn`、`mail`、`cn`、`uid` 和 `telephoneNumber` 属性的值。`cn` 属性是 RDN。

有关 LDAP 及其术语的更多信息，请访问 <http://www.openldap.org/doc/admin24/intro.html>。

## 32.5.2. 配置 LDAP 服务器

FreeBSD 不提供内建的 LDAP 服务器。首先，通过安装 [net/openldap-server](https://cgit.freebsd.org/ports/tree/net/openldap-server/) 软件包和 Ports 来进行配置：

```sh
# pkg install openldap-server
```

[包](https://docs.freebsd.org/en/articles/linux-users/#software) 中启用了大量的默认选项。通过运行 `pkg info openldap-server` 来查看它们。如果这些选项不够（例如需要 SQL 支持），请考虑使用适当的 [框架](https://docs.freebsd.org/en/books/handbook/ports/#ports-using) 重新编译端口。

安装后会创建目录 **/var/db/openldap-data** 用于存储数据。必须创建用于存储证书的目录：

```sh
# mkdir /usr/local/etc/openldap/private
```

下一步是配置证书颁发机构（Certificate Authority，CA）。以下命令必须从 **/usr/local/etc/openldap/private** 目录执行。因为文件权限需要严格限制，普通用户不能访问这些文件。有关证书及其参数的更详细信息，请参考 [OpenSSL](https://docs.freebsd.org/en/books/handbook/security/#openssl)。要创建证书颁发机构，从以下命令开始并根据提示操作：

```sh
# openssl req -days 365 -nodes -new -x509 -keyout ca.key -out ../ca.crt
```

提示中的条目可以是通用的 \*，除了 `Common Name`。此条目必须与系统主机名 *不同*。如果这是一个自签名证书，前缀主机名为 `CA`，表示证书颁发机构。

接下来的任务是创建证书签名请求和私钥。输入以下命令并按照提示操作：

```sh
# openssl req -days 365 -nodes -new -keyout server.key -out server.csr
```

在生成证书的过程中，确保正确设置 `Common Name` 属性。证书签名请求必须使用证书颁发机构签名，才能作为有效证书使用：

```sh
# openssl x509 -req -days 365 -in server.csr -out ../server.crt -CA ../ca.crt -CAkey ca.key -CAcreateserial
```

证书生成过程的最后一步是生成并签署客户端证书：

```sh
# openssl req -days 365 -nodes -new -keyout client.key -out client.csr
# openssl x509 -req -days 3650 -in client.csr -out ../client.crt -CA ../ca.crt -CAkey ca.key
```

记住在提示时使用相同的 `Common Name` 属性。完成后，确保通过上述命令生成了总共八（8）个新文件。

运行 OpenLDAP 服务器的守护进程是 **slapd**。其配置通过 **slapd.ldif** 进行：旧的 **slapd.conf** 已被 OpenLDAP 废弃。

[**slapd.ldif** 配置示例](http://www.openldap.org/doc/admin24/slapdconf2.html) 可用，且也可以在 **/usr/local/etc/openldap/slapd.ldif.sample** 中找到。选项的文档可以参考 slapd-config(5)。**slapd.ldif** 中的每一节，如同其他所有 LDAP 属性集一样，都通过一个 DN 来唯一标识。确保在 `dn:` 语句和该部分的结束之间没有空行。在以下示例中，将使用 TLS 来实现安全通道。第一部分表示全局配置：

```ini
#
# 详细的配置选项请参见 slapd-config(5)。
# 该文件不应为全世界可读。
#
dn: cn=config
objectClass: olcGlobal
cn: config
#
#
# 定义全局 ACL 禁用默认的读取权限。
#
olcArgsFile: /var/run/openldap/slapd.args
olcPidFile: /var/run/openldap/slapd.pid
olcTLSCertificateFile: /usr/local/etc/openldap/server.crt
olcTLSCertificateKeyFile: /usr/local/etc/openldap/private/server.key
olcTLSCACertificateFile: /usr/local/etc/openldap/ca.crt
#olcTLSCipherSuite: HIGH
olcTLSProtocolMin: 3.1
olcTLSVerifyClient: never
```

这里必须指定证书颁发机构、服务器证书和服务器私钥文件。建议让客户端选择安全加密套件，并省略 `olcTLSCipherSuite` 选项（因为它与 **openssl** 以外的 TLS 客户端不兼容）。选项 `olcTLSProtocolMin` 允许服务器要求最低的安全级别：这是推荐的。虽然对服务器来说，验证是强制的，但对客户端来说不是：`olcTLSVerifyClient: never`。

第二部分是关于后端模块的配置，可以按以下方式进行配置：

```sh
#
# 加载动态后端模块：
#
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulepath: /usr/local/libexec/openldap
olcModuleload: back_mdb.la
#olcModuleload: back_bdb.la
#olcModuleload: back_hdb.la
#olcModuleload: back_ldap.la
#olcModuleload: back_passwd.la
#olcModuleload: back_shell.la
```

第三部分用于加载数据库将使用的 `ldif` 模式：它们是必不可少的。

```sh
dn: cn=schema,cn=config
objectClass: olcSchemaConfig
cn: schema

include: file:///usr/local/etc/openldap/schema/core.ldif
include: file:///usr/local/etc/openldap/schema/cosine.ldif
include: file:///usr/local/etc/openldap/schema/inetorgperson.ldif
include: file:///usr/local/etc/openldap/schema/nis.ldif
```

接下来是前端配置部分：

```ini
# 前端设置
#
dn: olcDatabase={-1}frontend,cn=config
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: {-1}frontend
olcAccess: to * by * read
#
# 示例全局访问控制策略：
#	Root DSE：允许任何人读取
#	子模式（sub）条目 DSE：允许任何人读取
#	其他 DSE：
#		允许自我写访问
#		允许经过身份验证的用户读取访问
#		允许匿名用户进行身份验证
#
#olcAccess: to dn.base="" by * read
#olcAccess: to dn.base="cn=Subschema" by * read
#olcAccess: to *
#	by self write
#	by users read
#	by anonymous auth
#
# 如果没有访问控制策略，默认策略是：
# 允许任何人读取任何内容，但限制更新为 rootdn。 (例如，"access to * by * read")
#
# rootdn 总是可以读取和写入一切！
#
olcPasswordHash: {SSHA}
# {SSHA} 已是 olcPasswordHash 的默认值
```

另一个部分是配置后端部分，后续访问 OpenLDAP 服务器的配置只能作为全局超级用户进行。

```ini
dn: olcDatabase={0}config,cn=config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
olcAccess: to * by * none
olcRootPW: {SSHA}iae+lrQZILpiUdf16Z9KmDmSwT77Dj4U
```

默认的管理员用户名是 `cn=config`。在 shell 中输入 **slappasswd**，选择一个密码并使用其哈希值填入 `olcRootPW`。如果现在不指定此选项，在 **slapd.ldif** 导入之前，之后将无法修改 *全局配置* 部分。

最后一部分是关于数据库后端：

```ini
################################
# LMDB 数据库定义
################################
#
dn: olcDatabase=mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: mdb
olcDbMaxSize: 1073741824
olcSuffix: dc=domain,dc=example
olcRootDN: cn=mdbadmin,dc=domain,dc=example
# 避免使用明文密码，尤其是 rootdn 密码。
# 参见 slappasswd(8) 和 slapd-config(5) 以获取详细信息。
# 建议使用强身份验证。
olcRootPW: {SSHA}X2wHvIWDk6G76CQyCMS1vDCvtICWgn0+
# 数据库目录必须在运行 slapd 之前存在，并且
# 仅限 slapd 和 slap 工具访问。
# 推荐使用 700 权限。
olcDbDirectory: /var/db/openldap-data
# 要维护的索引
olcDbIndex: objectClass eq
```

这个数据库托管了 LDAP 目录的 *实际内容*。除了 `mdb` 类型外，还有其他类型可用。其超级用户（不要与全局超级用户混淆）在这里进行配置：`olcRootDN` 中是一个（可能是自定义的）用户名，`olcRootPW` 中是密码哈希值；可以像之前一样使用 **slappasswd**。

此 [仓库](http://www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=tree;f=tests/data/regressions/its8444;h=8a5e808e63b0de3d2bdaf2cf34fecca8577ca7fd;hb=HEAD) 包含了四个 **slapd.ldif** 的示例。要将现有的 **slapd.conf** 转换为 **slapd.ldif**，请参考 [此页面](http://www.openldap.org/doc/admin24/slapdconf2.html)（请注意，这可能会引入一些无用的选项）。

配置完成后，**slapd.ldif** 必须放置在一个空目录中。建议按照以下方式创建目录：

```sh
# mkdir /usr/local/etc/openldap/slapd.d/
```

导入配置数据库：

```sh
# /usr/local/sbin/slapadd -n0 -F /usr/local/etc/openldap/slapd.d/ -l /usr/local/etc/openldap/slapd.ldif
```

启动 **slapd** 守护进程：

```sh
# /usr/local/libexec/slapd -F /usr/local/etc/openldap/slapd.d/
```

可以使用 `-d` 选项进行调试，具体使用方法请参见 slapd(8)。要验证服务器是否正在运行并且工作正常，可以运行以下命令：

```ini
# ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
# extended LDIF
#
# LDAPv3
# base <> with scope baseObject
# filter: (objectclass=*)
# requesting: namingContexts
#

#
dn:
namingContexts: dc=domain,dc=example

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
```

服务器仍然需要信任。如果以前没有进行过此操作，请按照以下说明进行操作。安装 OpenSSL 软件包和 Ports ：

```sh
# pkg install openssl
```

从 **ca.crt** 存储的目录（在此示例中为 **/usr/local/etc/openldap**）中，运行以下命令：

```sh
# c_rehash .
```

现在，CA 和服务器证书将在各自的角色中正确识别。要验证这一点，可以从 **server.crt** 目录运行以下命令：

```sh
# openssl verify -verbose -CApath . server.crt
```

如果 **slapd** 正在运行，请重新启动它。如 **/usr/local/etc/rc.d/slapd** 所示，要在启动时正确运行 **slapd**，必须将以下行添加到 **/etc/rc.conf** 中：

```sh
slapd_enable="YES"
slapd_flags='-h "ldapi://%2fvar%2frun%2fopenldap%2fldapi/
ldap://0.0.0.0/"'
slapd_sockets="/var/run/openldap/ldapi"
slapd_cn_config="YES"
```

**slapd** 在启动时不提供调试信息。可以查看 **/var/log/debug.log**、**dmesg -a** 和 **/var/log/messages** 来进行调试。

以下示例将组 `team` 和用户 `john` 添加到仍然为空的 `domain.example` LDAP 数据库中。首先，创建文件 **domain.ldif**：

```sh
# cat domain.ldif
dn: dc=domain,dc=example
objectClass: dcObject
objectClass: organization
o: domain.example
dc: domain

dn: ou=groups,dc=domain,dc=example
objectClass: top
objectClass: organizationalunit
ou: groups

dn: ou=users,dc=domain,dc=example
objectClass: top
objectClass: organizationalunit
ou: users

dn: cn=team,ou=groups,dc=domain,dc=example
objectClass: top
objectClass: posixGroup
cn: team
gidNumber: 10001

dn: uid=john,ou=users,dc=domain,dc=example
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
cn: John McUser
uid: john
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/john/
loginShell: /usr/bin/bash
userPassword: secret
```

请参考 OpenLDAP 文档了解更多详情。使用 **slappasswd** 将明文密码 `secret` 替换为 `userPassword` 中的哈希值。指定的 `loginShell` 路径必须在允许 `john` 登录的所有系统中存在。最后，使用 `mdb` 管理员修改数据库：

```sh
# ldapadd -W -D "cn=mdbadmin,dc=domain,dc=example" -f domain.ldif
```

对 *全局配置* 部分的修改只能由全局超级用户执行。例如，假设 `olcTLSCipherSuite: HIGH:MEDIUM:SSLv3` 最初被指定，现在必须删除。首先，创建一个包含以下内容的文件：

```sh
# cat global_mod
dn: cn=config
changetype: modify
delete: olcTLSCipherSuite
```

然后，应用修改：

```sh
# ldapmodify -f global_mod -x -D "cn=config" -W
```

在提示时，提供在 *配置后端* 部分选择的密码。此时不需要提供用户名：这里的 `cn=config` 代表要修改的数据库部分的 DN。或者，可以使用 `ldapmodify` 删除数据库中的一行，使用 `ldapdelete` 删除整个条目。

如果出现问题，或者全局超级用户无法访问配置后端，则可以删除并重新写入整个配置：

```sh
# rm -rf /usr/local/etc/openldap/slapd.d/
```

然后，可以编辑并重新导入 **slapd.ldif**。请仅在没有其他解决方案时遵循此过程。

这仅是服务器的配置。相同的机器还可以托管 LDAP 客户端，并具有自己的单独配置。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://handbook.bsdcn.org/di-32-zhang-wang-luo-fu-wu-qi/32.5.-qing-xing-mu-lu-fang-wen-xie-yi-ldap.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
