Paranoid Penguin - 使用 LDAP 进行身份验证

作者:Mick Bauer

上个月,我们做了一些初步工作来设置 OpenLDAP 服务器。我们安装了 OpenLDAP 的基础包、服务器包以及(如果适用)客户端包,并将一些基本配置信息输入到文件 /etc/openldap/slapd.conf 中 (slapd 是 OpenLDAP 的服务器守护进程)。

本月,我们将配置 TLS 加密,启动守护进程并开始构建 LDAP 数据库。我们还不会有一个完成的身份验证服务器,但我们将非常接近。下个月,在本系列的第三篇也是最后一篇中,我们将完成它。

用于安全 LDAP 事务的 TLS

默认情况下,通过网络的 OpenLDAP 事务以明文形式进行。例如,如果您在受信任的网络上将 OpenLDAP 用作集中式地址簿服务器,这可能没问题。但是,如果您使用它来验证用户身份,无论所涉及的网络是否受信任,您都应该加密您的 LDAP 通信,以保护用户的密码免受窃听者的侵害。

LDAP v.3 协议(OpenLDAP 2.0 中引入了对其的支持)以传输层安全 (TLS) 的形式提供加密,TLS 与 Web 浏览器和邮件传输代理使用的机制相同。TLS 是 SSL(安全套接字层)的后继者。您需要做的就是在 LDAP 服务器上创建服务器证书;在 /etc/openldap/slapd.conf 中添加几行代码,并可选择调整 slapd 的启动选项。

要生成服务器证书,您需要 OpenSSL。您的系统上应该已经存在 OpenSSL,因为 OpenLDAP 二进制包依赖于 OpenSSL。

您应该将哪种证书用作 LDAP 证书实际上是一个相当微妙的问题。服务器是否需要由其他证书颁发机构 (CA)(例如 Thawte 或 VeriSign)签名的证书?也就是说,您的 LDAP 客户端在连接到您的服务器时是否需要看到外部可验证的证书?或者,您的组织是否将成为自己的 CA?如果是这样,LDAP 服务器是否也将充当您的本地 CA,颁发和签署其自身以及其他主机和用户的证书?

如果您的需求符合这些场景中的任何一种,您将需要做更多的工作,我在这里无法详细描述。这里只需说明,slapd 使用的证书不能与密码关联——其密钥不能是 DES 加密的——因此,自签名证书虽然在技术上是 CA 证书,但不应被用作实际的 CA 证书来签署其他证书。如果您想将您的 LDAP 服务器用作真正的 CA,您需要创建两个密钥,一个受密码保护的 CA 密钥和一个无密码的 slapd 密钥。Vincent Danen 的文章“使用 OpenLDAP 进行身份验证”(www.mandrakesecure.net/en/docs/ldap-auth.php) 讨论了这一点。

对于许多(如果不是大多数)读者来说,创建一个仅 TLS 的自生成证书供 slapd 专用就足够了。如果您不关心成为 CA,并且您不需要您的 LDAP 客户端能够通过第三方验证服务器证书的真实性,您可以像这样创建您的证书

bash-$> openssl req -new -x509 -nodes \
-out slapdcert.pem -keyout slapdkey.pem \
-days 365
Using configuration from /usr/share/ssl/openssl.cnf
Generating a 1024 bit RSA private key
....++++++
.........++++++
writing new private key to 'slapdkey.pem'


在此命令行中,我告诉 OpenSSL 生成一个新的 X.509 证书,不进行密码保护,证书(公钥)存储在当前工作目录中的文件 slapdcert.pem 中,私钥存储在文件 slapdkey.pem 中,有效期为 365 天,

发出此命令后,系统将提示您输入新证书和密钥的 distinguished name 信息。对于 OpenLDAP 的目的,这里最重要的字段是通用名称。这必须设置为您的 LDAP 服务器的 DNS 名称,这是您的 LDAP 客户端将看到的与此证书关联的名称。例如,如果您的 LDAP 服务器的 IP 地址反向解析为 bonzo.lamemoviesfromthepast.com,但其服务器证书显示的 CN 为 bonzo.lm.com,则 LDAP 客户端将拒绝该证书,因此将无法协商 TLS 连接(结果不可预测,具体取决于您的客户端软件)。

一旦您获得了证书和密钥文件,如果您在创建它们时不在 /etc/openldap 目录中,请将它们复制到该目录中。确保这两个文件都归 ldap 或您的 Linux 发行版运行 slapd 的任何用户所有(Red Hat 和 SuSE 使用 ldap),并且您的密钥文件具有非常严格的权限,例如-r--------。但是,您的证书文件可能是世界可读的,因为它包含公钥。

可以在 -out 和 -keyout 选项后指定相同的文件名,从而将证书和私钥都存储在单个文件中。如果您不打算共享证书,这很好。但是,将两者分开可以允许您分发服务器证书,同时仍然保持服务器(私钥)的秘密。

当然,仅仅拥有证书/密钥文件是不够的;您需要告诉 slapd 使用它们。与大多数 slapd 配置一样,这在 /etc/openldap/slapd.conf 中完成。清单 1 显示了上个月专栏中的示例 slapd.conf 条目(以防您忘记了我们涵盖的内容),以及三个附加条目:TLSCipherSuite、TLSCertificateFile 和 TLSCertificateKeyFile。

清单 1. /etc/openldap/slapd.conf 的自定义部分

database        ldbm
suffix          "dc=wiremonkeys,dc=org"
rootdn          "cn=ldapguy,dc=wiremonkeys,dc=org"
rootpw          {SSHA}zRsCkoVvVDXObE3ewn19/Imf3yDoH9
directory       /var/lib/ldap
TLSCipherSuite  HIGH:MEDIUM:+SSLv2
TLSCertificateFile      /etc/openldap/slapdcert.pem
TLSCertificateKeyFile   /etc/openldap/slapdkey.pem

TLSCipherSuite 指定 OpenSSL 密码列表,slapd 将从中选择密码来协商 TLS 连接,按首选项降序排列。要查看本地 OpenSSL 安装支持哪些密码,请发出以下命令

openssl ciphers -v ALL

除了这些特定密码之外,您还可以使用 OpenSSL 支持的任何通配符,这些通配符允许您用一个词指定多个密码。例如,在清单 1 中,TLSCipherSuite 设置为 HIGH:MEDIUM:+SSLv2;碰巧的是,HIGH、MEDIUM 和 +SSLv2 都是通配符。

HIGH 表示“所有使用密钥长度大于 128 位的密码”;MEDIUM 是“所有使用密钥长度等于 128 位的密码”的缩写,+SSLv2 表示“SSL 协议版本 2 中指定的所有密码,无论密钥强度如何”。有关 OpenSSL 密码的完整说明,包括所有支持的通配符,请参阅 ciphers(1) 手册页。

TLSCertificateFile 和 TLSCertificateKeyFile 更为明显。它们分别指定证书文件和私钥文件的路径。如果证书和密钥都组合在单个文件中,您可以为这两个参数指定相同的路径。

slapd 启动选项

我们已经完成了使 TLS 加密工作所需的一切(在服务器端)。只剩下一个细节需要考虑。我们应该强制对来自网络的所有 LDAP 请求使用 TLS,还是保持可选?

默认情况下,slapd 侦听 TCP 端口 389 上的 LDAP 连接,并接受该端口上的明文或 TLS 加密连接。但是,如果您使用 LDAP 进行身份验证,您可能不希望使 TLS 成为可选的。在这种情况下,更好的方法是让 slapd 仅在环回接口上的 TCP 389 上侦听仅限明文的 LDAP 连接,并让 slapd 在 TCP 636(ldaps 的标准端口)上侦听所有其他本地地址的启用 TLS 的 (ldaps) 连接。

此行为由 slapd 的启动选项 -h 控制,用于指定 slapd 将响应的 URL。例如,slapd -h ldap://127.0.0.1/ ldaps:///告诉 slapd 侦听环回地址 (127.0.0.1) 以进行到默认 ldap 端口 (TCP 389) 的 ldap 连接,并侦听所有本地地址以进行到默认 ldaps 端口 (TCP 636) 的 ldaps 连接。

如果您运行 Red Hat 7.3 或更高版本,这实际上是默认行为:/etc/init.d/ldap 检查 /etc/openldap/slapd.conf 中的 TLS 配置信息,如果找到它,则将 -h 选项设置为与上一段示例中的选项完全相同。如果您运行 SuSE 8.1 或更高版本,您可以通过编辑 /etc/sysconfig/openldap 使 OPENLDAP_START_LDAPS 的值为 yes 来实现相同的目的,然后编辑 /etc/init.d/openldap 以将 SLAPD_URLS 的值设置为 ldap://127.0.0.1。此变量在脚本的早期定义,默认值为 ldap:///。

其他 Linux 发行版可能有不同的方式将 -h 等启动选项传递给 slapd,但希望现在您已经了解了思路,并且可以弄清楚如何使 slapd 的侦听端口按您想要的方式工作。

测试

那么,我们启用 TLS 的 LDAP 服务器是否真的工作?一个快速的本地测试将告诉我们。首先,启动 LDAP

bash-$ /etc/init.d/ldap start

接下来,使用 ldapsearch 命令通过环回进行简单查询

bash-$ ldapsearch -x -H ldaps:/// \
-b 'dc=wiremonkeys,dc=org' '(objectclass=*)'

当然,您自己的 LDAP 服务器将具有与 dc=wiremonkeys,dc=org 不同的基本 DN。如果您愿意,您可以从远程主机运行最后一条命令,在 -h 选项中使用 LDAP 服务器的名称或 IP 地址代替 localhost。如果 LDAP 服务器返回 LDAP 数据库的转储(此时实际上是空的),然后返回字符串result: 0 Success,则您的测试已成功。

如果您收到有关无效证书的错误,请尝试将此行添加到客户端系统的 /etc/openldap/ldap.conf 文件中

TLS_REQCERT	allow

这允许您的 OpenLDAP 或基于 OpenLDAP 的客户端软件(例如,gq)接受自签名服务器证书。

LDAP 模式

您几乎可以开始填充 LDAP 数据库了。一方面,像 gq 和 ldapbrowser 这样的工具可以大大减少 LDAP 数据输入和管理的丑陋和辛劳。但是,要达到可以使用这些工具的地步,您首先必须确定 LDAP 模式的组合,而这可能是事情变得令人不愉快的地方。

出于本文讨论的目的,两种类型的 LDAP 数据很重要:属性和对象类。属性是构成记录的事物。用户的电话号码、电子邮件地址、昵称等都是属性。您可以在 LDAP 数据库中使用任意数量的属性。您甚至可以发明自己的属性。但是,为了使记录包含给定的属性,该记录必须与正确的对象类关联。

对象类描述了您尝试构建的记录类型。它定义了每个记录哪些属性是强制性的,哪些属性是可选的。您可能会想,“这很容易,那么我只需要选择一个对象类型,该类型提供我想为用户存储的属性组,并将每个用户记录与该对象类关联。” 如果您这样想,那么您只对了一半。

在实践中,您可能希望使用来自各种对象类的属性。“好吧,很好”,您想,“我只需在每个用户记录中指定多个对象类,并获得我完整的属性集à la carte。随便吧。” 又对了,但还有更多内容。您需要的属性所在的对象类很可能分布在许多模式文件中(这些是文本文件,每个文件都包含属性列表和引用它们的对象类)。因此,甚至在您可以开始编写用户记录之前,每个记录都包含一堆对象类语句和更大一堆属性设置,首先您需要确保 /etc/openldap/slapd.conf 包含您需要的所有模式文件的 include 语句,通常存在于 /etc/openldap/schema 中。

例如,假设因为我们将使用我们的示例 LDAP 服务器进行身份验证,我们想确保无论如何,我们都能够指定属性 userid 和 userPassword。快速grep在 /etc/openldap/schema 中的文件中显示,uid 出现在 inetorgperson.schema 文件中,在 inetOrgPerson 对象类的 MAY 列表(允许的属性列表)中。这有两个后果。首先,/etc/openldap/slapd.conf 需要包含以下行

include   /etc/openldap/schema/inetorgperson.schema

其次,每当我创建用户记录时,我都需要确保objectclass: inetOrgPerson语句存在。

创建和添加用户记录

那么,您如何创建用户记录?理想情况下,使用您选择的 GUI。上个月我提到了 gq,它是许多发行版中的标准软件包;另一个优秀的工具是 ldapbrowser,可在 www.iit.edu/~gawojar/ldap 获取。但是,最初,您可能希望至少手动添加您的组织条目,方法是创建一个 ldif 文件并通过 ldapadd 命令将其写入数据库。ldif 文件是一个文本文件,其中包含属性/对象类声明列表,每行一个;下面是一个简单的示例

dn: dc=wiremonkeys,dc=org
objectclass: top
objectclass: organization
o: Wiremonkeys of St. Paul

在这里,我们定义了组织 wiremonkeys.org。我们指定了它的 distinguished name,将其与对象类 top(所有记录的强制性对象类)和 organization 相关联,并指定了组织的名称 (Wiremonkeys of St. Paul),这是这两个对象类的唯一强制性属性。

要将此记录写入数据库,请发出以下命令

bash-$ ldapadd -x -H ldaps:/// \
-D "cn=ldapguy,dc=wiremonkeys,dc=org" \
-W -f wiremonkeys_init.ldif

与大多数 OpenLDAP 命令一样,-x 指定简单密码身份验证,-H 指定 LDAP 服务器的 URL,-D 指定管理员帐户的 DN,-W 导致提示输入管理员密码。-f 选项指定我们的 ldif 文件的路径。

仍然感到困惑吗?我在本月的专栏中 packed a lot of information,但我们的 LDAP 服务器已经非常接近完成了。为了在不等待下个月的情况下完成您的服务器,请参阅 OpenLDAP 管理员指南,网址为 www.openldap.org/doc,以获取有关 TLS、启动标志、模式和 ldif 文件的更多信息。

Mick Bauer,CISSP,是Linux Journal的安全编辑,也是明尼苏达州明尼阿波利斯的 Upstream Solutions LLC 的 IS 安全顾问。Mick 花费了他大量的空闲时间追逐小孩(严格来说是他自己的小孩)和演奏音乐,有时同时进行。Mick 是 Building Secure Servers With Linux (O'Reilly & Associates, 2002) 的作者。

加载 Disqus 评论