> For the complete documentation index, see [llms.txt](https://handbook.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://handbook.bsdcn.org/di-33-zhang-wang-luo-fu-wu-qi/33.5.-qing-xing-mu-lu-fang-wen-xie-yi-ldap.md).

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

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

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

## 33.5.1. LDAP 术语和结构

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

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

```sh
% ldapsearch -xb "uid=trhodes,ou=users,o=example.com"
# 扩展 LDIF
#
# LDAPv3
# 基准 <uid=trhodes,ou=users,o=example.com>，范围为 subtree
# 过滤器: (objectclass=*)
# 请求: 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: 2
result: 0 Success

# 响应数: 2
# 条目数: 1
```

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

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

## 33.5.2. 配置 LDAP 服务器

FreeBSD 不提供内建的 LDAP 服务器。首先安装软件包或 Port [net/openldap-server](https://cgit.freebsd.org/ports/tree/net/openldap-server/)：

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

[软件包](https://docs.freebsd.org/en/articles/linux-users/#software) 中启用了大量的默认选项。运行 `pkg info openldap-server` 查看它们。如果这些选项不够（例如需要 SQL 支持），请考虑使用适当的 [框架](/di-4-zhang-an-zhuang-ying-yong-cheng-xu-ruan-jian-bao-he-ports/4.5.-shi-yong-ports.md) 重新编译 Port。

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

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

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

```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)。要验证服务器是否正在运行并且工作正常，可以运行以下命令：

```sh
# ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
# 扩展 LDIF
#
# LDAPv3
# 基准 <>，范围为 baseObject
# 过滤器: (objectclass=*)
# 请求: namingContexts
#

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

# 搜索结果
search: 2
result: 0 Success

# 响应数: 2
# 条目数: 1
```

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

```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 客户端，并具有自己的单独配置。
