Paranoid Penguin - 使用 LDAP 进行身份验证,第三部分
在过去几个月的 Paranoid Penguin 专栏中,我们一直在构建 LDAP 服务器。我们已经安装了 OpenLDAP;配置了服务器守护进程 slapd;实现了 TLS 加密;并创建了我们的第一个 LDAP 记录,一个根组织条目。现在,是时候添加一些用户并开始使用我们的服务器来验证 IMAP 会话了。
创建 LDAP 用户数据库的第一步是决定目录结构,包括是否对用户和其他实体进行分组,还是使用完全扁平的结构。如果您的 LDAP 数据库严格来说是一个在线地址簿或身份验证服务器,那么扁平数据库可能就足够了。在这种情况下,用户的专有名称 (DN) 应该如下所示dn=Mick Bauer,dc=wiremonkeys,dc=org.
但是,如果您的数据库不仅包含关于个别用户的信息,还包含关于组织子组或部门、网络上的计算机等等的记录,您可能需要使用更复杂的目录树结构。有很多种方法可以做到这一点。一种方法是使用 domainComponent (dc) 字段来创建您域名的子域,而不管这些子域是否实际存在于 DNS 中。该方法看起来像dn=Bick Mauer,dc=engineering,dc=wiremonkeys,dc=org。另一种方法是以相同的方式使用 organizationalUnit 对象,例如,dn=Dick Lauer,ou=engineering,dc=wiremonkeys,dc=org.
为了使本讨论保持简单,本文的其余部分我将使用扁平数据库;是否以及如何构建最符合您特定 LDAP 需求的 LDAP 数据库,将由您来决定。《OpenLDAP 软件文档》(可在 www.openldap.org 找到,并包含在 OpenLDAP 软件中)提供了充分的示例。
您需要做出的另一个决定是您希望为每个记录包含哪些 LDAP 属性。上个月,我描述了这些属性如何在模式中分组和相互关联。您可能还记得,您在 /etc/openldap/slapd.conf 中指定或包含的模式决定了您可以在记录中使用的属性。
除了在 /etc/openldap/slapd.conf 中包含模式之外,在您创建的每个记录中,您还需要使用 objectClass 语句将适当的模式与每个用户关联起来。同样,正如上次讨论的那样,/etc/openldap/schema 中的模式文件决定了哪些模式支持哪些属性,并且在给定的模式中,哪些对象类适用于这些属性。
假设您打算使用您的 LDAP 服务器来验证 IMAP 连接。此目的的基本 LDAP 属性是 uid 和 userPassword。这对于任何其他使用 Bind 方法向 LDAP 进行身份验证的应用程序也适用,其中身份验证服务只是尝试使用用户提供的用户名和密码绑定到 LDAP 服务器。如果绑定成功,则身份验证被判定为成功,并且 LDAP 连接被关闭。
确定哪些模式和对象类提供 uid 和 userPassword 的一种方法是grep搜索 /etc/openldap/schema 的内容,查找字符串 uid 和 userPassword,记下哪些文件包含它们,然后手动解析这些文件,以查找在 MUST() 或 MAY() 语句中包含这些属性的对象类。如果我在运行 OpenLDAP 2.0 的 Red Hat 7.3 系统上对 uid 执行此操作,我发现文件 core.schema、cosine.schema、inetorgperson.schema、nis.schema 和 openldap.schema 包含对 uid 属性的引用。
快速扫描这些文件(使用less)告诉我以下信息:core.schema 的对象 uidObject 需要 uid;cosine.schema 中对属性 uid 的唯一引用被注释掉,可以忽略;inetorgperson.schema 包含一个对象类 inetOrgPerson,它支持 uid 作为可选属性;nis.schema 包含两个对象类 posixAccount 和 shadowAccount,它们都需要 uid;openldap.schema 的对象类 OpenLDAPperson 也需要 uid。
幸运的是,有一种更快的方法可以确定相同的信息。gq LDAP 工具允许您浏览 LDAP 服务器上所有受支持的模式中的所有受支持属性。图 1 是一个屏幕截图,说明了根据 gq,我的 LDAP 服务器对 uid 的支持。
图 1 中的“Used in objectclasses(在对象类中使用)”框告诉我们,所选属性 uid 出现在对象类 uidObject、posixAccount、shadowAccount 和 inetOrgPerson 中,所有这些都是我们之前使用grep识别出来的。对象类 OpenLDAPperson 没有出现在 gq 屏幕中,因为所讨论的 LDAP 服务器的 /etc/openldap/slapd.conf 文件中没有针对文件 openldap.schema 的 include 语句。当您不确定是否需要模式时,您应该包含您不确定的模式。在您确定 LDAP 记录格式后,您可以随时取消包含不包含您需要的对象类的模式。
所有这些听起来可能很麻烦,而且确实可能很麻烦,但是能够创建包含与您的 LDAP 需求相关的信息类型的记录非常重要。因为 LDAP 非常灵活,所以弄清楚如何以属性的形式精确地组装这些信息可能需要一些调整。
正如模式浏览可以手动完成或使用 GUI 完成一样,添加 LDAP 记录也是如此。上个月我们使用了手动方法来创建我们的根组织条目,我们将再次这样做来添加我们的第一个用户记录。此方法有两个步骤:首先,创建一个 LDIF 格式的特殊文本文件,然后使用 ldapadd 命令将其导入到 LDAP 数据库中。考虑清单 1 中的 LDIF 文件。
清单 1. 用户记录的 LDIF 文件
dn: cn=Wong Fei Hung,dc=wiremonkeys,dc=org cn: Wong Fei Hung sn: Wong givenname: Fei Hung objectclass: person objectclass: top objectclass: inetOrgPerson mail: wongfh@wiremonkeys.org telephonenumber: 651-344-1043 o: Wiremonkeys uid: wongfh
因为它们决定了其他一切,所以我们将首先检查清单 1 的 objectclass 语句:此用户已与对象类 top(所有记录都必须包含)、person 和 inetorgperson 关联。我选择 person 是因为它支持属性 userPassword(在清单 1 中未设置 not;我们稍后将设置 Wong 先生的密码)和 telephonenumber,我现在不需要它,但将来可能会需要。对象类 inetOrgPerson 支持 uid 属性,以及许多其他属性,这些属性也可能在以后派上用场。
绕过必须知道并遵守模式中的 MUST 和 MAY 限制的一种方法是添加语句schemacheck off到 /etc/openldap/slapd.conf。这允许您使用 slapd.conf 中包含的任何模式文件中定义的任何属性,而无需关注对象类。但是,它也会对您的 LDAP 服务器与其他 LDAP 服务器甚至与其他应用程序的互操作性产生不利影响(除了违反 LDAP RFC),因此许多 LDAP 专家认为以这种方式禁用模式检查是不良做法。
没有必要讨论清单 1 中的每一行;许多属性都是不言自明的。简而言之,要知道您不需要设置您打算使用的每个属性,但有些是强制性的;它们包含在其各自对象类定义中的 MUST() 语句中。您定义的每个属性都必须在记录中定义的至少一个对象类的 MUST() 或 MAY() 语句中指定,并且某些属性(例如 cn)可以在同一记录中定义多次。
要添加清单 1 中指定的记录,请使用 ldapadd 命令
$ ldapadd -x \ -D "cn=ldapguy,dc=wiremonkeys,dc=org" \ -W -f ./wong.ldif
这类似于我们在上个月的专栏中使用 ldapadd 的方式。有关此命令语法的完整说明,请参阅 ldapadd(1) 手册页。
如果您指定了 LDIF 文件中设置的所有对象类所需的属性,如果您指定的所有属性都受这些对象类支持,并且您在提示时提供了正确的 LDAP 绑定密码,则该记录将被添加到数据库中。但是,如果这些条件中的任何一个为假,则操作将失败,并且 ldapadd 会告诉您哪里出了问题。因此,您可以使用试错法来制作可行的记录格式。在您第一次弄清楚这一点之后,您可以对后续记录使用相同的格式,而无需经历所有这些模式引起的混乱。
我提供一个警告:假设您的 LDIF 文件包含多个记录(这是允许的),如果您的 LDAP 服务器检测到错误,它会停止解析该文件,并且不会尝试添加失败记录下方的任何记录。因此,在您最终确定记录格式之前,您应该坚持使用单记录 LDIF 文件进行前几个用户添加。
手动记录创建方法有点笨拙,但它可以进行一定程度的调整。这在 LDAP 数据库构建的早期阶段尤其有用。
一旦您有一个或两个用户记录到位,您可以使用 GUI 工具(例如 LDAP Browser/Editor (www.iit.edu/~gawojar/ldap))或 gq(包含在大多数 Linux 发行版中)来创建其他记录。例如,在 gq 中,左键单击记录会弹出一个菜单,其中包含选项“New→Use current entry(新建→使用当前条目)”,该选项将所选记录复制到新记录中。这比手动将所有内容键入 LDIF 文件要快得多也简单得多。
我在清单 1 的描述中提到,我们通常不在 LDIF 文件中指定用户密码。为此使用了一个单独的机制,即 ldappasswd 命令。按照设计,它的语法与 ldapadd 类似
bind-$ ldappasswd -S -x -D "cn=hostmaster,dc=upstreamsolutions,dc=com" \ -W "cn=Phil Lesh,dc=upstreamsolutions,dc=com"
您无需登录到 LDAP 服务器上的 shell 会话即可使用 ldappasswd 命令。相反,您可以使用 -H 选项来指定远程 LDAP 服务器的 URL,如下所示
$ ldappasswd -S -x \ -H ldaps://ldap.upstreamsolutions.com \ -D "cn=hostmaster,dc=upstreamsolutions,dc=com" \ -W "cn=Phil Lesh,dc=upstreamsolutions,dc=com"
此选项也可以与 ldapadd 一起使用。
在上面的示例中,需要 ldaps:// URL。我已经为简单的明文身份验证指定了 -x 选项,所以我肯定需要使用 TLS 加密而不是明文连接到服务器。上个月,我展示了如何设置 LDAP 服务器以接受 TLS 连接。
但是,说了这么多,我必须指出,最终用户的密码管理是 LDAP 的问题领域之一。一方面,如果您的用户都可以访问 ldappasswd 命令,则您可以结合本地 /etc/ldap.conf 文件和 ldappasswd 的脚本/前端,使用户可以相当容易地更改自己的密码。
但是对于运行其他操作系统的用户,您必须集中管理密码,并让所有用户每次需要更改密码时都联系电子邮件管理员,或者您必须为他们的操作系统安装 LDAP 客户端软件。对于运行 Microsoft Windows 的客户端系统,您可以配置 Samba 以允许用户使用 Windows 密码工具更改其 LDAP 密码。请参阅 LJ 2002 年 12 月的文章“OpenLDAP Everywhere”。
从技术上讲,我们已经涵盖或涉及了构建使用 OpenLDAP 的 LDAP 服务器所需的所有任务(必然不包括有时需要很长时间才能真正让您的各种服务器应用程序成功地针对它验证用户的步骤)。为了保证强大的安全性,这个概念对于本专栏的读者来说并不陌生,我们需要讨论另一件事:OpenLDAP 访问控制列表 (ACL)。
与影响 slapd 守护进程的大多数其他事情一样,ACL 在 /etc/openldap/slapd.conf 中设置。而且,与大多数其他涉及 LDAP 的事情一样,ACL 可能会令人困惑,至少通常需要一些调整才能正确设置。清单 2 显示了一组 ACL 示例。
清单 2. /etc/slapd.conf 中的 ACL
access to attrs=userPassword by dn="cn=ldapguy,dc=wiremonkeys,dc=org" write by self write by * compare access to * by dn="cn=ldapguy,dc=wiremonkeys,dc=org" write by users read by * auth
ACL 在 slapd.conf(5) 手册页中详细描述,但在清单 2 中,您可以大致了解它们的工作原理。对于您希望控制访问的每个 LDAP 元素,您需要指定谁可以访问它以及具有什么级别的访问权限。从技术上讲,整个 ACL 可以列在一行中,但按照惯例,我们将每个“by...”语句列在自己的行中。slapd 非常智能,足以知道字符串“access to”标记着下一个 ACL 的开始。
空间不允许我详细描述 ACL 语法,但请记住几个重要的点。首先,ACL 从上到下解析,并且第一个匹配获胜;它们就像一堆过滤器。因此,至关重要的是,您要将特定的 ACL 和 by 语句放在更通用的 ACL 和 by 语句之上。例如,在清单 2 中,我们看到一个 ACL 限制对 userPassword 属性的访问,然后是一个适用于 * 的 ACL,即整个 LDAP 数据库。将 userPassword ACL 放在首位意味着允许用户更改自己的密码的规则(access to attrs=userPassword by self write)是更通用的规则(用户可以读取任何内容,access to * by users read)的例外。
另一个重要的点是访问级别是分层的。可能的级别是 none、auth、compare、search、read 和 write,其中 none 是最低的访问级别,write 是最高的访问级别,并且每个级别都包含低于它的所有级别的权限。这两个点,第一个匹配获胜规则和访问级别的包含性质,对于理解 ACL 的解析方式至关重要。它们对于确保您的 ACL 不会导致比您在给定情况下预期的更高或更低的访问级别也很重要。
LDAP 是我个人最近使用过的最复杂的技术之一。为了使其以您需要的方式工作,您必须花费大量时间进行测试,同时查看日志并微调 LDAP 服务器和您希望针对其进行身份验证的应用程序的配置。但是,拥有如此灵活、强大且广泛支持的身份验证和目录机制是非常值得的。我希望这一系列文章已帮助您到达那里,或者至少为您指明了正确的方向。
资源
OpenLDAP 软件和文档,包括重要的“OpenLDAP 管理员指南”:www.openldap.org。
LDAP 错误消息中使用的错误代码列表。这对于解释 LDAP 日志消息至关重要:www-user.tu-chemnitz.de/~fri/web500gw/errors.html。
Exchange Replacement HOWTO,其中描述了如何使用 LDAP 作为 Cyrus-IMAPD 的身份验证机制:www.arrayservices.com/projects/Exchange-HOWTO/html/book1.html。
Carter, Gerald. LDAP 系统管理. Sebastopol, California: O'Reilly & Associates, 2003. 一本关于 OpenLDAP 的详细介绍的优秀新书。
Mick Bauer,CISSP,是Linux Journal 的安全编辑,也是明尼苏达州明尼阿波利斯市 Upstream Solutions LLC 的信息安全顾问。Mick 将他大量的空闲时间花在追逐小孩(严格来说是他自己的小孩)和演奏音乐上,有时同时进行。Mick 是 使用 Linux 构建安全服务器(O'Reilly & Associates,2002)的作者。