Linux 系统管理高级教程(八)
原文:Pro linux system administration
协议:CC BY-NC-SA 4.0
十五、使用 VPN 联网
在前几章中,我们讨论了您的组织可能实现的许多服务(例如,电子邮件和 web 服务)。我们向您展示了向您的用户和客户提供这些服务的各种方式,包括通过互联网,以及向移动用户和位于其他站点的用户提供这些服务。但是,有些服务在本地提供更容易、更安全(例如文件和打印服务)。如果您的用户不在本地,那么您需要某种方式将他们连接起来,就像他们在本地一样。输入虚拟专用网络(VPN)。
本质上,VPN 是一个运行在公共或敌对网络上的私有网络。VPN 通常被称为隧道,它们用于保护您希望在其他公共网络(如互联网)上保密的流量。VPN 可以从网络设备或主机发起。例如,它们可以在两个办公室之间进行,或者从台式机或笔记本电脑等客户端发送到办公室。通过 VPN 运行的流量通常通过 TLS/SSL 证书、密码或双因素身份验证机制(如令牌或智能卡)等机制进行加密和身份验证。
VPN 是常见的,家庭和企业级防火墙通常会支持它们。
在本章中,我们将向您展示如何安装创建 VPN 所需的软件(在我们的例子中,是一个名为 OpenVPN 的开源 VPN 工具),以及如何配置和生成 VPN 隧道。
我们的示例网络
在本章中,我们将演示各种 VPN 连接,并且我们将使用我们在第七章中创建的示例网络来配置我们的示例环境和网络。图 15-1 再次显示网络。
图 15-1。
Our example network
目前,在我们的示例网络中,我们的总部有两台主机:
- gateway.example.com:我们的堡垒主机。它的外部 IP 地址为 10.0.2.155,内部 IP 地址为 192.168.0.254。
- headoffice.example.com:我们主要的总部服务器主机。它的内部 IP 地址为 192.168.0.1,外部连接通过网关主机出去。
Note
我们在第十章中展示了如何创建这些域名CNAMEs(例如gateway和headoffice)。
我们还有三个分支机构,每个都有自己的内部和外部 IP 地址范围。
- branch1.example.com:外部 IP 地址为 10.0.2.156 的分支机构主机。该分支机构的内部 IP 地址范围为 192.168.10.0/24。
- branch2.example.com:外部 IP 地址为 10.0.2.157 的分支机构主机。该分支机构的内部 IP 地址范围为 192.168.20.0/24。
- branch3.example.com:外部 IP 地址为 10.0.2.158 的分支机构主机。该分支机构的内部 IP 地址范围为 192.168.30.0/24。
OpenVPN 简介
OpenVPN ( http://openvpn.net/ )是由 James Yonan 编写的开源 SSL VPN 应用程序,可以在 GNU GPLv2 许可证(OpenVPN v2)和 AGPL (OpenVPN v3)下获得。还有一个单独的 EULA 下的商业版本。您还可以选择使用 AWS Marketplace 捆绑的 EC2 实例,它允许您按小时付费,并消除了一些设置它的管理负担。
它以客户机/服务器模式工作,服务器运行在您的主机上,客户机连接到服务器并创建 VPN 隧道。
Note
其他基于 Linux 的 VPN 解决方案也是可用的,包括 Openswan 等 IPsec 实现。
OpenVPN 运行在各种平台上,包括 Linux、Solaris、macOS 和 Microsoft Windows。这允许您将各种客户机连接到您的 Linux 主机;例如,您可以从运行 Microsoft Windows 的台式机或笔记本电脑连接 VPN 隧道。OpenVPN 甚至可以在运行 Android、苹果 iPhone 或 Windows Mobile 的移动设备上运行。您可以使用它从这些类型的设备创建 VPN 隧道,以允许您安全地访问内部网络中的资源。
OpenVPN 也可以配置成使用几种不同的认证服务。我们将向您展示如何使用普通的 Linux PAM 模块进行身份验证,但是您也可以使用 PAM 针对 LDAP 和 RADIUS 之类的服务进行身份验证。
在接下来的部分中,我们将演示如何在各种配置中安装和设置 OpenVPN。
安装 OpenVPN
您需要在连接的两端安装 OpenVPN。对于主机来说,这意味着在两端安装 OpenVPN 服务器。如果连接的一端是支持连接到 OpenVPN 的网络设备,那么您只需要在将用作隧道端点的主机上安装服务器。
我们将从在示例网络的总部分支机构中的堡垒主机gateway.example.com上安装服务器开始,该主机的内部 IP 地址为 192.168.0.254,外部 IP 地址为 192.0.2.155。
OpenVPN 可以在 CentOS 和 Ubuntu 上运行,并且可以通过正常的包管理方法安装。在 CentOS 上,您将从 EPEL 库获得最新的 OpenVPN 包。Ubuntu 目前落后几个小版本。
要查看您的操作系统上当前可用的版本,请发出以下命令之一:
$ sudo aptitdue show openvpn (Ubuntu)
$ sudo yum info openvpn (CentOS)
在 CentOS 上,我们首先安装 EPEL 存储库(如果尚未安装),然后安装软件包。
$ sudo yum install epel-release
$ sudo yum install openvpn
在 Ubuntu 上,你安装openvpn包,一般也会安装一些额外的先决条件。
$ sudo aptitude install openvpn
现在让我们看看停止和启动服务。
启动和停止 OpenVPN
OpenVPN 在您的主机上作为服务运行。Ubuntu 和 CentOS 上的openvpn包将安装适当的 Systemd 服务脚本。
然而,对于 OpenVPN,我们启动和停止服务的方式略有不同。以 CentOS 为例,我们需要传递想要启动的 OpenVPN 配置的名称。systemctl命令可以接受参数。查看 OpenVPN 的 Systemd 服务文件,您会看到以下内容:
ExecStart=/usr/sbin/openvpn --daemon --writepid /var/run/openvpn/%i.pid --cd /etc/openvpn/ --config %i.conf
你注意到%i了吗?我们需要在启动openvpn服务时指定配置文件(--config %i.conf)。我们使用带有@符号的systemctl命令来完成这项工作。
$ sudo systemctl start service@configuration
systemctl命令将把%i.conf外推至我们在openvpn@之后传入的配置名。在下面的命令中,我们将开始在/etc/openvpn/gateway.conf中定义的配置:
$ sudo systemctl start openvpn@gateway
您使用 Systemd enable命令来确保它在启动时启动。
$ sudo systemctl enable openvpn@gateway
在 Ubuntu 上,我们只需运行以下命令:
$ sudo systemctl start openvpn
当我们启动 OpenVPN 时,这些配置服务文件将被自动加载,服务器将尝试启动指定的 VPN。
配置 OpenVPN
正如我们前面提到的,我们需要在任何连接的两端配置 OpenVPN。我们将通过在总部的堡垒主机上设置 OpenVPN 服务器来开始我们的配置,然后我们将配置到分公司的连接。像这样的两个办公室之间的连接被称为静态或点对点 VPN。最后,我们将向您展示如何为移动用户配置客户端,例如笔记本电脑或台式机。
我们建议的 VPN 配置
让我们快速查看一下我们提议的 VPN 隧道配置的网络图(见图 15-2 )。我们将在总公司分部和每个分部之间建立隧道。
图 15-2。
Point-to-point VPN configuration between the head office and branches
在我们的网关服务器上配置 OpenVPN
我们已经在我们总公司的堡垒服务器gateway.example.com上安装了 OpenVPN,现在我们将开始配置它。我们首先告诉 OpenVPN 一些关于我们配置的基础知识。我们将在/etc/openvpn目录下创建一个名为gateway.conf的文件,如清单 15-1 所示。
# Network configuration
dev tun
port 1194
proto udp
server 10.8.0.0 255.255.255.0
keepalive 10 120
# Logging configuration
log-append /var/log/openvpn.log
status /var/log/openvpn-status.log
verb 4
mute 20
# Security configuration
user nobody
group nobody
persist-key
persist-tun
# Compression
comp-lzo
Listing 15-1.The gateway.conf Configuration File
Note
我们将在继续进行的过程中用额外的选项扩展gateway.conf文件。
在清单 15-1 中,我们指定了许多选项。第一个选项是dev tun,即隧道设备。tun 设备是内核中用户空间程序可以使用的特殊软件网络接口。调谐器设备将接收原始 IP 数据包,其姊妹设备 tap 期待以太网帧。
我们将配置一个 tun 来运行我们的 VPN,我们将创建一个虚拟设备tun0,它将用于 VPN 连接。通过指定dev tun,我们还创建了一个路由 VPN。OpenVPN 可以创建两种类型的 VPN:桥接和路由。简单地说,桥接 VPN 在以太网级别将您的网络连接在一起,而路由 VPN 依靠 TCP/IP 网络将您的网络连接在一起。我们将实现一个路由 VPN(这是 tun 设备的原因),因为这些类型使用 IP 协议,它更具可扩展性,更适合我们的网络需求。
Note
如果您有兴趣阅读更多关于路由 VPN 和桥接 VPN 的区别,您可以在 http://openvpn.net/index.php/documentation/howto.html#vpntype 找到更多信息。有关 taps 或 tuns 的更多信息,请访问 https://www.kernel.org/doc/Documentation/networking/tuntap.txt 。
接下来,我们指定了两个选项,port 1194和proto udp。它们告诉 OpenVPN 在建立 VPN 连接时监听端口 1194 上的 UDP 流量。需要将防火墙配置为允许此端口上的流量接受传入的连接。
Tip
如果需要,您可以通过将这些选项更改为所需的端口和协议来更改端口并使用 TCP。
使用 Netfilter 防火墙和iptables命令,您可以确保流量通过主机上适当的端口,如清单 15-2 所示。
$ sudo firewall-cmd --zone public --permanent --add-port=1194/udp && sudo firewall-cmd --reload
Listing 15-2.OpenVPN Firewall Rules CentOS
这里我们添加了一条规则,允许传入的 UDP 流量到达端口 1194 上的tun0接口。
或者,如果您使用 TCP,您可以使用如下规则:
$ sudo firewall-cmd --zone public --permanent --add-port=1194/tcp && sudo firewall-cmd –reload
当然,对于 Ubuntu 来说是这样的:
$ sudo ufw allow 1194/udp
Tip
VPN 配置失败的最常见原因是防火墙问题。您应该使用tcpdump或nmap命令检查您的防火墙规则是否允许访问该端口。我们将在第七章中讨论防火墙的设置、规则和故障排除。
接下来在清单 15-1 中,服务器选项告诉 OpenVPN 服务器的 IP 地址和可供我们的 VPN 客户端使用的 IP 地址池。我们已经指定了默认网络 10.8.0.0/24。默认情况下,我们的 OpenVPN 服务器将采用地址 10.8.0.1,并将剩余的地址分配给传入的 VPN 连接。
我们已经指定了一个名为keepalive的选项,用于保持我们的连接打开。我们指定了keepalive选项和两个值,10和120。值10表示 OpenVPN 将每 10 秒 ping 一次连接,以检查它是否处于活动状态。值120表示 OpenVPN 等待响应的时间(秒)。如果在 120 秒内没有收到响应,那么 OpenVPN 将假定连接断开并通知我们。
然后我们添加了一些日志功能。第一个是log-append,它告诉 OpenVPN 记录到/var/log/openvpn.log文件。第二个选项status,输出一个状态文件,显示当前到日志文件的连接,在我们的例子中是/var/log/openvpn-status.log。第三个选项,verb,告诉 OpenVPN 要做多少日志记录。范围从 0 到 15,其中 0 表示不记录日志,15 表示最大记录日志。值为 4 会生成足够的日志来满足大多数目的。最后,mute减少来自同一类别的连续消息。
下一组配置选项为我们的 OpenVPN 服务器提供了一些额外的安全性。前两个选项user和group允许我们为 OpenVPN 指定一个用户和组来运行。这将放弃该进程拥有的任何权限(例如来自root用户的权限),并确保如果有人危害该进程,他将拥有在您的主机上利用该危害的有限权限。在 CentOS 上,您可以使用nobody用户和nobody组。在 Ubuntu 上,我们推荐你使用用户nobody和组nogroup。
user nobody
group nogroup
接下来的两个选项persist-tun和persist-key与特权的放弃有关。它们允许 OpenVPN 保留足够的权限来使用网络接口和 SSL 证书。
文件中的最后一个选项comp-lzo告诉 OpenVPN 对 VPN 隧道使用压缩。这提高了隧道的性能。
我们已经配置了 VPN 的基础,但是现在准备好了吗?还没有——我们还有一个步骤:认证。
配置 OpenVPN 身份验证
身份验证确保只有经过授权的主机才能启动 VPN 并进行连接。OpenVPN 允许多种认证机制,包括预共享密钥、双因素认证(如令牌)和 TLS/SSL 证书。
最基本的身份验证是预共享密钥,这是在您的主机上生成的静态密钥,然后分发到您想要连接的主机。您可以使用openvpn命令的--genkey选项生成一个静态密钥,如下所示:
$ sudo openvpn --genkey --secret /etc/openvpn/secret.key
这将在目录/etc/openvpn中一个名为secret.key的文件中创建一个包含密钥的文件,文件名和目录由--secret选项指定。您可以使用secret选项在 OpenVPN 配置中指定该文件的位置。
secret /etc/openvpn/secret.key
然后你可以复制这个文件,最好是以一种安全的方式,比如通过scp或 GPG 加密的文件共享(就像 https://keybase.io 提供的),或者通过加密和使用一个配置管理系统,并将其应用到其他主机的 OpenVPN 配置。
不过,我们不打算使用预共享密钥,因为它们有一些限制。它们最大的限制是只能有一个服务器和客户端连接(即,每个 VPN 隧道只能连接一个主机)。如果您想要连接多台主机或办公室,这不是一个理想的模式。例如,允许多个移动用户连接到您的总部是无效的。
相反,我们将使用证书来保护总公司和分公司之间的 VPN。要使用证书,我们需要创建证书并由证书颁发机构(CA)签名。另一种方法是使用加密或商业证书。商业证书可能会变得昂贵,让我们加密证书需要经常更新。我们将使用我们自己的 CA,因为它更便宜,而且我们可以管理更新周期。
Note
我们在第十一章中详细讨论了 CA 过程(如果你愿意,现在就参考那里)。
我们将向您展示如何使用我们在第十一章中创建的 CA 来创建和签署您自己的 CA。
我们首先需要为 VPN 服务器创建一个服务器证书。您应该记得,创建新证书过程的第一步是创建证书签名请求(CSR)和密钥。现在让我们通过首先切换到/etc/pki/tls目录并执行以下命令来实现这一点:
$ openssl req -new -newkey rsa:4096 -nodes -keyout private/gateway.example.com.key
-out gateway.example.com.req
我们已经在/etc/pki/tls/private目录中生成了一个 4096 位的 RSA 密钥,并创建了一个 CSR。系统将提示您填写必填字段(州、城市等。).您需要使用与您的证书颁发机构相同的值。对于 Common Name 字段,您应该指定服务器的完全合格的域名,在我们的例子中是 gateway.example.com。
我们需要登录到管理我们的 CA 的主机,签署我们的 CSR 请求并生成我们的公共证书。我们需要将 CSR 复制到 CA 主机,然后运行清单 15-3 中所示的命令。
$ cd /etc/pki/CA
$ sudo openssl ca -out gateway.example.com.cert -cert certs/cacert.pem -infiles gateway.example.com.req
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 1 (0x1)
Validity
Not Before: Oct 22 11:47:26 2016 GMT
Not After : Oct 22 11:47:26 2017 GMT
Subject:
countryName = AU
stateOrProvinceName = Victoria
organizationName = Example Inc
organizationalUnitName = IT
commonName = gateway.example.com
emailAddress = admin@example.com
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
A6:A4:16:17:32:D2:7B:03:D2:5C:5A:DE:85:29:51:BE:E4:73:EA:20
X509v3 Authority Key Identifier:
keyid:98:3E:03:EB:FF:8A:FF:E8:1A:BC:56:04:CA:BE:BC:DB:D2:FA:68:12
Certificate is to be certified until Oct 22 11:47:26 2017 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
Listing 15-3.Signing Our Server Certificate
首先,在我们的 CentOS CA 服务器上,我们切换到我们的/etc/pki/CA目录,然后运行openssl ca命令来签署我们的 CSR。这会输出一个由我们的 CA 签名的证书。我们现在有了我们的公共证书文件,gateway.example.com.cert。我们需要将这个文件复制到网关主机上的/etc/pki/tls/certs目录中。我们还需要复制 CA 根证书。
为此,我们在 CA 服务器和gateway服务器上都打开了 OpenSSH shell。然后我们使用cat程序将 public 和 cacert 证书打印到屏幕上。对于每个文件,我们将它们复制并粘贴到以下目录的gateway.example.com.cert和cacert.pem文件中:
[ca.example.come] $ sudo cat gateway.example.com.cert
[gateway.example.com] $ sudo vi /etc/pki/tls/certs/gateway.example.com.cert
[ca.example.come] $ sudo cat certs/cacert.pem
[gateway.example.com] $ vi /etc/pki/tls/certs/cacerts.pem
然后在gateway主机上,我们确保将私钥移动到正确的目录。
[gateway.example.com] $ sudo mv gateway.example.com.key /etc/pki/tls/private/gateway.example.com.key
在前面几行中,我们已经将 TLS 证书复制到了我们的gateway主机上的正确位置,包括 CA 的根证书cacert.pem。另外,gateway.example.com.key应该在我们的gateway.example.com主机上,并被移到适当的位置。
Note
除了私钥和证书文件,您还有一个 CSR 请求文件。当你的证书过期时,值得保留它。正如我们在第十一章中提到的,您可以再次使用这个请求来创建一个新的证书。
我们希望用正确的所有权和对密钥的一些受限权限来保护我们的证书和密钥。
$ sudo chown root:root /etc/pki/tls/{private,certs}/gateway.example.com.*
$ sudo chmod 0400 /etc//pki/tls/private/gateway.example.com.key
我们还需要创建一些 Diffie-Hellman 参数,即增强 VPN 会话安全性的加密参数。这仅在服务器上。该密钥用于生成公钥,通信方使用该公钥来生成双方将用于通信的共享秘密。(你可以在 https://wiki.openssl.org/index.php/Diffie_Hellman 阅读关于迪菲-海尔曼更详细的内容)。我们可以使用openssl命令来创建这些。
$ sudo openssl dhparam -out /etc/openvpn/dh2048.pem 2048
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
.........................................+..........................................
这里我们在/etc/openvpn目录中创建了一个名为dh2048.pem的文件。这是一个 2048 位的 DH 参数文件,我们已经使用2048选项指定了大小。在几年内,这将是一个足够安全的密钥大小,但是如果您想要更长时间地防止将来被破解,您可以将它增加到 4,096。
我们要快速做的另一件事是创建一个tls-auth键。这也用于通过使用预共享密钥为 TLS 通道提供更高的安全性。此 PSK 应该部署到所有服务器和客户端。
openvpn --genkey --secret /etc/openvpn/ta.key
现在我们告诉 OpenVPN 我们的新证书以及我们的 CA 证书和 DH 参数文件的位置(参见清单 15-4 )。
# Network configuration
dev tun
port 1194
proto udp
server 10.8.0.0 255.255.255.0
keepalive 10 120
# Certificate configuration
ca /etc/pki/tls/certs/cacert.pem
dh /etc/openvpn/dh2048.pem
cert /etc/pki/tls/certs/gateway.example.com.cert
key /etc/pki/tls/private/gateway.example.com.key
tls-auth ta.key 0
# Logging configuration
log-append /var/log/openvpn.log
status /var/log/openvpn-status.log
verb 4
mute 20
# Security configuration
user nobody
group nobody
persist-key
persist-tun
# Compression
comp-lzo
Listing 15-4.The gateway.conf Configuration File
您可以看到我们增加了四个选项。第一个是ca选项,指定 CA 证书的位置,在我们的例子中是/etc/pki/tls/certs/cacert.pem。下一个是dh,指定我们创建的 Diffie-Hellman 参数文件的位置,在我们的例子中是/etc/openvpn/dh2048.pem。最后,我们使用了cert和key选项来分别指定我们的证书和密钥文件的位置,在我们的例子中是/etc/pki/tls/certs/gateway.example.com.cert和/etc/pki/tls/private/gateway.example.com.key。
需要在服务器上将tls-auth键指定为tls-auth <key> 0,在客户端指定为tls-auth <key> 1。我们需要将tls-auth密钥复制给所有客户端。
我们可以如下启动我们的 OpenVPN 服务器:
$ sudo systemctl start openvpn@gateway
$ sudo systemctl status openvpn@gateway.service
● openvpn@gateway.service - OpenVPN Robust And Highly Flexible Tunneling Application On gateway
Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
Active: active (running) since Sat 2016-10-22 22:37:09 UTC; 1h 39min ago
Process: 7779 ExecStart=/usr/sbin/openvpn --daemon --writepid /var/run/openvpn/%i.pid --cd /etc/openvpn/ --config %i.conf (code=exited, status=0/SUCCESS)
Main PID: 7780 (openvpn)
...
您可以看到我们已经启动了 OpenVPN 服务器,并且您可以看到它何时启动了名为gateway.service的 VPN。VPN 以gateway.conf配置文件命名。
我们还可以在/var/log/openvpn.log文件中看到一些日志条目,如清单 15-5 所示。
Sat Oct 22 22:37:09 2016 us=712503 OpenVPN 2.3.12 x86_64-redhat-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [MH] [IPv6] built on Aug 23 2016
Sat Oct 22 22:37:09 2016 us=712511 library versions: OpenSSL 1.0.1e-fips 11 Feb 2013, LZO 2.06
Sat Oct 22 22:37:09 2016 us=724495 Diffie-Hellman initialized with 2048 bit key
Sat Oct 22 22:37:09 2016 us=724919 TLS-Auth MTU parms [ L:1542 D:1212 EF:38 EB:0 ET:0 EL:3 ]
Sat Oct 22 22:37:09 2016 us=724982 Socket Buffers: R=[212992->212992] S=[212992->212992]
Sat Oct 22 22:37:09 2016 us=725172 ROUTE_GATEWAY 10.0.2.2/255.255.255.0 IFACE=eth0 HWADDR=52:54:00:c5:83:ad
Sat Oct 22 22:37:09 2016 us=733863 TUN/TAP device tun0 opened
Sat Oct 22 22:37:09 2016 us=733929 TUN/TAP TX queue length set to 100
Sat Oct 22 22:37:09 2016 us=733946 do_ifconfig, tt->ipv6=0, tt->did_ifconfig_ipv6_setup=0
Sat Oct 22 22:37:09 2016 us=733963 /usr/sbin/ip link set dev tun0 up mtu 1500
Sat Oct 22 22:37:09 2016 us=740224 /usr/sbin/ip addr add dev tun0 local 10.8.0.1 peer 10.8.0.2
Sat Oct 22 22:37:09 2016 us=745857 /usr/sbin/ip route add 10.8.0.0/24 via 10.8.0.2
Sat Oct 22 22:37:09 2016 us=755742 Data Channel MTU parms [ L:1542 D:1450 EF:42 EB:143 ET:0 EL:3 AF:3/1 ]
Sat Oct 22 22:37:09 2016 us=756240 GID set to nobody
Sat Oct 22 22:37:09 2016 us=756256 UID set to nobody
Sat Oct 22 22:37:09 2016 us=756265 UDPv4 link local (bound): [undef]
Sat Oct 22 22:37:09 2016 us=756270 UDPv4 link remote: [undef]
Sat Oct 22 22:37:09 2016 us=756279 MULTI: multi_init called, r=256 v=256
Sat Oct 22 22:37:09 2016 us=756297 IFCONFIG POOL: base=10.8.0.4 size=62, ipv6=0
Sat Oct 22 22:37:09 2016 us=756313 Initialization Sequence Completed
Listing 15-5.The /var/log/openvpn.log Log File
清单 15-5 显示 OpenVPN 服务器已经启动,我们的接口tun0被创建,我们的 IP 地址 10.8.0.1 被添加并绑定到 UDP 端口 1194。
Note
确保您有正确的防火墙规则来允许 VPN 连接到您的主机,在这种情况下,需要接受 UDP 端口 1194 上的传入连接。
我们的服务器正在运行,但我们还没有完全完成。我们需要配置我们的客户端,安装 OpenVPN 软件,并为我们的客户端创建用于连接的证书。
Note
在本章中,我们将提到两台主机。当一个命令需要在一个特定的主机上运行时,我们将为它加上与该主机相同的前缀:[ca]或[branch1]$`。
在我们的分支机构服务器上配置 OpenVPN
首先,我们需要在我们的分支服务器branch1.example.com上安装 OpenVPN 包。遵循适用于您的发行版的说明,并将 OpenVPN 设置为在您的主机启动时启动,例如:
[branch1]$ sudo systemctl enable openvpn@branch1
Note
通过命令前缀[branch1]$,可以看出我们正在branch1.example.com主机上运行前面的命令。
下一步是为我们的每个分支机构创建一个证书和密钥。我们将在ca.example.com主机上创建我们的证书和密钥,并使用该主机上的 CA 对其进行签名。我们将从一个分支机构开始,branch1.example.com。
[ca]$ openssl req -new -newkey rsa:4096 -nodes -keyout branch1.example.com.key
-out branch1.example.com.req
我们已经生成了一个 4,096 位 RSA 密钥并创建了一个 CSR。系统将提示您填写必填字段:州、城市等等。您需要使用与您的证书颁发机构相同的值。对于 Common Name 字段,您应该指定服务器的完全合格的域名,在我们的例子中是branch1.example.com。
然后,我们使用我们的 CA 来签署我们的证书,就像前面所做的那样。
[ca]$ cd /etc/pki/CA
[ca]$ sudo openssl ca -out branch1.example.com.cert -cert certs/cacert.pem
-infiles branch1.example.com.req
现在,我们需要将我们的证书和密钥以及 CA 证书发送到分支服务器。有很多方法可以做到这一点,但是你必须安全地做——例如,不要用电子邮件发送,因为它们很容易被拦截。将它们放在 USB 密钥上,添加到您的配置管理系统(加密),并在目标服务器/客户机上本地安装它们。或者,如果您能够直接连接到主机,请使用scp(安全复制)或sftp(安全 FTP)命令发送文件。这些命令使用 SSH 连接安全地连接到另一台主机并传输文件。
在本例中,我们将使用sftp命令连接到我们的分支机构服务器并传输所需的文件。
[ca]$ sftp jsmith@branch1.example.com
Connecting to branch1.example.com...
jsmith@branch1.example.com's password:
sftp> put branch1.example.com.cert
Uploading /home/jsmith/branch1.example.com.cert to
/home/jsmith/branch1.example.com.cert
/home/jsmith/branch1.example.com.cert 100% 5881 12.0KB/s 00:03
我们已经连接到了branch1.example.com主机,并使用put命令将branch1.example.com.cert证书文件从它的位置(这里是/home/jsmith directory)传输到了branch1主机上的对等目录/home/jsmith。您可以使用cd命令切换到远程主机上您想要写入文件的适当目录。
sftp> cd /tmp
您将需要权限来写入该位置。如果您的文件位于其他地方,您可以使用cd命令来更改本地gateway.example.com主机上的目录。
我们使用put命令将branch1.example.com.key和cacert.pem CA 证书文件放到branch1主机上。因为这是一个 Ubuntu 服务器,我们现在要把我们的文件移到/etc/ssl目录,并保护它们的所有权和权限。在 CentOS 上,我们将再次使用/etc/pki/tls目录。
[branch1]$ sudo mv branch1.example.com.cert /etc/ssl/certs/
[branch1]$ sudo mv branch1.example.com.key /etc/ssl/private/
[branch1]$ sudo mv cacert.pem /etc/ssl/certs/
我们希望用正确的所有权和对密钥的一些受限权限来保护我们的证书和密钥。
branch1$ sudo chown root:root /etc/ssl{certs,private}/branch1.example.com.*
branch1$ sudo chown root:root /etc/ssl/certs/cacert.pem
branch1$ sudo chmod 0400 /etc/ssl/private/gateway.example.com.key
接下来,我们需要为我们的客户机创建一个配置文件。我们将在我们的/etc/openvpn目录中创建一个名为branch1.conf的文件,如清单 15-6 所示。
# Network configuration
dev tun
client
remote gateway.example.com 1194
keepalive 10 120
# Certificate configuration
ca /etc/ssl/certs/cacert.pem
cert /etc/ssl/certs/branch1.example.com.cert
key /etc/ssl/private/branch1.example.com.key
tls-auth ta.key 1
# Logging configuration
log-append /var/log/openvpn.log
status /var/log/openvpn-status.log
verb 4
mute 20
# Security configuration
user nobody
group nogroup
persist-key
persist-tun
# Compression
comp-lzo
Listing 15-6.The branch1.conf Configuration File
Note
如果你使用的是 CentOS,你的group设置应该设置为nobody而不是nogroup。
清单 15-6 中的文件类似于gateway.conf配置文件,但是我们指定了一些不同的选项,因为主机的角色是客户端。同样,我们已经为路由 VPN 指定了dev tun。我们还指定了client选项,它表明这是一个客户端,以及remote选项,它告诉 OpenVPN 在哪里连接我们的 VPN 隧道。我们已经指定了gateway.example.com和端口 1194。
Note
OpenVPN 必须能够解析该主机(即,它必须能够找到该主机的 IP 地址)。如果您没有 DNS(您应该有),那么您可以在此选项中直接指定一个 IP 地址。
我们还使用ca选项指定了我们的 CA 证书的位置,并分别使用cert和key选项指定了我们的客户端证书和密钥的位置。
在我们的分支机构服务器上启动 OpenVPN
在我们的服务器上配置好 VPN 后,我们现在可以在我们的客户机上启动 OpenVPN。例如,我们在 CentOS 上使用以下命令:
[branch1]$ sudo systemctl start openvpn@branch1
测试我们的 OpenVPN 隧道
您可以确定您的连接是否以多种方式工作,我们将带您了解所有这些方式。首先,您应该在branch1主机上的/var/log/openvpn.log文件中看到一些条目。您应该会看到类似于清单 15-5 中的条目,但是您也会看到我们的客户端连接到服务器时的协商过程。
Sun Oct 23 06:24:58 2016 us=729639 [gateway.example.com] Peer Connection Initiated
with [AF_INET]10.0.2.155:1194
Note
在这些示例中,您会看到不同的公共 IPv4 地址(10.0.2.155 和 10.0.2.156 ),它们与图 15-2 中的图表相匹配。
您还应该在显示连接的/var/log/openvpn.log文件中看到网关主机上的一些条目。
gateway$ less /var/log/openvpn.log
Sun Oct 23 06:25:06 2016 us=416152 branch1.example.com/10.0.2.156:1194 SENT CONTROL [branch1.example.com]: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5' (status=1)
此外,您可以看到在gateway和branch1主机上都创建了一个新接口,从tun开始。在网关主机上,您可以看到一个名为tun0的新接口,其 IP 地址为 10.8.0.1(如前所述)。
[gateway]$ ip addr show tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
link/none
inet 10.8.0.1 peer 10.8.0.2/32 scope global tun0
valid_lft forever preferred_lft forever
在branch1主机上,已经创建了一个接口,也称为tun0,其 IP 地址为我们服务器提供的地址池中的 10.8.0.6。
[branch1]$ ip addr show tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100
link/none
inet 10.8.0.6 peer 10.8.0.5/32 scope global tun0
valid_lft forever preferred_lft forever
您还可以看到branch1主机上的路由表(使用第七章中介绍的ip命令)有一条到我们 10.8.0.0/24 网络的路由。
branch1$ ip route show
sudo ip route show
default via 10.0.2.2 dev enp0s3
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15
10.8.0.1 via 10.8.0.5 dev tun0
10.8.0.5 dev tun0 proto kernel scope link src 10.8.0.6
192.168.0.0/24 dev enp0s8 proto kernel scope link src 10.0.2.156
通过tun0接口有一条到 10.8.0.1 主机的路由。
您会注意到在两台主机上都创建了一个名为/var/log/openvpn-status.log的文件。该文件包含当前连接的列表,每 60 秒刷新一次。让我们看看网关主机上的这个文件。
gateway$ less /var/log/openvpn-status.log
OpenVPN CLIENT LIST
Updated,Sun Oct 23 06:44:43 2016
Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since
branch1.example.com,10.0.2.156:1194,16384,16774,Sun Oct 23 06:24:30 2016
ROUTING TABLE
Virtual Address,Common Name,Real Address,Last Ref
10.8.0.6,branch1.example.com,10.0.2.156:1194,Sun Oct 23 06:33:01 2016
GLOBAL STATS
Max bcast/mcast queue length,0
END
您可以看到从 IP 地址为 10.0.2.156 的branch1主机列出的一个连接。
最后,您可以使用网络工具来测试您的实际连接。让我们从在网关主机上使用ping命令 ping 我们的branch1主机开始。我们将 ping 用作我们在branch1主机上的 VPN 隧道末端的地址,我们之前发现该地址是 10.8.0.6。
[gateway]$ ping 10.8.0.6 -c 3
PING 10.8.0.6 (10.8.0.6) 56(84) bytes of data.
64 bytes from 10.8.0.6: icmp_seq=1 ttl=64 time=0.593 ms
64 bytes from 10.8.0.6: icmp_seq=2 ttl=64 time=0.588 ms
64 bytes from 10.8.0.6: icmp_seq=3 ttl=64 time=0.807 ms
前面的代码向我们展示了网关主机可以看到使用 ICMP 的branch1主机,并且该branch1主机上的 10.8.0.6 IP 地址响应 ICMP 流量。
我们可以通过在网关主机端 ping IP 地址 10.8.0.1,从branch1主机做同样的事情。
branch1$
ping 10.8.0.1 -c 3
PING 10.8.0.1 (10.8.0.1) 56(84) bytes of data.
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=0.682 ms
64 bytes from 10.8.0.1: icmp_seq=2 ttl=64 time=0.672 ms
64 bytes from 10.8.0.1: icmp_seq=3 ttl=64 time=0.751 ms
如果两端都有响应,那么您的 VPN 就开通了,您可以使用它将流量从分公司路由到总公司。
如果现在需要,您可以为任何其他分支机构重复此配置。例如,在我们的例子中,我们可以添加来自branch2.example.com和branch3.example.com办公室的隧道。
使用 OpenVPN 暴露总部资源
根据目前的配置,我们可以通过 VPN 隧道在分公司和总部之间路由流量。让我们看看我们的总公司和分公司现在是如何互动的。
再看一下图 15-2 。目前,从我们的分支机构到我们的总部有两条路径。第一个是通过你在第七章中看到的 10.0.2.0/24 网络。这是我们办公室与互联网之间的 DSL 或 ASDL(或类似类型)互联网连接。每个单独的办公室通常都有单独的互联网连接。我们使用 10.0.2.0/24 网络作为他们的 IP 地址,但更有可能的情况是,每个办公室都有一个从 ISP 处获得的地址。
这个网络不安全,因为它是在互联网上运行的。除非我们保护特定的应用程序或协议(例如,在第十二章中我们使用 SSL/TLS 来保护我们的 SMTP 和 IMAP 服务),否则攻击者可以从该网络读取我们的数据。由于这一潜在的安全问题,我们通过此连接实例化了这些主机之间的第二条路径,即我们用于 VPN 隧道的 10.8.0.0/24 网络。我们的总部是 OpenVPN 服务器,IP 地址为 10.8.0.1。每个分支办公室都有一个该范围内的 IP 地址,例如,您已经看到了分配给branch1.example.com办公室的 10.8.0.6 IP 地址。
然而,目前通过我们的 VPN 隧道,我们只能到达 IP 地址 10.8.0.1。这对我们没有多大用处,因为我们无法访问内部网络上的任何可用资源(例如,headoffice.example.com主机上的文件共享)。我们可以通过试着 pingheadoffice主机,从我们的分支机构主机测试这一点:
branch1$ ping 192.168.0.1
我们不会从这些 pings 得到任何回复,因为没有到这个网络的路由。要访问这些资源,我们需要确保两个要素有序:路由和防火墙规则。我们将在接下来的章节中讨论这些。
选择途径
我们首先需要配置我们的分支机构,以路由到我们总部的内部网络。为此,我们告诉我们的分支机构,当它想要路由到 192.168.0.0/24 网络时,它需要通过 VPN 隧道到达网关主机。如清单 15-7 所示,我们通过在网关主机上的gateway.conf配置文件中添加一行来将路由推送到我们的分支主机。
push "route 192.168.0.0 255.255.255.0"
Listing 15-7.Push Route Added to gateway.conf
该行向连接到 OpenVPN 服务器的所有客户端添加一个路由。对于要推送的新路由,我们需要在网关主机和分支机构主机上重启openvpn服务。
如果我们查看我们的branch1主机上的路由表,我们可以在我们的路由中看到一条到 192.168.0.0/24 网络的新路由(粗体)。
[branch1]$ ip route
default via 10.0.2.2 dev eth0 proto static metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.8.0.1 via 10.8.0.5 dev tun0
10.8.0.5 dev tun0 proto kernel scope link src 10.8.0.6
169.254.0.0/16 dev eth1 scope link metric 1003
10.0.2.0/24 dev eth1 proto kernel scope link src 10.0.2.156
192.168.0.0/24 via 10.8.0.5 dev tun0
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.254
我们现在可以从branch1主机 ping 这个网络。
branch1$ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=1.18 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=1.31 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=2.33 ms
64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=1.25 ms
64 bytes from 192.168.0.1: icmp_seq=5 ttl=64 time=0.923 ms
您可以看到,我们的 branch1 主机收到了来自主机 192.168.0.1 的响应。我们可以访问网关主机上的防火墙允许我们访问 192.168.0.0/24 网络的任何内容。
如果branch1主机是 192 . 168 . 10 . 0/24(branch1站点的本地网络)的默认路由,那么 192.162.10.0/24 网络中的所有用户都将能够访问我们总部的 192.168.0.0/24 网络中的资源。
Note
我们不会告诉我们的总部如何路由到我们分支机构的内部网络,但这也是可能的。
防火墙
我们还需要确保网关和分支机构主机上的防火墙规则允许流量进出相关网络。在我们的网关主机上,这涉及到将 IP 流量从我们的网关主机转发到内部网络并返回,很像我们在第七章中创建的配置,用于从我们的堡垒主机gateway.example.com转发我们的服务。
首先,我们使用公共区域从 VPN 隧道接口tun0引导我们希望通过网关主机转发的流量。我们将把tun0接口添加到public区域。
[gateway]$ sudo firewall-cmd --zone public --permanent --add-interface tun0
然后,我们需要为这个区域创建一个规则集,以允许我们的网关主机通过该主机转发特定的流量,如清单 15-8 所示。
[gateway]$ sudo firewall-cmd --zone public --permanent --add-service=http
success
[gateway]$ sudo firewall-cmd --zone public --permanent --add-service=https
success
[gateway]$ sudo firewall-cmd --zone public --permanent --add-service=smtp
success
[gateway]$ sudo firewall-cmd --zone public --permanent --add-service=dns
success
[gateway]$ sudo firewall-cmd --zone public --permanent --add-service=ntp
success
[gateway]$ sudo firewall-cmd --zone public --permanent --add-service=imaps
success
Listing 15-8.Some Sample firewalld Rules for OpenVPN Routing
现在我们必须重新加载防火墙配置。
[gateway]$ sudo firewall-cmd --reload
我们创建了各种简单的规则来允许流量通过 VPN 隧道,并将其转发到我们的内部网络。我们转发了 SMTP、HTTP/HTTPS 和 IMAP 等协议。
Note
您可以使用第七章中提供的说明将这些规则添加到您的网关主机中。
移动用户的 VPN 连接
OpenVPN 不仅仅允许从分支机构到总部的点对点连接。您还可以使用它来允许移动用户连接到您的总部并访问文件共享、打印机和应用程序等资源。为此,我们需要在网关主机上设置另一个 VPN 隧道,并在客户机上安装 OpenVPN。正如我们在本章前面提到的,OpenVPN 可以运行在包括 Linux、Microsoft Windows、macOS 等平台上。
我们将对我们的移动用户做一些不同的事情。我们不打算使用证书(尽管我们可以使用它们)来认证我们的客户端,因为潜在的生成大量证书的开销可能相当高。
Note
利用 OpenVPN 使证书管理更容易的一个工具是easy-rsa。文档可以在这里找到: https://openvpn.net/index.php/open-source/documentation/miscellaneous/77-rsa-key-management.html 。
相反,我们将向您展示如何使用 PAM(我们在第五章中介绍过)来认证您的用户。由于 PAM 能够插入各种身份验证机制,我们可以使用它来包括如下身份验证机制:
- 本地 Linux 用户
- 二元身份认证,如 RSA 令牌或智能卡
- 麻省理工学院开发的安全认证系统
- 半径
- IMAP(针对 IMAP 服务器)
- 轻量级目录访问协议
我们将向您展示如何配置基本的本地用户身份验证,也就是说,您的用户将在网关主机上拥有一个 Linux 用户,他们将通过身份验证,就像使用控制台或通过 SSH 登录到该主机一样。使用 PAM,您可以很容易地将其扩展到其他形式的身份验证。
配置我们的移动 VPN
要开始我们的配置,我们需要一个新的.conf文件。我们将在/etc/openvpn中创建一个名为mobile.conf的,如清单 15-9 所示。
# Network configuration
dev tun
port 1195
proto udp
server 10.9.0.0 255.255.255.0
keepalive 10 120
# Certificate configuration
dh /etc/openvpn/dh2048.pem
ca /etc/pki/tls/certs/cacert.pem
cert /etc/pki/tls/certs/gateway.example.com.cert
key /etc/pki/tls/private/gateway.example.com.key
tls-auth ta.key 0
plugin /usr/lib64/openvpn/plugins/openvpn-plugin-auth-pam.so login
# Logging configuration
log-append /var/log/openvpn-mobile.log
status /var/log/openvpn-status-mobile.log
verb 4
mute 20
# Security configuration
user nobody
group nobody
persist-key
persist-tun
# Compression
comp-lzo
Listing 15-9.Mobile User’s mobile.conf Configuration File
Note
在本节的后面,当我们查看在我们的客户机上配置路由和相关功能时,我们将扩展这个配置。
你可以看到我们的mobile.conf配置类似于我们的gateway.conf VPN 隧道。我们将两者分开,以便机器使用 1194 上的服务器,漫游用户使用 1195,这将通过他们的用户名和密码得到进一步的保护。
让我们把重点放在不同点上。我们更改了一些网络配置:我们使用了不同的端口 1195,因为我们的另一个 VPN 隧道绑定到 1194 端口。管理员为移动用户使用 TCP 协议和端口 443 是很常见的;这是因为像 1195 或 1194 这样的端口可以在机场这样的地方被阻塞。我们还为移动用户指定了一个额外的 IP 子网 10.9.0.0/24。
我们需要确保为该子网制定合适的防火墙规则。首先,我们在tun1接口上为我们的隧道打开 1195 端口(对于我们的新隧道,接口号已经增加)。
[gateway]$ sudo firewall-cmd --zone public --permanent --add-port=1195/udp
Note
我们的新接口将是tun1,因为我们已经有了一个tun0接口。如果你没有另一个 VPN 隧道,那么你的接口可能是tun0。
我们指定了相同的证书、密钥、tls-auth和 DH 参数,但是我们添加了一个名为plugin的新选项。plugin配置选项允许我们指定外部插件,在我们的例子中,OpenVPN 包附带了一个名为openvpn-pluging-auth-pam.so的 PAM 插件。
在 Ubuntu 上,插件位于/usr/lib/openvpn/openvpn-plugin-auth-pam.so。
我们还需要指定一个 PAM 认证文件的名称,OpenVPN 将使用它来认证 VPN 隧道。在 Ubuntu 上,我们已经指定了标准的影子密码 PAM 认证文件passwd,作为要使用的认证机制。在 CentOS 上,我们可以指定system-auth默认 PAM 认证文件。
对于其他形式的身份验证,您可以在这里为该机制指定一个合适的 PAM 身份验证文件。例如,要启用双因素身份验证,您可以指定一个使用 Google Authenticator PAM 模块的文件pam_google_authenticator.so(参见 https://www.linux.com/learn/how-set-2-factor-authentication-login-and-sudo 以获取如何实现它的示例)。该模块允许您将 Google Authenticator 与 PAM 集成,以允许您提供一个令牌来认证用户。
我们已经更新了日志文件,以便为我们的移动连接创建新文件。我们还指定了user和group选项(在本例中,我们使用了 CentOS 上的nobody组)。
接下来,我们需要重启 OpenVPN 服务来启动我们的移动 VPN 隧道。
配置移动 VPN 客户端
您需要配置您的客户端以连接到网关,根据客户端的不同,您可以通过多种方式来完成。有许多客户端可用,从普通的 OpenVPN 二进制程序到复杂的 GUI 客户端。在这一节中,我们将提供一些可用客户端的列表,并向您展示如何通过 OpenVPN 进行连接。
最简单的客户端是 OpenVPN 二进制。我们将为mobile1.example.com创建并签署一个密钥和证书,就像我们为其他 TLS 证书所做的那样。我们将把它们放在和以前相似的地方,或者在/etc/pki/tls目录中,或者在/etc/ssl目录中,这取决于我们的主机。
如果您使用 OpenVPN 二进制文件,您还需要创建一个客户端配置文件。我们将调用我们的mobileclient.conf并将它存储在客户端的/etc/openvpn目录中,如清单 15-10 所示。
# Network configuration
dev tun
client
remote gateway.example.com 1195
keepalive 10 120
# Certificate configuration
ca /etc/ssl/certs/cacert.pem
cert /etc/ssl/certs/mobile1.example.com.cert
key /etc/ssl/private/mobile1.example.com.key
tls-auth ta.key 1
auth-user-pass
# Logging configuration
log-append /var/log/openvpn.log
status /var/log/openvpn-status.log
verb 4
mute 20
# Security configuration
user nobody
group nobody
persist-key
persist-tun
# Compression
comp-lzo
Listing 15-10.The mobileclient.conf Configuration File
您可以看到我们之前使用的选项,其中增加了一个选项,并进行了一些小的更改。我们已经把要连接的远程端口改成了 1195。我们还添加了auth-user-pass选项,它告诉客户端我们将使用用户名和密码,而不仅仅是证书认证。
现在,如果我们在客户机上启动 OpenVPN,它将连接到网关,提示用户输入适当的凭证。
$ sudo /etc/init.d/openvpn restart
Shutting down openvpn [ OK ]
Starting openvpn:
Enter Auth Username:jsmith
Enter Auth Password:********
请注意验证用户名和密码提示。我们已经在网关主机上输入了一个用户的用户名jsmith,以及他的密码。
然后客户端将会连接,您应该能够看到一个新的界面(在我们的例子中是tun1)。
$ ip addr show tun0
10: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
link/none
inet 10.8.0.6 peer 10.8.0.5/32 scope global tun0
valid_lft forever preferred_lft forever
接口有网关主机下发的 IP 地址 10.9.0.6(记住,我们把我们的移动客户端 VPN 网络设置为 10.9.0.0/24)。然后,您可以 ping 网关主机的这个 IP 地址(在我们的例子中是 10.9.0.1 ),反之亦然,返回到 10.9.0.6。
$ ping 10.9.0.1
PING 10.9.0.1 (10.9.0.1) 56(84) bytes of data.
64 bytes from 10.9.0.1: icmp_seq=1 ttl=64 time=10.3 ms
64 bytes from 10.9.0.1: icmp_seq=2 ttl=64 time=10.64 ms
64 bytes from 10.9.0.1: icmp_seq=3 ttl=64 time=10.59 ms
64 bytes from 10.9.0.1: icmp_seq=4 ttl=64 time=10.73 ms
64 bytes from 10.9.0.1: icmp_seq=5 ttl=64 time=10.59 ms
OpenVPN 客户端配置文件
我们分发 OpenVPN 客户端配置的另一种方法是创建一个 OpenVPN 配置文件。这些可以通过 OpenVPN 访问服务器或其他方式分发。在本例中,我们将为我们的移动客户端创建一个.ovpn配置文件,将其上传到 iTunes 中的 OpenVPN 应用程序,并连接到 OpenVPN 服务器。
配置文件基本上与普通的 OpenVPN 配置文件相同。不同之处在于我们包括了 TLS 证书,包括私钥。以下是我们为个人资料创建的mobile1.ovpn文件:
client
proto udp
remote gateway.example.com
port 1195
dev tun
nobind
auth-user-pass
key-direction 1
<ca>
-----BEGIN CERTIFICATE-----
# TLS root ca
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
# TLS public certificate
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
# TLS private key
-----END PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
# tls-auth key
-----END OpenVPN Static key V1-----
</tls-auth>
现在我们需要在 iPhone 上安装来自 App Store 的应用程序(图 15-3 )。
图 15-3。
Installing the app
现在我们需要通过 iTunes 将mobile1.ovpn文件添加到应用程序中(图 15-4 )。
图 15-4。
Adding mobile1.ovpn to the OpenVPN client
完成后,我们就可以连接到 OpenVPN 服务器了。在应用程序中,我们使用绿色加号按钮添加个人资料。然后我们准备好签到。在图 15-5 中,我们输入jsmith的用户名和密码,然后切换连接按钮。
图 15-5。
Entering the username and password
图 15-6 显示我们通过 iPhone 应用程序联系在一起。
图 15-6。
Connected via the iPhone app
我们可以在网关机器上的服务器日志中确认这一点。
AUTH-PAM: BACKGROUND: received command code: 0
AUTH-PAM: BACKGROUND: USER: jsmith
AUTH-PAM: BACKGROUND: my_conv[0] query='Password: ' style=1
Mon Oct 24 09:50:48 2016 us=779104 118.209.127.108:55174 PLUGIN_CALL: POST /usr/lib64/openvpn/plugins/openvpn-plugin-auth-pam.so/PLUGIN_AUTH_USER_PASS_VERIFY status=0
Mon Oct 24 09:50:48 2016 us=779146 118.209.127.108:55174 TLS: Username/Password authentication succeeded for username 'jsmith' [CN SET]
...
<snip>
...
Mon Oct 24 09:50:48 2016 us=801096 118.209.127.108:55174 [jsmith] Peer Connection Initiated with [AF_INET]118.209.127.108:55174
我们已经完成了身份验证,现在我们的 iPhone 通过 OpenVPN 服务器连接到了本地网络。
由于许多网络工具将支持 OpenVPN,因此有各种平台的各种其他客户端可用。您也可以在此下载适用于大多数系统的客户端:
移动 VPN 路由
正如您可以创建点对点总公司到分公司 VPN 隧道一样,您也可以在您的客户端主机和网关之间执行各种路由配置。在这种情况下,您不仅要帮助客户端看到网关主机后面的主机,还要告诉客户端如何配置自己,尤其是它的 DHCP 设置。
您还将看到如何强制所有流量通过 VPN。这是一种通常用于确保用户流量只流向您的组织的方法。例如,它通常用于确保所有用户 web 流量通过您组织的代理,从而确保它符合您组织的可接受使用策略或类似标准。
首先,让我们的移动用户看到我们总部的 192.168.0.0/24 内部网络。我们通过将push选项添加到网关主机上的mobile.conf配置中来实现这一点,并使用它来推送路由。
push "route 192.168.0.0 255.255.255.0"
我们还需要更新网关主机上的防火墙规则,就像我们为分支机构到总部的隧道添加规则一样。我们为移动 VPN 隧道添加了另一个链。
[gateway]$ sudo firewall-cmd --zone public --permanent --add-interface tun1
现在我们已经重新加载了防火墙配置。
[gateway]$ sudo firewall-cmd --reload
我们创建了各种简单的规则来允许流量通过 VPN 隧道,并将其转发到我们的内部网络。我们转发了 SMTP、HTTP/HTTPS 和 IMAP 等协议。
Note
您可以使用第七章中提供的说明将这些规则添加到您的网关主机中。
我们还可以向我们的客户端传递各种选项,例如,帮助设置 DHCP 选项,如 DNS 和 WINS 服务器。
Note
我们在第十章中设置了 DHCP。
不同类型的客户端(例如,Linux 和 Microsoft Windows 客户端)需要不同的方法来下推所需的选项。当向 Microsoft Windows 客户端传递选项时,我们可以简单地使用 push 选项传递所需的选项。例如,要将 DNS 服务器 IP 地址推送到客户端,我们需要执行以下操作:
push "dhcp-option DNS 10.0.2.155"
在这里,我们告诉 OpenVPN 告诉微软 Windows 客户端将 DNS 服务器 10.0.2.155 推送到它的 DHCP 选项。
在微软 Windows 环境中,我们还可以下推各种其他选项,如表 15-1 所示。
表 15-1。
DHCP Options
| [计]选项 | 描述 | | --- | --- | | `DOMAIN`姓名 | 设置客户端的 DNS 后缀。 | | `DNS`地址 | 设置 DNS 服务器地址。重复设置辅助 DNS 服务器。 | | `WINS`地址 | 设置 WINS 服务器地址。重复设置辅助 WINS 服务器。 | | `NBDD`地址 | 设置 NBDD 服务器地址。重复设置辅助 NBDD 服务器。 | | `NTP`地址 | 设置 NTP 服务器地址。重复设置辅助 NTP 服务器。 | | `DISABLE-NBT` | 禁用 TCP/IP 上的 NetBIOS。 |在 Linux 和其他主机上,您不能使用push选项直接设置这些选项。相反,你需要告诉 OpenVPN 在隧道到达up和down时运行脚本。为此,您可以使用适当命名的up和down选项。我们可以在 VPN 客户端的mobileclient.conf中添加以下选项:
up /etc/openvpn/tunnelup.sh
down /etc/openvpn/tunneldown.sh
每个选项都指定了当 VPN 隧道开启和关闭时将运行的脚本或命令。如果我们想设置客户机的 DNS 配置,我们可以使用一个 up 脚本tunnelup.sh,如下所示:
#!/bin/sh
mv /etc/resolv.conf /etc/resolv.conf.bak
echo "search example.org" > /etc/resolv.conf
echo "nameserver 10.0.2.155" >> /etc/resolv.conf exit 0
然后,我们可以使用一个 down 脚本tunneldown.sh来恢复我们的配置选项,如下所示:
#!/bin/sh
mv /etc/resolv.conf.bak /etc/resolv.conf
Note
你需要自己将这些脚本传输到客户端,或者使用配置管理工具,我们将在第十九章向你展示。
最后,我们可以强制所有流量从客户端流向 VPN。这通常用于强制用户遵守某些策略或标准,或者确保通过代理或病毒扫描程序对所有流量进行病毒和恶意软件扫描。
不过,让所有流量都通过隧道会有一些问题。最值得注意的是,将流量通过隧道推至您的办公室,然后推至互联网,会影响性能。对于客户端生成的所有流量,您还需要一个代理或 NAT 重定向,因为每个协议(不仅仅是 web 流量)都需要一种连接方式。
为了强制来自客户端的所有流量通过 VPN 隧道,我们将以下指令添加到网关主机上的mobile.conf配置文件中:
push "redirect-gateway def1"
如果您的 VPN 设置是通过无线网络进行的,并且所有客户端和服务器都在同一个无线网络上,则您需要将添加到此指令中。
push "redirect-gateway local def1"
你可以看到我们已经在指令中添加了local选项。
OpenVPN 故障排除
排除 OpenVPN 故障要求您考虑连接的所有要素:网络、防火墙和 OpenVPN 本身。OpenVPN 广泛的日志记录(你在本章前面看到了log-append、status和verb选项)允许你快速发现错误。此外,OpenVPN 的错误信息通常会清楚准确地指出实际问题。
但是,您还需要确保检查您的网络连接和适当的防火墙规则,包括主机上的iptables规则和任何中间网络设备上的潜在规则,以允许连接。您需要检查连接是否正常,防火墙规则是否允许 VPN 隧道连接,以及是否存在允许流量通过 VPN 到达预期目的地的规则和路由。
Tip
第六章介绍网络和防火墙故障排除。
寻求故障排除帮助的最佳起点是 OpenVPN 网站( http://openvpn.net/ )。在那里您可以找到文档,包括一个全面的操作方法页面:
http://openvpn.net/index.php/documentation/howto.html
和一个常见问题页面:
http://openvpn.net/index.php/documentation/faq.html
您可以在此处找到 OpenVPN 的手册页:
https://openvpn.net/index.php/open-source/documentation/manuals.html
您也可以在此加入邮件列表:
openvpn.net/index.php/documentation/miscellaneous/mailing-lists.html
对于更复杂的实现,OpenVPN 开发人员提供商业支持,或者您可以求助于 Markus Feilner 所著的《OpenVPN:构建和集成虚拟专用网》(Packt Publishing,2006)一书。
摘要
在本章中,我们向您介绍了配置和管理 VPN 通道的过程。我们引入了点对点隧道,比如总部和远程分支机构之间的隧道。我们还解释了如何使用 VPN 隧道让您的移动用户安全可靠地连接到您的总部或其他位置的资源。您已经学会了如何执行以下操作:
- 配置 VPN 隧道
- 创建和配置用于身份验证的证书
- 利用 PAM 允许替代形式的身份验证
- 配置您的
iptables防火墙以允许 VPN 隧道 - 配置您的网络和路由,以允许用户穿越 VPN 隧道并访问资源
- 使用 OpenVPN 在客户端上配置网络选项
在下一章,我们将讨论 LDAP 服务。
十六、目录服务
目录服务在主要的计算机网络中广泛存在。轻量级目录访问协议(LDAP)目录就是这种服务的一个例子。LDAP 目录是特殊的数据库,通常包含用户名、密码、常用名、电子邮件地址、业务地址和其他属性。组织首先使用目录服务来促进地址簿和用户信息的分发。从那时起,目录服务已经发展成为所有用户信息和身份验证服务的中央存储库。开发的应用程序能够根据目录服务进行身份验证,这进一步增强了它们在组织中的重要性。
在本章中,我们将向您展示如何安装和配置 OpenLDAP 服务器。我们还将讨论通过添加您自己的模式来扩展您的 OpenLDAP 目录服务器。我们将向您展示如何设计访问控制列表来保护您的安装,以及如何通过命令行工具和基于 web 的 GUI 来管理 LDAP 服务器。最后,您将看到如何将 LDAP 服务器与现有的网络和应用程序集成,包括实现单点登录服务和 Apache web 身份验证的能力。
目录服务的实现可能很复杂。虽然安装很简单,但要安全地配置它们通常很复杂。OpenLDAP 没有商业支持的版本,但即使是 OpenLDAP 邮件列表中最简单的问题,也会由该项目的高级工程师和设计师定期回答(这对他们的奉献精神是一个巨大的帮助和绝对的荣誉)。也就是说,在您开始安装之前,购买一本专门介绍该主题的书会对您有所帮助,从而加深您对该软件的理解。我们向您推荐以下内容:
- 如果您需要专家支持,请查看技术支持页面:
www.openldap.org/support/ - 部署 OpenLDAP
- Matt Butcher 的《掌握 OpenLDAP:配置、保护和集成目录服务》(Packt Publishing,2007)
Tip
OpenLDAP 网站在 www.openldap.org 也包含了很好的管理指南和常见问题解答。
概观
在这一章中,我们将探索 OpenLDAP 并把它作为一种认证服务。我们可以将它用作任何支持 LDAP 的身份验证机制的单点登录服务。它可以用来集中保存我们所有用户的身份信息,包括用户名、密码、电子邮件地址以及其他用户和组信息。在本章中,我们将带您了解以下内容:
- 安装和设置 OpenLDAP 服务
- 解释模式并创建一个属性,我们可以使用该属性作为过滤器来检查活动和非活动用户
- 向我们的 LDAP 服务添加用户
- 使用访问控制列表保护服务以保护敏感数据
- 使用 LDAP 工具,如
ldapmodify、ldapadd和ldapsearch - 设置 web GUI 来管理 LDAP
- 使用 SSSD 和 LDAP 执行单点登录
- 使用 LDAP 实现 web 身份验证
您可以搜索、添加、修改、删除和验证 LDAP 服务中的条目。这些操作受访问列表的限制,不同的用户可以有不同的访问权限。在确定访问级别的初始阶段会发生一个身份验证过程,这称为绑定。这可以由一个用户代表另一个用户完成,也可以匿名完成,具体取决于您如何配置您的访问列表。完成后,我们就可以访问服务中的条目了。
请继续阅读,我们将解释什么是 LDAP,并让您了解构成 LDAP 服务的组件。
什么是 LDAP?
轻型目录访问协议用于访问从目录访问协议(DAP)派生的基于 X.500 的目录服务。X.500 是一组协议,概述了应该如何存储用户信息以及应该如何访问这些信息。LDAP 是由没有 TCP/IP 功能的目录访问协议产生的。
Note
有关 X.500 OSI 协议的更多信息,请参见 http://en.wikipedia.org/wiki/X.500 。
存在几种常见类型的目录服务,它们都是从 X.500 DAP OSI 模型派生出来的。一些常见的例子有:微软的活动目录、Red Hat 的目录服务和 Oracle 目录服务器企业版。
在这一章中,我们将集中讨论常用且健壮的 OpenLDAP 服务器。OpenLDAP 是从密歇根大学最初设计的原始项目中分出来的,现在通过 OpenLDAP 项目( www.openldap.org/project/ )的工程师和开发人员社区的工作继续进行。
Note
另一个建议是 FreeIPA 项目,尽管本书没有探讨。它允许您管理身份(用户帐户等),执行策略授权,如 DNS 和sudo的 Kerberos 策略,并在其他身份服务(如 Microsoft AD)之间创建相互信任。您可以在 https://www.freeipa.org/page/Main_Page 查看更多相关信息。还有一篇关于在 https://www.dragonsreach.it/2014/10/12/the-gnome-infrastructures-freeipa-move-behind-the-scenes/ 从 OpenLDAP 迁移到 FreeIPA 的文章。
X.500 DAP OSI 模型描述了 LDAP 遵循的一些基本概念。首先,您需要有一个单一的目录信息树(DIT)。这是条目的层次结构。这些条目中的每一个都需要一个可分辨名称(DN)。条目的 DN 由相对可分辨名称(RDN)及其所属的祖先条目组成。图 16-1 显示了 DIT、DN 和 RDN 之间的基本关系。
图 16-1。
DITs, DNs, and RDNs
DIT 是目录树,在这种情况下,它的根 DN 是dc=com。有几种方法来定义你的根和主分支。有些人根据他们的 DNS 域名选择布局,就像我们这里一样,有些人使用地理位置,如o=US、o=AU或o=DE作为他们的根。在我们的例子中,我们选择使用 DNS 命名标准,因为我们不太关心我们组织的地理位置。如果我们愿意,我们总是可以在树的更下面引入LocalityName属性,就像我们在compB分支中指定l=Amsterdam的位置一样。应该考虑如何布置目录结构,但最终你希望它尽可能简单易懂。
Tip
现在定义一种命名分支机构和描述组织的标准方式并坚持下去是很重要的。
分支用于将信息组织成逻辑组。这些逻辑组被称为组织单元,表示为ou。您可以将您喜欢的任何东西分组在一起,但是您通常会在 LDAP 树中看到的主要组织单位是People、Groups和Machines。你将与你的人相关的一切都存储在ou=people下,将你的用户组存储在ou=groups下,将你的非人类资产存储在ou=Machines(通常也叫做ou=hosts)下。组织单元可以包含其他组织单元,并且可以像您希望的那样复杂,尽管我们再次建议在设计您的 DIT 时越简单越好。
DN 是根下的唯一条目,它由 RDN 及其祖先组成。你可以在图 16-1 中看到我们有一个cn=Angela Taylor,ou=people,dc=example,dc=com的 DN。它由 RDN cn=Angela Taylor和ou=people、dc=example、dc=com的祖先组成。同样,ou=people,dc=example,dc=com是People组织单元的 DN,ou=people是它的 RDN。
每个 DN 条目都由描述该条目的对象类和属性组成。对象类描述了什么属性必须存在或者允许存在。这些类可以支持其他类,以便为它们提供扩展属性。这些属性由 RDN 值描述。类和属性必须在模式中定义,并且必须是唯一的。
模式是一组定义,描述可以存储在目录服务器中的数据。模式用于描述可用类和属性定义的语法和匹配规则。如果您发现可用的模式文件不能正确描述您的组织,您可以根据需要为您的公司创建自己的模式文件。一旦创建了模式文件,就可以将它包含在 OpenLDAP 配置文件中。
组织通常需要某些属性来描述您的用户或内部系统,这些属性在提供的模式文件中没有提供。在这种情况下,您将在自己的模式文件中创建自己的对象类和属性。创建模式文件时,必须记住使对象类和属性的名称是唯一的。
为所有创建的属性和类添加前缀以确保它们是唯一的,这是一个很好的做法。假设我们想自己添加一个属性,让我们知道用户何时被禁用。在这种情况下,我们可以为我们的示例公司定义一个“活动”属性为exampleActive。然后,我们可以通过将exampleActive设置为TRUE或FALSE来启用和禁用条目。
下面是它在 LDAP 条目中的样子:
dn: uid=user1,ou=people,dc=example,dc=com
uid: user1
exampleActive: TRUE
一旦这个属性被添加到 LDAP 中的一个条目,我们就可以使用过滤器在 LDAP 目录中搜索所有的exampleActive = TRUE实例,这将加快活动用户的搜索速度。这只是一个如何使用自己的模式定义的例子;可能有其他方法可以达到同样的效果。
Note
《OpenLDAP 管理员指南》在这里有关于如何创建模式文件的解释: www.openldap.org/doc/admin24/schema.html 。
OpenLDAP 可以使用各种后端。默认情况下,OpenLDAP 使用内存映射数据库(MDB ),它基于 Lightning 内存映射数据库(LMDB)。LMDB 是由 Symas 开发的,Symas 是一个软件组织,由核心 OpenLDAP 开发团队中的许多人(如果不是全部的话)创建。它速度极快,可伸缩性也很强,数据库可以容纳数百万条记录。它针对阅读、搜索和浏览进行了优化。如果您愿意,OpenLDAP 可以使用其他数据库作为后端。
Note
你可以在 https://symas.com/products/lightning-memory-mapped-database/ 了解更多关于 LMDB 的信息。
总则
Ubuntu 和 CentOS 提供了不同版本的 OpenLDAP。CentOS 和 Ubuntu 都提供了最新的 OpenLDAP 2.4 版本。以下是它支持的一些功能:
- 镜像模式和多主控复制
- 代理同步复制
- 扩展文档
- LDAP 版本 3 扩展
- LDAP 链接操作支持
- 不使用复制控制支持
- LDAP 动态目录服务(RFC 2589)
- 添加覆盖层以获得更强大的功能
如果您正在寻求对多主控复制功能的支持(即拥有多个 LDAP 主目录服务的能力),多主控可以增强 LDAP 安装的冗余性。
你也可以利用叠加。覆盖为 OpenLDAP 提供了高级功能,以改变或扩展正常的 LDAP 行为。密码策略(ppolicy)覆盖等覆盖层支持 OpenLDAP 基本代码中未提供的密码控制。ppolicy 覆盖允许您设置密码时效和最小字符长度等内容。
您还需要决定您的组织将支持哪种身份验证方法。OpenLDAP 支持两种认证方法,简单和 SASL。简单方法有三种操作模式。
- 匿名:不提供用户名或密码。
- 未经验证:提供了用户名,但没有密码。
- 用户名/密码验证:必须提供有效的用户名和密码。
对于 SASL 方法,《OpenLDAP 管理员指南》说,您需要一个现有的塞勒斯 says 安装来提供 SASL 机制。这并不完全正确,这取决于您想要实现的 SASL 机制。您可以非常容易地设置PLAIN / LOGIN和 DIGESTMD5 机制。然而,你必须安装赛勒斯 SASL。SASL 提供了以下机制:
- 普通/登录
- DIGESTMD5
- GSSAPI (Kerberos v5)
- 外部(X.509 公钥/私钥身份验证)
Note
SASL (PLAIN/LOGIN,DIGESTMD5)要求在userPasswd属性中使用明文密码。这对于安全来说是好是坏是一个激烈的争论。争论的一方是这样的:“一旦我进入了你的数据库,我就可以访问你所有的密码。”对此的反驳是,“如果你能进入我的数据库,游戏就结束了。至少我不会通过可能被拦截的线路发送密码。”
您可以在《OpenLDAP 管理员指南》的以下页面中了解有关这些不同认证方法的更多信息:
www.openldap.org/doc/admin24/security.html#Authentication%20Methodswww.openldap.org/doc/admin24/sasl.html
履行
在我们向您展示如何在我们的示例系统上安装 OpenLDAP 服务器之前,我们需要回顾一下实现的一些细节。
- 我们将在 DNS 中设置一个 CNAME,它将把
ldap.example.com指向headoffice.example.com记录,或者为安装 LDAP 服务器的主机定义一些其他的 DNS A 记录。有关 DNS 的说明,请参见第十章。 - 我们没有使用任何目录服务的副本。复制是指我们的网络上可以有多个 LDAP 服务器共享我们的全部或部分 LDAP 数据,并响应客户端请求。这需要额外的配置。
让我们只看一下网络的一部分。假设我们的网络上有一个 web 服务器,我们想确保只有我们组织中某个组的人才能访问它。通常,我们需要在网站上添加复杂的登录机制,使用某种用户数据库来存储信息,等等。有了 Apache web 服务器,我们可以使用 Apache LDAP 模块让 web 服务器使用 LDAP 服务器来验证请求。如果没有此身份验证,网站将无法访问。我们还可以让其他服务对我们的 OpenLDAP 目录服务器进行认证。在图 16-2 中,你可以看到我们如何认证我们的 web 服务器。
图 16-2。
LDAP authentication of web services
图 16-2 给出了一个简单的图表,显示了一个 web 服务器使用 LDAP 向 web 服务认证我们的桌面或互联网客户端(如果没有必要的硬件资源,LDAP 服务和 web 服务可以在同一个主机上)。当网站收到请求时,发出请求的用户需要在被授予访问权限之前进行验证。在headoffice.example.com向 LDAP 服务发送认证请求。如果用户通过验证,LDAP 服务器会将响应发送给 web 服务,用户就可以访问站点了。
我们在本书中描述的许多服务都可以使用 LDAP 服务。这使您可以将身份认证服务集中在一台主机上,降低了复杂性,提高了身份认证安全性,并为您的所有员工详细信息提供了一个中央存储库。
我们将向您展示如何为 Apache web 服务设置 LDAP 服务和身份验证。
装置
在 CentOS 和 Ubuntu 上都可以通过它们的在线存储库获得 OpenLDAP。同样,对于 OpenLDAP,这两个发行版之间存在细微的差异,我们将在接下来详述它们。
CentOS 安装指南
现在,我们将带您在 CentOS 主机上安装 OpenLDAP 服务器。二进制文件可以从 CentOS 存储库中获得,您可以通过yum命令或软件包管理器 GUI 来安装它们。我们将通过yum命令安装它们,如下所示:
$ sudo yum install openldap openldap-clients openldap-servers
这将安装配置、运行和管理 LDAP 服务器所需的文件。openldap包安装允许主机与 OpenLDAP 服务器集成所需的基础包。openldap-clients包安装管理和查询 LDAP 服务器的工具。openldap-servers包安装运行 OpenLDAP 服务器所需的文件。
Ubuntu 安装指南
要在 Ubuntu 主机上安装 OpenLDAP,我们需要安装ldap-utils包和slapd包。以下命令将安装这些软件包:
$ sudo aptitude install ldap-utils slapd
当您发出这个命令时,slapd包将要求您提供根 LDAP 用户的密码。您可以输入密码并继续安装。如果您不想提供密码,只需按两次 Enter 键(我们将在接下来的“配置”部分向您展示如何创建密码)。一旦安装完毕,ldap-utils包将安装管理和搜索 LDAP 目录所需的文件。slapd包安装运行和配置 LDAP 目录所需的文件。
客户端配置包是auth-client-config (PAM 和 NSS 配置文件切换器)和ldap-auth-client(用于 LDAP 认证和ldap-auth-config的元包)。你可能也想安装这些。
配置
我们将向您展示如何配置 LDAP 目录服务。LDAP 服务器被称为 SLAPD。我们将向您展示如何配置该服务。
OpenLDAP 使用动态运行时配置来管理 SLAPD,这意味着它通过自己的 DIT(目录树)来配置自己。这意味着 SLAPD 配置更改可以通过使用用于更改其他 LDAP 记录的标准工具来更改 DIT 中的记录来动态完成,使用的命令有ldapmodify之类。
在我们的例子中,我们将在 Ubuntu 主机上配置我们的 SLAPDCentOS 的某些目录路径会有所不同。对于 CentOS 主机,配置目录称为/etc/openldap,而不是 Ubuntu 主机上的/etc/ldap。两个发行版都将数据库存储在/var/lib/ldap。
例如,CentOS 主机上 OpenLDAP 的配置文件存储在/etc/openldap/中。
$ sudo ls -l /etc/openldap/
total 12
drwxr-xr-x. 2 root root 85 Oct 26 12:46 certs
-rw-r--r--. 1 root root 121 Mar 31 2016 check_password.conf
-rw-r--r--. 1 root root 365 Oct 3 13:47 ldap.conf
drwxr-xr-x. 2 root root 4096 Oct 26 12:46 schema
drwx------. 3 ldap ldap 43 Oct 26 12:46 slapd.d
机密存储在certs目录中。专门用于配置 LDAP 客户端的配置文件是ldap.conf。schema目录包含我们的ldap服务的模式文件。在那里你会找到.schema文件和.ldif文件。LDIF 文件是 LDAP 交换格式文件,这是一种在 LDAP 中指定数据更改的特殊格式。在slapd.d目录中,您将找到包含 SLAPD DIT 的文件。我们将在本章中解释这些。
要求
在配置 OpenLDAP 之前,我们将设置一些要求。我们需要创建一个 TLS 证书和密钥以及一个 DNS 名称条目。
第一步是创建 DNS 记录。像 LDAP 这样的身份验证系统通常不会向公众公开,如果公开,它们可能会受到外部攻击。因此,我们通常不会提供公共 IP 地址。对于需要针对服务进行身份验证的外部办公室,我们建议您使用专用 VPN 进行访问。
这个 OpenLDAP 服务将被安装在我们的headoffice.example.com主机上。我们将为我们的 DNS 服务器提供CNAME记录,以将ldap.example.com指向headoffice.example.com。我们需要在我们的 DNS 服务器上发出以下命令:
$ sudo nsupdate -k /etc/bind/ddns_update.key
> server localhost
> update add ldap.example.com 8600 CNAME headoffice.example.com
> send
> quit
$ host ldap.example.com
ldap.example.com is an alias for headoffice.example.com.
headoffice.example.com has address 192.168.0.1
由于这指向一个内部私有 IPv4 地址,我们将无法使用 Let's Encrypt 来创建我们的 TLS 证书,而必须使用我们自己的私有 CA。连接到我们的 LDAP 服务器的客户端需要安装 CA 根证书。
首先,创建一个名为/etc/ldap/certs的新目录,然后更改它的权限。
$ sudo mkdir /etc/ldap/certs
我们将在我们的ldap.example.com主机上创建密钥和 CSR,并从/etc/ldap/certs目录中运行以下内容:
$ sudo openssl req -new -newkey rsa:4096 -nodes -keyout
ldap.example.com.key -out ldap.example.com.req
继续,像我们在第 11 和 15 章中所做的那样,通过我们的私人 CA 在请求上签名。然后,我们需要将生成的公共证书和根 CA 一起添加到/etc/ssl/certs目录中,如果根 CA 不在那里的话,cacert.pem。
当证书被安装到certs目录中时,我们应该将所有权和权限更改如下:
$ sudo chown openldap:openldap –R /etc/ldap/certs
$ sudo chmod 600 /etc/ldap/certs/ldap.example.com.key
在 CentOS 上运行 LDAP 服务的用户是ldap,需要在前面的chown命令中使用。
配置 SLAPD
有了这些要求,我们现在可以开始配置 OpenLDAP 服务器了。当我们在 Ubuntu 上安装了slapd包并被要求输入管理员密码时,基本的 OpenLDAP 服务器被配置、安装并启动(在 CentOS 上,您必须在运行该命令之前启动slapd服务)。
在清单 16-1 中,我们有ldapsearch命令的输出。该命令是用于与 LDAP 服务器(或任何 LDAP 服务器)交互的命令套件的一部分。在这个例子中,我们将–Q参数传递给ldapsearch来启用 SASL 安静模式(因为我们使用提升的sudo特权)。这三个字母(-LLL)都有一个意思。拥有一个意味着以 LDIF 格式打印;另外两个减少输出。–H参数是我们想要连接的 URI,ldapi:///,也就是说,通过本地主机上的 Unix 套接字连接到本地 LDAP 服务器。这样,我们可以将用户的 UID 和 GID 传递给 LDAP 服务器进行身份验证。接下来,-b定义搜索基数;我们使用dn作为过滤器,在cn=config DIT 中搜索每个dn(或识别名)。
$ sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config dn
dn: cn=config
dn: cn=module{0},cn=config
dn: cn=schema,cn=config
dn: cn={0}core,cn=schema,cn=config
dn: cn={1}cosine,cn=schema,cn=config
dn: cn={2}nis,cn=schema,cn=config
dn: cn={3}inetorgperson,cn=schema,cn=config
dn: olcBackend={0}mdb,cn=config
dn: olcDatabase={-1}frontend,cn=config
dn: olcDatabase={0}config,cn=config
dn: olcDatabase={1}mdb,cn=config
Listing 16-1.Viewing the Default Configuration
在清单 16-1 中,您还可以看到我们已经通过了–Y选项。这指定了我们想要使用的 SASL 认证机制。EXTERNAL这里说在这种情况下使用 localhost 的认证。我们通过 Unix 套接字将 root 用户的 UID 和 GID 传递给 LDAP 进行身份验证。默认安装允许本地超级用户访问已安装的 LDAP 服务器。
LDIF Format
LDAP 目录交换格式(LDIF)是如何在 LDAP 数据库中添加和删除条目的规范。它有自己的 RFC ( https://www.ietf.org/rfc/rfc2849.txt ),LDAP 工具可以使用它来更改 LDAP 数据库中的记录。
LDIF 文件的格式如下:
dn: <the distinguished name you wish to change>
changetype: optional change type of either add, replace, or delete
<attribute or objectclass>: value
下面是一个例子:
dn: dc=example,dc=com
objectclass: dcObject
objectclass: organizationalUnit
dc: example
ou: example
这里我们已经描述了 DIT 的顶层。DN dc=example,dc=com将由那些特定的对象类和属性组成。
要修改现有的 DN 条目,我们可以像这样使用:
dn: uid=ffrank,ou=people,dc=example,dc=com
changetype: replace
replace: userPassword
userPassword: <new password>
-
这里我们用一个 LDIF 格式的文本文件修改了ffrank的userPassword。可以看到我们要更改的 DN,我们要执行的更改类型(replace,我们要替换的属性(userPassword);最后,我们将新值赋给属性。我们可以在一个文件中有很多这样的语句,我们用-在新的一行中把每个语句分开。
然后在清单 16-1 中,我们有了在cn=config全局配置 DIT 中的全局指令 dn 列表。你可以看到全局 DIT 由根cn=config组成,然后域名嵌套在它下面,如图 16-3 所示。
图 16-3。
cn=config DIT
从清单 16-1 和图 16-3 中可以看到,LDAP 模式cn={1}cosine,cn=schema,cn=config位于cn=schema DN 下,DN 位于cn=config下。{1}表示模式 DN 的索引。
此外,在清单 16-1 中,还有其他几个全局指令,如olcBackend和olcDatabase。顾名思义,它们描述了后端数据存储。
dn: olcBackend={0}mdb,cn=config
dn: olcDatabase={-1}frontend,cn=config
dn: olcDatabase={0}config,cn=config
dn: olcDatabase={1}mdb,cn=config
要获得cn=config DIT 的完整列表或备份,可以发出以下命令:
sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config > slapd.ldif
我们使用了与之前相同的命令,但是从末尾移除了dn过滤器,并将输出定向到slapd.ldif文件。如果您查看该文件,您将会看到这些指令是如何配置的。
看第一个声明,我们有了cn=config的 DN。它有一个olcGlobal的ObjectClass,它是定义全局 DIT 的对象类。您还可以看到,我们可以声明通用名称(cn:)、用于slapd ( olcArgsFile:)的参数文件、日志级别(olcLogLevel:)、进程 ID ( olcPidFile:)和要使用的线程(olcToolThreads:)。
olcToolThreads指令告诉slapd守护进程只使用一个 CPU 来运行索引。如果您有多个 CPU,您可以将其设置为一个较大的数字,但不能高于您拥有的 CPU 数量。可以开启其他性能设置,包括olcThreads、olcTimeLimits、olcSockBuffMaxIncoming和olcSockBuffMinIncoming。
Tip
你可以转动的另一个调节旋钮是在一次点击中从一个ldapsearch返回的条目的数量,olcSizeLimit。
每次您声明一个 DN 时,您都需要提供它所属的对象类。该对象类将具有它所采用的属性。因此,olcGlobal对象类将olcArgsFile作为属性,这将在模式文件中描述。
定义日志级别
在olcGobal对象类中,我们可以定义我们的日志记录。默认设置为none。这是一个关键字,但也可以用数字(甚至十六进制)来表示,如表 16-1 所述。
表 16-1。
Additive Logging Levels
| 水平 | 关键词/描述 | | --- | --- | | `-1` | (任何)打开所有调试信息。这有助于在更细粒度地记录日志之前找出 LDAP 服务器的故障所在。 | | `0` | 关闭所有调试。这是生产模式的推荐选项。 | | `1` | (`0x1` trace)跟踪函数调用。 | | `2` | (`0x2` packets)调试数据包处理。 | | `4` | (`0x4` args)提供重跟踪调试(函数 args)。 | | `8` | (`0x8` conns)提供连接管理。 | | `16` | 打印出发送和接收的数据包。 | | `32` | (`0x20` filter)提供搜索过滤处理。 | | `64` | (`0x40` config)提供配置文件处理。 | | `128` | (`0x80` ACL)提供访问控制列表处理。 | | `256` | (`0x100` stats)提供连接、LDAP 操作和结果(推荐)。 | | `512` | (`0x200` stats2)表示已发送的统计日志条目。 | | `1024` | (`0x400` shell)打印与 shell 后端的通信。 | | `2048` | (`0x800` parse)解析条目。 | | `16384` | (`0x4000` sync)提供 LDAPSync 复制。 | | `32768` | (`0x8000`无)无论设置了什么日志级别,都只记录消息。 |日志级别对于帮助调试您的安装非常重要。老实说,对于新用户来说,报告的内容可能非常混乱。然而,日志级别是附加的,您可以在日志中获得更细粒度的细节。在生产环境中,我们建议将该值设置为0,如果您愿意,可以使用审计覆盖来监控您的安装发生了什么(覆盖是一个软件模块,可以连接到后端以提供特定的信息,在本例中是审计跟踪)。
我们希望将日志级别设置为 480。这将在我们的日志中显示搜索过滤器、配置文件处理、访问控制和连接信息。如上所述,Loglevel设置是附加的,这意味着您可以通过添加想要记录的内容的值来启用更多的日志记录。您可能已经知道了,我们的480的Loglevel由层次32(搜索过滤器)、64(配置处理)、128(访问控制列表处理)和256(连接和 LDAP 操作结果)组成。当我们设置 LDAP 服务时,这是一个很好的设置,因为它提供了很好的信息。如果我们卡住了,我们可以将Loglevel改为-1来打开调试,这将打开所有的日志功能。另外,请记住,在生产环境中,您通常希望将Loglevel设置为0。要设置Loglevel,也可以在一行中列出十六进制数,达到同样的效果;在这种情况下,我们会将日志级别设置为Loglevel 0x20 0x40 0x80 0x100。
使用 ldapmodify 修改日志级别配置
让我们修改LogLevel到我们想要的水平。为此,我们将使用ldapmodify命令。这采用了与我们之前使用的ldapsearch命令相似的参数。我们将为该命令提供一个名为loglevel.ldif的文件,如下所示:
dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: 480
要修改一个属性,我们需要提供要修改的dn值(dn: cn=config)、变化类型(changetype: modify)、要替换的属性(replace: olcLogLevel),最后是要设置的属性(olcLogLevel: 480)。
现在,让我们使用ldapmodify来修改日志属性。
$ sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f loglevel.ldif
为了确认这已经被设置,我们可以再次发出ldapsearch命令来验证。
$ sudo ldapsearch –Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config cn=config
dn: cn=config
objectClass: olcGlobal
cn: config
olcArgsFile: /var/run/slapd/slapd.args
olcPidFile: /var/run/slapd/slapd.pid
olcToolThreads: 1
olcLogLevel: 480
很好,这就是我们请求的日志设置。因为我们已经设置了这个属性,所以任何其他的 SLAPD 配置属性都可以以类似的方式设置。
添加模块
在slapd.ldif文件中,我们有模块部分。模块被添加到配置中以提供对某些功能的访问。
dn: cn=module{0},cn=config
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
这里我们声明了找到我们的模块的路径,olcModulePath: /usr/lib/ldap。我们加载了一个模块,back_mdb,这是我们前面提到的层次内存映射数据库。
我们还希望启用政策覆盖模块。ppolicy 模块允许我们通过密码到期和其他密码控制功能对数据库中的密码进行更好的控制。如果我们检查前面描述的模块路径,我们可以验证所需的文件在那里。
$ ll /usr/lib/ldap/pp*
-rw-r--r-- 1 root root 39328 May 11 17:11 /usr/lib/ldap/ppolicy-2.4.so.2.10.5
-rw-r--r-- 1 root root 948 May 11 17:11 /usr/lib/ldap/ppolicy.la
为了加载策略,我们将创建一个名为ppolicy_module.ldif的文件,并使用ldapmodify来添加它。
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: ppolicy.la
当我们执行ldapmodify命令时,您可以看到我们现在要求它添加模块ppolicy.la。如果我们现在仅对那些包含对象类olcModuleList的 DNs 进行ldapearch过滤,我们会看到以下内容:
$ sudo ldapsearch -H ldapi:// -Y EXTERNAL -b "cn=config" -LLL -Q "objectClass=olcModuleList"
dn: cn=module{0},cn=config
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
olcModuleLoad: {1}ppolicy.la
在“密码策略覆盖”一节中,当我们将 LDIFs 加载到 OpenLDAP 数据库中时,我们将进一步添加到 ppolicy 覆盖配置中。
设置后缀、RootDN 和 RootPW
我们现在将配置保存 DIT 的后端数据库。如果需要,我们可以更改默认的数据库后端,这里有几个选项。一般情况下,你会选择默认的mdb。其他类型可以选择(ldap、ldif、metadirectory、perl等)。)用于代理您的 LDAP 服务器。
Note
有关后端数据库选择的更多信息,请参见在线文档: www.openldap.org/doc/admin24/backends.html 。
为了查看当前的数据库设置,我们可以发出下面的ldapsearch:
$ sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b "olcDatabase={1}mdb,cn=config" -LLL -Q
dn: olcDatabase={1}mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
olcDbDirectory: /var/lib/ldap
olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none
olcAccess: {1}to attrs=shadowLastChange by self write by * read
olcAccess: {2}to * by * read
olcLastMod: TRUE
olcDbCheckpoint: 512 30
olcDbIndex: objectClass eq
olcDbIndex: cn,uid eq
olcDbIndex: uidNumber,gidNumber eq
olcDbIndex: member,memberUid eq
olcDbMaxSize: 1073741824
olcSuffix: dc=nodomain
olcRootDN: cn=admin,dc=nodomain
olcRootPW: {SSHA}EEyEuYme4zBPYbRzHc+l4rApfvrXjXnV
我们的数据库类型的默认值在这里定义:olcDatabase: {1}mdb。您可以声明多个数据库实例。我们配置的下一个细节是 DIT 的顶部,后缀,以及一个可以完全访问它的用户,比如根用户。
Note
如果您在 Ubuntu 服务器上安装 OpenLDAP 服务器时配置了它,您就不需要执行这个步骤。
这里我们将创建一个名为db.ldif的文件,它包含以下内容:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com
-
replace: olcRootDN
olcRootDN: cn=admin,dc=example,dc=com
-
replace: olcRootPW
olcRootPW: {SSHA}QN+NZNjLxIsG/+PGDvb/6Yg3qX2SsX95
olcSuffix将对dc=example,dc=com的查询指向这个数据库实例。因为这些属性已经有了值,所以我们在 LDIF 文件中使用 replace 指令,例如:replace: olcRootPW。您可以在这里声明多个后缀。olcRootDN是根用户,对数据库有完全的访问权;密码在olcRootPW中声明。您可以使用slappasswd命令创建密码,如下所示:
$ sudo slappasswd
然后可以像前面一样将打印的密码复制并粘贴到olcRootPW中。在应用此 LDIF 之前,我们将查看我们的索引。
创建索引
接下来,我们可以设置我们的索引。索引用于加快数据库的搜索速度。您可以通过运行以下命令来查看数据库的当前索引:
$ sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b "olcDatabase={1}mdb,cn=config" -LLL –Q olcDbIndex
olcDbIndex: objectClass eq
olcDbIndex: cn,uid eq
olcDbIndex: uidNumber,gidNumber eq
olcDbIndex: member,memberUid eq
作为一个规则,你应该索引你的客户通常会搜索什么。当电子邮件客户端的地址簿寻找人们的名字来填充其地址簿条目时,它可能会搜索常用名,或cn。在这种情况下,您可能希望为子字符串sub优化cn属性的索引。表 16-2 列出了可用的常用索引类型。
表 16-2。
Common Index Types
| 类型 | 描述 | | --- | --- | | `sub` | 对于优化包含通配符`cn=Jane*`的字符串搜索非常有用 | | `eq` | 有助于优化对精确字符串的搜索,如`sn=Smith` | | `pres` | 有助于优化对象类或属性的搜索,如`objectclass=person` | | `approx` | 有助于优化类似声音的搜索,如`sn∼=Smi*` |其他索引类型也是可用的,您可以在slapd.conf手册页上阅读它们。我们想要索引objectclass、cn和uid,我们知道当用户尝试进行身份验证时会经常搜索到它们。在前面的代码中,你可以看到我们已经在索引这些东西了。我们将为即将创建的属性exampleActive添加一个索引。我们将在db.ldif中添加以下内容:
add: olcDbIndex
olcDbIndex: exampleActive pres,eq
现在让我们继续使用ldapmodify来应用我们的db.ldif更改。
$ sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f db.ldif
Note
你可以在这里阅读更多关于配置引擎数据库: www.openldap.org/doc/admin24/slapdconf2.html 。
列出、添加和创建模式
模式向 SLAPD 服务器提供对象的类和属性的结构。虽然与数据库模式不同,但 LDAP 模式描述了 LDAP 服务器将拥有的对象类和属性,就像数据库模式描述表和行一样。您可以使用下面的ldapsearch查看当前加载的模式:
$ sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=schema,cn=config dn
dn: cn=schema,cn=config
dn: cn={0}core,cn=schema,cn=config
dn: cn={1}cosine,cn=schema,cn=config
dn: cn={2}nis,cn=schema,cn=config
dn: cn={3}inetorgperson,cn=schema,cn=config
最上面的dn是cn=schema,cn=config,那是我们图式的父代。然后,我们有了一些由我们的安装提供的默认模式。核心模式提供了诸如dcObject ( dc)和organizationalUnit ( ou)这样的对象类。cosine模式提供了dNSDomain对象类和host属性。nis模式提供用户帐户对象和属性,例如posixAccount和影子密码设置。inetorgperson保存其他各种与员工相关的对象和类。您可以使用也可以不使用这些提供的对象和属性。
要查看所有可用的模式,可以列出/etc/ldap/schema目录。例如,我们可以看到在该目录中存在 ppolicy 模式的ppolicy.schema和ppolicy.ldif文件。
$ ls /etc/ldap/schema/pp*
/etc/ldap/schema/ppolicy.ldif /etc/ldap/schema/ppolicy.schema
对于我们来说,ppolicy.ldif文件是从ppolicy.schema文件中派生出来的。我们将把我们的ppolicy.ldif模式添加到 SLAPD 中。我们通过使用ldapadd命令来做到这一点。它采用了与ldapsearch和ldapmodify相似的论点。
$ sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/ppolicy.ldif
adding new entry "cn=ppolicy,cn=schema,cn=config"
让我们看看是否已经加载。
$ sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=schema,cn=config dn
dn: cn=schema,cn=config
dn: cn={0}core,cn=schema,cn=config
dn: cn={1}cosine,cn=schema,cn=config
dn: cn={2}nis,cn=schema,cn=config
dn: cn={3}inetorgperson,cn=schema,cn=config
dn: cn={4}ppolicy,cn=schema,cn=config
您可以看到我们的 ppolicy 模式已经添加到索引{4}处。记住这个索引号,因为我们在添加自己的模式时会用到它。让我们看看如何创建和添加我们自己的模式。
创建我们的模式
我们将创建一个名为/etc/ldap/schema/exampleactive.schema的文件。在这个模式文件中,我们将包含一个简单的类和属性,用于指示用户帐户是否处于活动状态。
首先,让我们看看如何在模式中声明一个对象类。以下内容出现在schema目录下的core.schema文件中:
objectclass ( 1.3.6.1.4.1.1466.344 NAME 'dcObject'
DESC 'RFC2247: domain component object'
SUP top AUXILIARY MUST dc )
这是将包含在 DIT 中的主要对象类之一。我们需要声明我们正在使用什么类型的实体,对于一个对象类,我们从objectclass ( schema detail)开始。空白在声明对象类和属性时很重要,并且在每一端的( )中必须有一个空格。对象类声明应遵循以下格式:
objectclass ( <OID> NAME <name> DESC <description> SUP <parent class> <class type> <MUST|MAY> attritubutes )
你看到的数字,1.3.6.1.4.1.1466.344,是私企号(PEN),或者说是对象标识符(OID),是用于识别对象的唯一的一系列数字;如果您熟悉 SNMP 之类的东西,您应该认识到这一点,因为它们使用相同的 OID 概念。
Note
您可以在互联网号码分配机构(IANA)网站: http://pen.iana.org/pen/PenApplication.page 注册自己的 OID 或 PEN。
对象类被赋予一个名称、dcObject和一个描述(DESC)。下一行告诉你这将继承对象类SUP top。SUP代表上级,top表示该对象类没有父对象类;它是对象类层次结构中的最高级别。其他后续的对象类可以使用这个对象类作为它们的SUP或继承的对象类。
AUXILIARY表示对象类的类型。有三种类型的对象类。
AUXILIARY:允许您向条目添加属性,但不能创建条目STRUCTURAL:允许您创建有效条目ABSTRACT:可以定义其他对象类的基础对象;top是一个ABSTRACT的例子
MUST dc表示如果这个对象是在目录服务器中声明的,那么属性dc也必须被添加。对于对象类来说,非强制但可用的属性可以声明为MAY。
Note
声明对象类的全部细节包含在这个 RFC 中: www.rfc-editor.org/rfc/rfc4512.txt 。扩展你的图式的快速解释可以在这里找到: www.openldap.org/doc/admin24/schema.html 。
属性也有一定的规则。它们必须在模式中声明,并且同一属性可以包含在一个或多个对象类中。另外,默认情况下,属性是MULTI-VALUE,这意味着我们可以为 DN 声明多个值。常见的例子是电子邮件地址;一个用户可以有多个电子邮件地址。其他属性被声明为SINGLE-VALUE,并且只能声明一次,比如用户的密码。
属性可以是分层的,并且可以继承其父属性。它们以不同的方式表示为对象类层次结构,如下所示:
- 它们没有以顶部结束。
- 上级定义的缺失表明了等级制度的终结。
属性继承的常见例子是name属性。name属性是普通名(cn)、名(gn)和姓(sn)的父属性。
让我们来看看我们自己创建的模式文件,/etc/ldap/schema/exampleactive.schema:
# $Id$
attributetype ( 1.1.3.10 NAME 'exampleActive'
DESC 'Example User Active'
SINGLE-VALUE
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7)
objectclass ( 1.1.1.2 NAME 'exampleClient'
SUP top AUXILIARY
DESC 'Example.com User objectclass'
MAY ( exampleActive ))
在这两个模式对象中,我们有两个 oid,这是我们编造的。本例中的文件可能与其他现有的模式文件冲突,并且仅用于演示。为了避免这种情况,我们通常会申请我们自己的笔。我们将假设我们这样做了,并且我们收到了 1.3.6.1.4.1.111111 的 OID,其中 1.3.6.1.4.1 是 IANA 弧或节点,111111 是区分我们公司和其他公司的特殊数字。我们现在可以使用我们的 OID 来代替前面模式中的那些。
Caution
正如我们提到的,我们已经为此次演示构建了 1.3.6.1.4.1.111111 OID。请不要在您的生产环境中编造数字或使用此 OID。你真的应该有自己的笔;否则,你就有发生冲突和打破东西的风险。有关 oid 和 LDAP 的更多信息,请同时查看以下内容: www.zytrax.com/books/ldap/apa/oid.html 。
attributetype ( 1.3.6.1.4.1.111111.3.1.1 NAME 'exampleActive'
DESC 'Example User Active'
SINGLE-VALUE
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )
objectclass ( 1.3.6.1.4.1.111111.3.2.1 NAME 'exampleClient' SUP top AUXILIARY DESC
'Example.com User objectclass'
MAY ( exampleActive ))
一旦有了钢笔或 OID,就可以将其分成有用的线段(也称为节点或弧)。通常,您不仅可以将 OID 用于 LDAP 模式对象,还可以用于 SNMP MIBs 之类的对象。如您所见,我们已经为 LDAP 模式定义分支了 1.3.6.1.4.1.111111.3。在那之下,我们将把我们所有的对象类定义放在 1.3.6.1.4.1.111111.3.2 下,把我们的属性放在 1.3.6.1.4.1.111111.3.1 下。
Note
将 1.3.6.1.4.1.111111.3.1 和 1.3.6.1.4.1.111111.3.2 分配给 LDAP 类和属性完全是任意的。您可以选择任何您想要的编号方案。
我们的属性exampleActive只能声明一次,所以我们将它设为SINGLE-VALUE。如果我们试图为一个特定的 DN 多次声明这个属性,我们将得到一个违例错误。
我们将exampleActive属性设置为布尔匹配,这意味着它可以是真或假。将该属性设置为TRUE将意味着我们的帐户是活动的。将它设置为FALSE将意味着该帐户是不活跃的。我们可以索引这个属性,这将再次加快我们的搜索。这就是为什么我们在前面的db.ldif中添加了以下内容:
olcDbIndex: exampleActive pres,eq
exampleClient对象类定义了当我们在 DN 条目中包含该对象类时,我们可能会出现exampleActive属性(如MAY所示)。如果我们想加强它的存在,我们可以指定MUST来代替。对象类的类型是AUXILARY,并且具有由SUP top定义的超类。默认的对象类型是STRUCTURAL。条目中必须有一个STRUCTURAL对象类,不能有两个STRUCTURAL对象类指向同一个父类或上级类。
Note
您可以在 www.rfc-editor.org/rfc/rfc4512.txt 找到描述 LDAP 模式文件的 RFC。
添加我们的模式
要添加我们的模式,我们需要经历以下过程:
- 通过
slaptest将我们的模式转换为 LDIF 模式 - 编辑输出,为输入模式做准备
- 通过
ldapadd将其添加到我们的 SLAPD 中
为了将模式文件转换成 LDIF,我们使用了slaptest命令。slaptest命令对于将基于文本的模式文件转换成 LDIF 格式很有用。
我们将把/etc/ldap/schema/exampleactive.schema传递给slaptest,输出文件将在一个临时的 SLAPD 配置目录中生成。
首先创建一个临时目录来保存我们转换后的文件。
$ sudo mkdir /etc/ldap/ldif_converted && cd /etc/ldap
在这个目录中,我们现在将创建一个名为schema_load.conf的文件,格式为old slapd.conf,它将用于指导slaptest命令读取我们的模式文件。它有以下内容:
include /etc/ldap/schema/exampleactive.schema
现在我们可以用它作为我们的slaptest命令的输入文件。
$ sudo slaptest –f schema_load.conf –F ldif_converted
这会创建一个 LDIF 格式的文件。
/etc/ldap/ldif_converted/cn\=config/cn\=schema/cn={0}exampleactive.ldif
如果出现以下错误:
58180fbb schema/exampleactive.schema: line 1 attributetype: Missing closing parenthesis before end of input
这表明模式文件中有空白错误。您可以将声明放在一行中,没有回车,并注意空格。这里有一个例子:
attributetype ( attribute detail )
objectclass ( object detail )
我们将使用我们的vi编辑器编辑已经输出的 LDIF 文件,并使用sudo提升我们的特权。
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 39f1bf5a
dn: cn={0}exampleactive
objectClass: olcSchemaConfig
cn: {0}exampleactive
olcAttributeTypes: {0}( 1.3.6.1.4.1.111111.3.1.1 NAME 'exampleActive' DESC '
Example User Active' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
1.7 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.111111.3.2.1 NAME 'exampleClient' DESC 'E
xample.com User objectclass' SUP top AUXILIARY MAY exampleActive )
structuralObjectClass: olcSchemaConfig
entryUUID: 53a98d60-3432-1036-9ae2-35c34321a848
creatorsName: cn=config
createTimestamp: 20161101035217Z
entryCSN: 20161101035217.399551Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20161101035217Z
我们需要删除粗体字的行,比如从structuralObjectClass: olcSchemaConf到modifyTimestamp: 20161101035217Z以及最上面的两个#行。接下来,您可以看到下面一行:
dn: cn={0}exampleactive
记住,{0}指的是索引,如果我们试图加载这个 DN,我们将与任何具有cn={0}的现有模式冲突,回到我们的ldapsearch输出,它是核心模式。当我们添加 ppolicy 模式时,我们说过要记住索引号({4}),现在我们需要给它添加一个,以确保我们的索引不冲突。
dn: cn={5}exampleactive
然后我们会将该文件保存到/etc/ldap/schema/exampleactive.ldif。您现在可以使用ldapadd命令将创建的 LDIF 添加到我们的 SLAPD 服务器中。
$ sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f schema/exampleactive.ldif
adding new entry "cn={5}exampleactive,cn=schema,cn=config"
当我们在本章的“LDIFs 和添加用户”一节中声明我们的用户时,我们将使用exampleactive.schema文件。
访问控制列表
如果您希望它是安全的,那么每个访问您的 LDAP 服务器的连接都必须被赋予对树的各个部分的特定访问权。OpenLDAP 默认访问权限是 read,如果您存储了密码之类的秘密,那么您将希望锁定它。您可以指定从何处接受连接、安全级别或连接必须具有的加密以获得访问权限,直到您允许访问的分支或属性。您还可以授予请求连接几个级别的访问权限:manage、write、read、search和auth。
列出访问控制
访问控制附加到数据库配置。要查看当前的访问控制列表,我们需要执行以下命令:
$ sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b "olcDatabase={1}mdb,cn=config" -LLL -Q
dn: olcDatabase={1}mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=example,dc=com
olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none
olcAccess: {1}to attrs=shadowLastChange by self write by * read
olcAccess: {2}to * by * read
olcLastMod: TRUE
...
在前面几行中,您可以看到访问列表。它们以olcAccess开头,并被分配了一个索引号{0}。我们可以查看指标{0}如下:
(access) to attrs=userPassword
by self write
by anonymous auth
by * none
这允许用户写入他们自己的userPassword属性,并且匿名用户可以进行身份验证。其他一切都做不了什么(by * none)。
如何定义访问控制列表
您将看到“旧的slapd.conf”风格和动态或 LDIF 格式的访问控制列表文档。在旧的格式中,您将使用access指令来引导访问列表。在 LDIF 格式中,你会有索引号。
在其最基本的形式中,访问是使用以下语法给出的:
[access|{n}]to what [ by who [ access-level ] [ control ] ]
what是 LDAP 数据库中的一个实体,who是请求信息的客户端,access-level是您希望该客户端拥有的访问级别。control指定该条目后如何处理列表,可选。
Note
在本节中,当我们显示访问列表指令时,我们将忽略访问指令或索引号。最终的访问列表将采用 LDIF 格式。
在下面这个简单的例子中,我们给出了对 DIT 中所有内容的读取权限。
to *
by * read stop
您可以使用通配符*来允许一般的无限制访问。这里的访问控制表示任何用户都拥有对任何内容的读取权限。接下来是一个控制语句,告诉slapd停止处理任何其他指令。顺序在访问控制列表中很重要,顺序较高的指令在顺序较低的指令之前被处理。当您给出一个特权或访问级别时,它意味着所有以前的特权或访问级别。例如,读访问自动授予前面的disclose、auth、compare和search访问级别,包括read访问权限。表 16-3 列出了可以分配给实体访问请求的访问级别。
表 16-3。
Access Privileges
| 接近 | 特权 | | --- | --- | | `none` | 不允许任何访问 | | `disclose` | 不允许访问,但返回错误 | | `auth` | 启用绑定操作(身份验证) | | `compare` | 允许您比较实体 | | `search` | 允许您搜索 DIT 的这一部分 | | `read` | 允许读取访问 | | `write` | 允许写访问 | | `manage` | 允许所有访问和删除实体的能力 |当您选择none时,您拒绝对实体的所有访问,而不会向请求者返回错误。这有助于防止您的 DIT 中有什么和没有什么的信息泄露。与none不同,disclose访问将向请求客户端返回一个错误。
定义谁
进一步看请求对实体的访问,您需要知道谁在请求访问。可以有多个who声明,每个声明使用特定的关键字。这些关键字可以与一个style限定符结合,这个限定符可以是类似于regex或exact的东西。regex风格指的是可以用来匹配 DN 各个部分的正则表达式。处理访问控制列表的成本更高。
Tip
关于使用正则表达式的技巧以及我们在这里讨论的其他主题,请参见 OpenLDAP 管理员指南: www.openldap.org/doc/admin24/access-control.html 。
从处理的角度来看,它总是成本较低,但更精确地描述您希望向谁提供访问权限。这里有一个例子:
to dn.subtree=ou=people,dc=example,dc=com
by dn.exact="cn=admin,ou=meta,dc=example,dc=com" read
在这里,我们再次授予对组织单位People下所有内容的读取权限。我们很明确地定义了这个访问权限只授予 DN cn=admin,ou=meta, dc=example,dc=com。
定义授予什么访问权限会变得很棘手。有几种标准方法可用于授予访问权限。您可以使用以下内容:
dn.base
dn.one
dn.subtree
dn.children
为了解释这些与我们正在处理的对象的关系,我们将借用 OpenLDAP 管理员指南中的一个例子。假设我们有以下列表:
0: dc=example,dc=com
1: cn=Manager,dc=example,dc=com
2: ou=people,dc=example,dc=com
3: uid=jsmith,ou=people,dc=example,dc=com
4: cn=addresses,uid=jsmith,ou=people,dc=example,dc=com
5: uid=ataylor,ou=people,dc=example,dc=com
当我们试图处理 DIT 的一部分时,我们可以声明模式匹配的范围。
dn.base="ou=people,dc=example,dc=com" match 2;
dn.one="ou=people,dc=example,dc=com" match 3, and 5;
dn.subtree="ou=people,dc=example,dc=com" match 2, 3, 4, and 5; and
dn.children="ou=people,dc=example,dc=com" match 3, 4, and 5.
声明正确的范围将捕获 DIT 树的正确部分。如您所见,范围dn.base将只引用声明的树的级别ou=people,dc=example,dc=com。dn.one的范围将在ou=people,dc=example,dc=com后作用于树的直接部分。
dn.subtree范围将作用于ou=people,dc=example,dc=com下的所有东西及其自身,而dn.children将作用于ou=people,dc=example,dc=com下的所有东西。
通过过滤器定义人员
在 LDAP 中,您可以使用过滤器,这是一种剔除不需要的数据并留下您想要的确切结果的方法。在访问控制列表中,您可以使用过滤器来更具体地确定您要授予的访问权限。请看下面一行:
to dn.subtree="ou=people,dc=example,dc=com" attrs="userPassword"
by dn.exact="cn=admin,ou=meta,dc=example,dc=com" write
by * none
在这个例子中,我们已经声明我们希望这个应用于所有在ou=people,dc=example,dc=com下的东西,以及任何可能在那里找到的名为userPassword的属性。在这种情况下,属性userPassword就是过滤器。我们给予管理员用户对userPassword的写访问权限,一切都将被无声地拒绝。
关于访问控制列表的更多信息,手册页是很好的资源,OpenLDAP 管理员指南也很好: www.openldap.org/doc/admin24/access-control.html 。
定义我们的访问控制列表
现在,我们将带您浏览我们将在example.com LDAP DIT 中使用的访问控制列表。这是我们想要做的:
- 我们希望用户更改自己的密码并绑定。
- 我们想要创建一个能够代表用户绑定的
meta用户组。 - 我们希望一个管理组能够管理用户的条目。
我们将为我们的系统 root 用户授予对 DIT 的管理访问权限。这可以在以后的阶段删除,但它为我们提供了访问权限,以防我们的访问列表出错。这与在cn=config数据库上提供的默认访问相同。
to *
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by * break
这表示根用户(uid 0, gid 0)被允许manage整个 DIT (to *)。然后我们进行break处理,并进入下一个访问列表。正如我们之前提到的,这个用户是由外部提供者进行认证的(系统认证,或 PAM)。当我们提供-Y EXTERNAL时,LDAP 将允许访问 UID 和 GID 0(或 root 用户),而不提示身份验证本身。
接下来,我们将定义对密码信息的访问。正如我们前面提到的,访问控制列表是自顶向下读取和实现的。将敏感的访问控制列表放在顶部很重要,这样它们就不会被更高的条目覆盖。
在本节中,我们限制了对用户密码信息的访问,这些密码信息存储在整个 DIT 的属性userPassword、shadowLastChange、entry和member中。entry是一个特殊的伪属性,我们必须指定它来访问一个条目,而member是用来访问组成员。
我们将只允许管理员拥有特殊访问权限。webadmin用户将用于从我们的 web 服务器绑定到我们的 LDAP 服务器,以便我们的 web 用户可以进行身份验证。我们只允许 TLS 安全强度因子(tls_ssf)等于或大于 128 的连接访问这些属性。我们将在本章的“使用 TLS 保护 SLAPD”一节中进一步解释ssf,但是现在,tls_ssf指定了访问这些属性所需的最小 TLS 密钥大小,这意味着只有当这些属性具有足够安全的传输层时,我们才允许访问这些属性。
Note
我们将很快解释安全强度因素。您可以使用其他选项来限制对您的属性的访问,例如指定接受连接的对等名称或域。有关该列表和一般访问控制列表的更多信息,请参见 www.openldap.org/doc/admin24/access-control.html 。
我们授予anonymous auth访问权;也就是说,客户端不需要绑定(或认证)到我们的 LDAP 服务器来进行认证。有三种方法进行身份验证;一种是不提供用户名或密码(匿名),另一种是只提供用户名,另一种是提供用户名和密码。在没有严格准入条件的情况下,不特别推荐匿名。如果您的 LDAP 服务器在互联网上是公开的,就不应该使用匿名。
Note
您可以使用用户和密码对 LDAP 服务器进行初始身份验证,以执行绑定操作(对用户进行身份验证)。在本章的后面,当我们使用 Apache 进行身份验证时,我们将向您展示如何做到这一点。
匿名认证对于我们实现单点登录服务是必需的,我们将在本章的“单点登录:集中式 Linux 认证”部分解释这一点。只有当匿名的 TLS 安全强度因子(tls_ssf)为 128 时,我们才能保证匿名的身份验证。我们还允许用户通过self write访问来更改他们自己的密码细节。
清单 16-2 中的最后一行很重要。这是一个控制语句,用于阻止访问列表中的下一级访问。
olcAccess: {1}to attrs=userPassword,shadowLastChange,entry,member
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" tls_ssf=128 auth
by anonymous tls_ssf=128 auth
by group.exact="cn=admins,ou=groups,dc=example,dc=com" tls_ssf=128 write
by self tls_ssf=128 write
by * tls_ssf=128 search
by * none stop
Listing 16-2.Access List for Sensitive Attributes
它表示任何其他用户(*)没有访问权限(none),然后停止进一步处理。
我们已经说过,秩序很重要。当一个访问请求进入您的 LDAP 主机时,访问控制列表被解析,如果发现匹配,访问被授予或拒绝。您可以通过将访问控制列表按照请求最多的访问到最少的访问的顺序排列来加快您的访问请求。您希望所有这些常见的请求位于访问控制列表的顶部,不太常见的请求位于底部。假设在这个例子中,一些元用户可以访问我们的目录服务器的各个部分,并且这些用户有最常见的访问请求。这就是为什么我们将处理元用户组的访问控制放在列表顶部,就在用户密码条目的下面。
分支ou=meta包含我们用来将我们的认证代理到我们的目录服务器的用户。我们并不总是要求用户直接绑定到我们的目录服务器,但是有时我们仍然希望他们能够根据目录服务器进行身份验证,比如当我们执行 web 身份验证时。您已经看到,我们已经将用户密码条目的授权访问权授予了webadmin。现在,我们宣布这些域名能够看到自己的信息。
to dn.children="ou=meta,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
by self read
我们允许cn=admins组对这个组织单元进行write访问,在这个组中,我们将把我们的系统管理员用户和read访问放在元用户本身。这防止了在ou=meta组织单元下定义的用户能够改变他们自己的任何条目,并且这为那些用户提供了更大的安全性。
接下来,我们授予对ou=people分支下所有内容的访问权,记住我们已经在前面的访问控制列表中定义了对用户密码属性的访问。先前的访问定义将覆盖我们在这里为先前定义的属性详述的任何访问。管理员帐户至少需要读取权限,我们已经给了admins组write访问权限。我们将希望admins组也不时改变细节。webadmin用户只需要只读权限。我们用关键字self赋予条目本身的读权限。
to dn.children="ou=people,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
by self write
by users read
在您的网络中,您可能有不同的需求,将self访问改为write是很常见的。该设置将使用户能够更改定义其个人信息的属性细节,而read access 则不能。
在下面的代码中,我们授予对ou=groups分支的访问权,在那里我们将保存所有的组信息。
to dn.children="ou=groups,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
by anonymous read
如您所见,这类似于ou=people分支,相同的管理员帐户拥有相同的访问权限。然而,我们已经允许认证用户通过指定users read来读取组。
接下来,我们有ou=hosts组织单位。有人把这个单位命名为machines,但选择权在你。它将保存您所有的主机信息、IP 地址、位置等等。我们已经使用了subtree的作用域,除了cn=admins组之外,对任何事物都授予了最小的write访问权限。
to dn.children="ou=hosts,dc=example.com"
by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
by anonymous read
这里,cn=admins组将需要write访问。我们给予anonymous客户端读权限,这些客户端没有建立绑定连接(未经身份验证)。各种应用程序可以利用ou=hosts组织单元,包括 Samba 这样的应用程序。
最后一条规则是全面拒绝规则。这将强制拒绝所有其他访问。这基本上是多余的,因为任何未被授予显式访问权限的内容都将被拒绝;但是,它显示了您的访问控制列表集的结尾,并防止可能出现在它下面的任何访问控制列表被错误地读入。
to * by * none stop
这里的通配符匹配所有内容,这意味着任何访问排序都会被拒绝,并且所有进一步的处理都会被 control 字段中的 stop 选项停止。其他可用的加工控制有break和continue。
在匹配时,break控制选项将停止访问控制组中的进一步处理,并跳到下一个。匹配后,continue选项将继续进一步处理访问控制组,允许授予增量特权。stop选项只是立即停止任何进一步的处理,并且是默认的控件。清单 16-3 显示了我们完整的访问控制列表。
Note
你在清单 16-3 中看到的olcAccess:指令清单被分成不同的行,只是为了文档的清晰。如果您对空白有任何错误,请尝试将每个指令放在一行中。
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to *
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by * break
-
add: olcAccess
olcAccess: {1}to attrs=userPassword,shadowLastChange,entry,member
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" tls_ssf=128 auth
by anonymous tls_ssf=128 auth
by group.exact="cn=admins,ou=groups,dc=example,dc=com" tls_ssf=128 write
by self tls_ssf=128 write
by * tls_ssf=128 search
by * none stop
-
add: olcAccess
olcAccess: {2}to dn.children="ou=meta,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
by self read
-
add: olcAccess
olcAccess: {3}to dn.children="ou=people,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
by self write
by users read
-
add: olcAccess
olcAccess: {4}to dn.children="ou=groups,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=groups,dc=example,
dc=com" write
by anonymous read
-
add: olcAccess
olcAccess: {5}to dn.children="ou=hosts,dc=example.com"
by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" search
-
add: olcAccess
olcAccess: {6}to * by * none
Listing 16-3.The Complete Access Control List
关于更新访问控制列表,有一些事情需要注意。在清单 16-3 中,我们看到我们正在使用 LDIF 格式来添加这些访问列表。先拿第一节来解释一下。
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}<access list>
-
add: olcAccess
olcAccess: {1}<access list>
第一行是 DN,在本例中是主配置数据库,我们希望在上面工作。第二行是变更类型,即修改。对于第一个索引元素{0},我们需要使用替换修改类型。对于之后的访问列表,我们需要添加列表。当使用动态访问列表和ldapmodify时,需要记住一些规则。
- 如果替换一个索引元素,需要加载完整的访问列表。
- 只能用一个
add指令追加到访问列表的末尾。 - 访问列表是从头到尾读取的。
我们现在可以将这些访问列表指令放入一个名为access.ldif的文件中,然后使用ldapmodify来应用它们。
$ sudo ldapmodify -H ldapi:/// -Y EXTERNAL -f access.ldif
我们将很快解释如何搜索测试这些。
使用 slapd 守护程序
您可以通过两种方式运行您的slapd守护进程:使用slapd.d配置引擎(动态配置)或不使用它。如前所述,配置引擎支持使用 LDIF 语法和 LDAP 命令动态更改 SLAPD 配置。
另一种方法是加载一个有旧样式指令的slapd.conf文件。当我们把我们的exampleactive.schema文件转换成 LDIF 格式时,我们看到了一个slapd.conf语法的例子。
这两种方式都受支持,但是用旧式的slapd.conf运行会被否决,所以我们不建议用它启动slapd。您可以通过发出以下命令将您的旧式slapd.conf转换为动态 LDIF 配置引擎(SLAPD 不能已经在运行):
$ sudo slapd -f slapd.conf -F slapd.d -u openldap -g openldap
这类似于我们之前运行的slaptest命令。您会注意到这是在前台运行的,当它试图启动时,您可以看到是否有任何问题。对于 CentOS 主机,运行 OpenLDAP 的用户可以使用-u ldap -g ldap,而不是运行 Ubuntu 主机的-u openldap。然后-f slapd.conf指向我们想要读入的配置文件,-F指向slapd.d目录,该目录将保存您的配置引擎的 LDIF 文件。
当您的slapd实例启动时,您将看到slapd.d目录现在包含几个文件和目录。这些文件包含您在slapd.conf中指定的 LDAP 设置以及其他 LDIF 文件格式的附带文件。
Note
您可以在 https://help.ubuntu.com/lts/serverguide/openldap-server.html 查看更多关于管理您的 OpenLDAP 服务器配置的信息。
对于故障排除,在前台以调试模式运行 SLAPD 守护进程来查看服务正在做什么通常是很有用的。为此,您可以发出以下命令(在 Ubuntu 上,使用–u ldap和–g ldap表示 CentOS):
$ sudo slapd -F /etc/ldap/slapd.d -d -1 -u openldap -g openldap -h ldapi:///
您可以在 CentOS 或 Ubuntu 上使用以下命令手动启动或停止 SLAPD 服务器服务:
$ sudo systemctl start slapd
$ sudo systemctl stop slapd
然后,您可以使用以下命令检查守护程序的状态:
$ sudo systemctl status slapd
您可以使用以下命令在启动时启用:
$ sudo systemctl enable slapd
一旦服务启动,您可以跟踪日志以查看任何日志记录信息。
$ sudo journalctl -xfe -u slapd
您可以使用日志来监控和解决您的访问请求问题。
用 TLS 保护 SLAPD
因为 LDAP 通常包含敏感数据,所以确保在 LDAP 客户端和 LDAP 服务器之间传输的数据是加密的是一个很好的预防措施。LDAP 可用于地址簿之类的东西,但也可用于存储更敏感的数据,如密码、员工详细信息等。
我们可以配置传输层安全性(TLS)来保护我们的网络传输。TLS 用于加密我们的服务器和它的客户端之间的通信。我们将创建一个 LDIF 文件来添加这些记录。
dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/certs/cacert.pem
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/certs/ldap.example.com.cert
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/certs/ldap.example.com.key
-
add: olcTLSVerifyClient
olcTLSVerifyClient: allow
在这里,我们从我们的私钥创建一个证书文件,并添加细节。发布证书文件被添加到cacert.pem文件中。我们还为TLSVerifyClient指定了allow。这意味着我们将验证在 TLS 交换期间提交给我们的任何客户端证书,但如果我们无法验证证书,我们不会失败。其他选项有never、allow、try、demand. try、demand未验证证书连接失败。
我们像以前一样用ldapmodify来应用tls.ldif。
$ sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f tls.ldif
我们可以验证我们仍然可以使用 root 用户来查询 LDAP 服务器并查看我们的 TLS 条目。
$ sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b "cn=config" -LLL -Q |grep TLS
olcTLSCACertificateFile: /etc/ldap/certs/cacert.pem
olcTLSCertificateFile: /etc/ldap/certs/ldap.example.com.cert
olcTLSCertificateKeyFile: /etc/ldap/certs/ldap.example.com.key
我们将无法针对ldap:/// URI 发出ldapsearch请求,直到我们对/etc/ldap/ldap.conf文件进行一些调整。我们将在下一节中这样做(在前面的章节中,我们使用了 Unix 套接字ldapi:///)。
与 SSF 合作
我们现在将讨论安全强度因子指令,或ssf。我们可以为我们的连接定义最小的安全强度,并为更敏感的角色指定更高安全强度的通信。
例如,如果我们有一个如下所示的sec.ldif文件,它描述了我们对某些连接类型所要求的安全因素:
dn: cn=config
changetype: modify
add: olcSecurity
olcSecurity: ssf=128 update_ssf=256 simple_bind=128 tls=256
ssf=128设置描述了基于加密密钥大小的服务所需的整体安全强度因子。更大的密钥意味着更强的加密强度。
如果我们在全局 DIT 中定义了ssf安全性,那么它是为所有其他 DIT 定义的。我们将其设置为 128,这是合理的,并且我们可以限制具有更强安全需求的更敏感的 DITs。update_ssf=256设置描述了目录更新所需的整体安全强度,simple_bind=128设置是simple_bind操作所需的安全因素。ssf值如下:
- 0(零)表示无保护。
1仅表示完整性保护。56允许 DES 或其他弱密码。112允许三重 DES 和其他强密码。- 允许 RC4、河豚和其他强密码。
- 256 允许 AES、SHA 密码。
默认为0。您可以在访问控制列表中组合它们,根据连接的安全强度来控制这些连接可以访问的内容。
我们目前不打算应用这个配置,但是我们已经在我们的访问控制列表中用ssf_tls=128保护了我们的敏感用户数据。
如果我们想对用户密码或其他敏感数据进行调整,我们现在必须用 TLS 证书的详细信息设置 LDAP 客户端。
设置您的 LDAP 客户端
Ubuntu 和 CentOS 都使用ldap.conf文件为客户端配置系统范围的 LDAP 默认值(还有另一种使用sssd程序的方法,我们将在“单点登录:集中式 Linux 身份验证”一节中讨论)。使用 OpenLDAP 库的应用程序将使用这些文件来获取 LDAP 细节。你会在目录/etc/ldap中找到 Ubuntu 的文件,在/etc/openldap中找到 CentOS 的文件。
Note
重要的是,不要把这个与libnss-ldap文件提供的文件混淆,这个文件也叫做ldap.conf,在两个发行版中都可以找到:/etc/ldap.conf。该文件用于为您的系统配置用户和主机信息,而/etc/(open)ldap/ldap.conf供ldapmodify、ldapadd等 OpenLDAP 工具使用。
您需要通过添加以下几行文本来编辑您的ldap.conf文件。在我们的例子中,我们将稍微欺骗一下,不用担心为 LDAP 客户端设置客户端 SSL 证书。如果这台主机用于复制我们的 LDAP 服务器,我们肯定会确保服务器和客户机都启用了 SSL 验证。详情请查看ldap.conf的man页面。
URI ldap://ldap.example.com/
BASE dc=example,dc=com
TLS_CACERT /etc/ldap/certs/cacert.pem
TLS_REQCERT demand
URI指向我们的 LDAP 服务器。BASE是 LDAP 操作的默认基本 DN。TLS_CACERT指向我们的 CA 证书文件,其中将包含我们的example.com CA 证书。在某些客户端上,您可能已经将 CA 证书安装到默认位置/etc/ssl/certs。我们在TLS_REQCERT字段中指定的demand意味着我们将尝试验证证书,如果无法验证,我们将取消连接(这是默认设置)。其他选项有try,表示如果不提供证书,连接将继续,但如果提供了一个坏的证书,则立即停止连接;allow,这意味着如果提供的证书是坏的,会话仍然可以继续;和never,这意味着在建立连接之前,您的主机不会请求或检查服务器证书。
如果您正在查看 CentOS 主机,您很可能会在/etc/pki/tls/certs目录中找到您的 SSL CA 证书。
LDAP 管理和工具
那么如何用 LDAP 管理条目呢?有几种工具可用于此目的。使用命令行,您可以从文本文件添加条目、搜索现有条目以及删除条目。文本文件必须是一种叫做 LDIF 的格式。LDIF 文件的格式如下:
dn: <dn entry>
objectclass: <objectclass to be included>
attribute: <attribute value described in an objectclass>
通常,为处理的不同部分创建单独的 LDIF 文件是个好主意。比如ou=people,dc=example,dc=com下的东西都可以在people.ldif里,ou=groups,dc=example,dc=com下的东西都可以在groups.ldif里。或者,对于新的 LDAP 服务器,您可以将所有条目放在一个文件中,但是要注意,在具有现有条目的 LDAP 服务器中,如果您再次尝试添加现有条目,将会出现错误。在这种情况下,可以在条目的每一行的开头使用#符号,在 LDIF 文件中注释掉该条目。LDAP 工具可以通过使用–f filename选项来使用 LDIF 文件,我们将在下面的章节中详细介绍。
管理条目的另一种方法是使用众多可用的 GUI 工具之一。我们将在“LDAP 帐户管理器:基于 web 的 GUI”一节中展示如何安装和配置基于 Web 的 GUI。
LDIFs 和添加用户
DIT 的顶端是rootDN。DIT 从dcObject类的声明开始。下面是我们将用来填充 LDAP 服务器的 LDIF 文本文件的一个片段:
dn: dc=example,dc=com
objectclass: dcObject
objectClass: organization
dc: example
o: example
这表明我们将创建rootDN dc=example,dc=com。根据 Ubuntu 上core.schema中的dcObject对象类,我们必须包含dc属性。让我们看看来自core.schema文件的对象类声明:
objectclass ( 1.3.6.1.4.1.1466.344 NAME 'dcObject'
DESC 'RFC2247: domain component object'
SUP top AUXILIARY MUST dc )
您可以看到我们如何在 DN: dc=example, dc=com的声明中使用前面的 object 类。我们指定了被指示使用的dc属性,由对象定义中的MUST子句指示。记住,这是一个AUXILLARY对象类,不能用于创建条目。我们需要一个STRUCTUAL对象类,而organization就是这样一个类。它需要organization的o属性。我们将在下一节添加用户时添加这个条目。这应该是您添加到 LDAP 服务器的第一个条目。
这里需要注意的是,根据你使用的是不是 Ubuntu,rootDN可能已经存在。您可以通过运行以下命令来找到答案:
$ sudo ldapsearch -D "cn=admin,dc=example,dc=com" -b "dc=example,dc=com" –ZZ -H ldap://ldap.example.com –W
# example.com
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: example
dc: example
如果是这种情况,请不要将其包含在users.ldif中,否则当您尝试添加它时,会出现如下错误:
adding new entry "dc=example,dc=com"
ldap_add: Already exists (68)
接下来,我们希望在我们的组织中设置用户,因此我们现在将声明我们的people组织单位。如果需要的话,我们可以将这个部分单独放入一个新文件中,只存放我们的people条目。
dn: ou=people,dc=example,dc=com
objectclass: organizationalUnit
ou: people
您可以看到,LDIF 格式要求声明 DN,后面是我们想要使用的对象类和属性。每个声明都应该用一个空行隔开。声明的顺序也很重要;在创建组织单位之前,您不能在ou=people,dc=example,dc=com中创建用户。对象类organizationalUnit要求我们像这里的ou: people一样声明ou属性。
现在我们要添加一个用户,jsmith。
dn: uid=jsmith,ou=people,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: posixAccount
objectclass: exampleClient
cn: Jane Smith
sn: Smith
uid: jsmith
uidNumber: 1000
gidNumber: 1000
exampleActive: TRUE
homeDirectory: /home/jsmith
userPassword: {SSHA}IZ6u7bmw12t345s3GajRt4D4YHkDScH8
所以,我们先来看看 DN。您可以看到我们使用uid属性声明了我们的 DN。除了uid=jsmith: cn=Jane Smith或uid=jane.smith@example.com,我们还可以在这里使用一些变体。您最终在您的系统上使用哪一个取决于您认为哪一个最适合您的服务器(记住索引),并且您应该意识到这些必须是唯一的(SINGLE-VALUE)。
接下来,我们必须声明对象类top和person。top对象类,一个ABSTRACT超类,需要提供其他对象类,并用于终止层次结构。
person对象类提供了sn(姓氏)和cn(常用名)属性。让我们快速查看一下这个对象类的模式。
objectclass ( 2.5.6.6 NAME 'person'
DESC 'RFC2256: a person'
SUP top STRUCTURAL
MUST ( sn $ cn )
MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
person对象类的父类是top ( SUP top),它是一个STRUCTURAL对象类,这意味着它的属性可以在 d it 中形成一个条目。对于person类,我们MUST提供了一个cn和一个sn条目。我们MAY提供userPassword、telephone、seeAlso和description。
包含posixAccount和exampleClient对象类是可选的。对象类posixAccount将提供对 Unix/Linux 主机有用的属性,例如userPassword、uid、uidNumber、gidNumber和homeDirectory。我们在模式部分创建的exampleClient对象类提供了exampleActive属性,我们可以用它来激活和停用我们的用户。请注意,布尔属性如exampleActive的属性值必须是大写的,因为它是一个SINGLE-VALUE属性,所以我们只能声明它一次。
我们现在将添加groups作为organizationalUnit,这样我们就可以利用组来管理对用户的访问。同样,我们可以为我们的组创建一个新的 LDIF 文本文件。
dn: ou=groups,dc=example,dc=com
objectclass: top
objectclass: organizationalUnit
ou: groups
您可以看到为groups创建组织单位与我们之前声明people组织单位的方式相似。按照模式定义的要求,我们使用并声明了ou属性来命名我们的 DN。接下来,我们声明将用于对管理员进行分组的组admins。
dn: cn=admins,ou=groups,dc=example,dc=com
objectclass: top
objectclass: groupOfNames
cn: admins
member: uid=ataylor,ou=people,dc=example,dc=com
我们用groupOfNames对象类声明一个组列表,这允许我们只添加成员。我们也可以使用posixGroup对象类,这也允许我们使用gidNumbers。我们可以通过在单独一行添加member: DN来添加任意多的成员。
现在我们可以看看如何将我们的详细信息添加到 LDAP 数据库中。为此,我们将使用 OpenLDAP 附带的ldapadd工具。
从 LDIF 文件添加用户
LDAP 工具都共享一组公共选项,您可以提供这些选项来连接到 LDAP 服务器。OpenLDAP 客户端工具可用于连接其他软件制造商提供的其他 LDAP 服务器。表 16-4 列出了大多数 LDAP 工具可用的公共选项。
表 16-4。
Common LDAP Tool Options
| [计]选项 | 描述 | | --- | --- | | `-x` | 执行简单绑定。 | | `-v` | 指定详细输出。 | | `-W` | 提示输入密码。 | | `-f` | 指向一个输入文件,该文件在不同的工具上下文中可以是不同的类型。 | | `-D` | 指定要绑定的 DN。此 DN 必须具有适当的访问权限才能处理条目。 | | `-Z` | 尝试使用 TLS 建立 LDAP 连接。`ZZ`表示在继续连接之前必须成功使用 TLS。 | | `-Y` | 指定连接到 LDAP 服务器的 SASL 身份验证机制。您必须将 SASL 配置为使用此选项。 | | `-X` | 指定 SASL authzid,或为 SASL 绑定请求的授权 id。 | | `-U` | 指定 SASL 绑定的 SASL 身份验证 id 或身份验证 ID。 | | `-b` | 指定基本 DN。不用查询整个树,你可以指定一个基数开始,比如`ou=people,dc=example,dc=com`。 | | `-s` | 指示搜索查询的范围。可以是`base`、`one`、`sub`或`children`。 |最常见的是,您将使用–D选项来指定您正在绑定的用户,以进行查询或修改,并将使用-xW选项来执行简单的绑定,并被提示输入密码(与 SASL 绑定–Y相反)。我们在表 16-4 中显示的一些选项并不适用于所有的 LDAP 工具。LDAP 工具命令的语法通常如下所示:
ldaptool <options> filter entry
有几个可用的 LDAP 工具。主要的选项有ldapadd、ldapmodify、ldapsearch和ldapdelete,正如我们所说的,它们都共享一些或所有前面显示的公共选项。有关可用的确切选项,请参考这些工具的man页面。在接下来的章节中,我们将举例说明如何使用这些命令和选项。
既然我们已经创建了 LDIF 文件,那么向 LDAP 服务器添加用户就很容易了。让我们看看完整的 LDIF 档案。正如我们已经提到的,我们需要在这个文件的顶部添加dc条目,或者我们的 DIT 的顶层(如果它已经存在,记得从文件中删除它)。如果您要添加数百个用户,您可能希望使用一个脚本或程序来处理现有用户(或在尝试重新添加现有用户或 DN 时生成的 LDAP 错误)。
$ sudo cat users.ldif
dn: dc=example,dc=com
objectclass: dcObject
objectclass: organizationalUnit
dc: example
ou: example
dn: ou=people,dc=example,dc=com
objectclass: organizationalUnit
ou: people
dn: uid=jsmith,ou=people,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: posixAccount
objectclass: exampleClient
cn: Jane Smith
sn: Smith
uid: jsmith
uidNumber: 1000
gidNumber: 1000
exampleActive: TRUE
homeDirectory: /home/jsmith
userPassword: {SSHA}IZ6u7bmw12t345s3GajRt4D4YHkDScH8
dn: uid=ataylor,ou=people,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: posixAccount
objectclass: exampleClient
cn: Angela Taylor
sn: Taylor
uid: ataylor
uidNumber: 1002
gidNumber: 1000
exampleActive: TRUE
homeDirectory: /home/ataylor
userPassword: {SSHA}PRqu69QU5WK5i8/dvqQuvFXo0xJ74OFG
dn: ou=meta,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
ou: meta
dn: cn=webadmin,ou=meta,dc=example,dc=com
objectClass: organizationalRole
objectclass: simpleSecurityObject
userPassword: {SSHA}KE0JMvJjYjQ/9lpigDCbLla5iNoBb8O8
dn: ou=groups,dc=example,dc=com
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: cn=staff,ou=groups,dc=example,dc=com
objectclass: top
objectclass: posixGroup
gidNumber: 1000
cn: staff
dn: cn=admins,ou=groups,dc=example,dc=com
objectclass: top
objectclass: groupOfNames
cn: admins
member: uid=ataylor,ou=people,dc=example,dc=com
dn: ou=hosts,dc=example,dc=com
objectclass: top
objectclass: organizationalUnit
ou: hosts
我们现在将使用文件users.ldif添加我们的用户。ldapadd工具是多功能的,并且有许多选项。我们将使用它的方式如下:
$ sudo ldapadd -D "cn=admin,dc=example,dc=com" -ZZ -H ldap://ldap.example.com
-xWv -f users.ldif
ldapadd命令可以使用 SASL 认证方法或简单方法。正如我们前面提到的,如果您设置了 SASL,您可以在不通过网络发送密码的情况下进行绑定,使用简单身份验证方法,密码被发送到 LDAP 服务器上进行验证,因此应该通过 TLS 之类的安全传输进行发送。-x会让ldapadd使用简单的方法。-W告诉ldapadd我们希望被提示输入密码。-v就是信息要啰嗦。当您指定-D时,您给出了想要绑定的用户名。在本例中,我们使用的是cn=admin,dc=example,dc=com用户,您可能还记得,这是我们添加到SLAPD server earlier中的rootDN。-h开关是主机名,ldap.example.com. -Z告诉命令使用STARTTLS,或者建立到 LDAP 主机的 TLS 连接,但是如果您已经在/etc/ldap/ldap.conf或/etc/openldap/ldap.conf中设置了 TLS,您的命令将会失败。最后,-f表示我们想要用来添加用户的文件,users.ldif。
这里使用的选项对于所有其他 LDAP 工具都是一样的;更多详情见man页。当您发出这个命令时,您将得到类似这样的内容:
jsmith@ldap:/etc/ldap$ ldapadd -xWv -D cn=admin,dc=example,dc=com
-h ldap.example.com -Z -f users.ldif
ldap_initialize( ldap://ldap.example.com )
Enter LDAP Password:
add objectclass:
top
person
exampleClient
posixAccount
add cn:
Jane Smith
add sn:
Smith
add uid:
jsmith
add uidNumber:
1000
add gidNumber:
1000
add exampleActive:
TRUE
add homeDirectory:
/home/jsmith
add userPassword:
{SSHA}IZ6u7bmw12t345s3GajRt4D4YHkDScH8
adding new entry "uid=jsmith,ou=people,dc=example,dc=com"
modify complete
如果成功,您将看到“修改完成”消息。如果出现问题,您将在控制台输出上收到一个错误,您可以使用journalctl来进一步检查生成的日志条目。
$ journalctl –xe –u slapd
如果您在日志中没有看到足够的细节,记得像前面描述的那样调整您的LogLevel条目。
搜索您的 LDAP 树
现在我们的 LDAP 数据库中有了一些条目,我们可以搜索它以确保我们可以返回有用的信息。让我们看看搜索 LDAP 目录的方法。
$ ldapsearch -xvW -H ldap://ldap.example.com -ZZ \
-D cn=admin,dc=example,dc=com \
-b ou=people,dc=example,dc=com -s sub \
'(&(&(objectclass=person)(uid=jsmith))(exampleActive=TRUE))' cn
我们用于搜索的参数类似于我们用于ldapadd命令的参数。我们首先指定我们正在执行一个带有详细输出的简单绑定,我们希望得到输入密码的提示,-xvW. -h声明我们想要连接的主机,-Z说尝试使用 TLS 建立连接(一个-ZZ意味着在继续之前确认 TLS 连接成功)。安吉拉·泰勒是我们放在cn=admins,ou=groups,dc=example,dc=com的用户;记住,我们已经通过访问控制列表给了write访问ou=people,dc=example,dc=com下所有条目的权限。我们刚刚将用户简·史密斯添加到我们的 LDAP 目录中,我们将进行搜索以查看她的详细信息。
在我们的ldapsearch命令中,您可以看到我们包含了一个过滤器来利用索引并减少我们的搜索响应时间。我们知道所有用户条目都有对象类person。正如我们解释过的,在我们的slapd.conf文件中,所有的对象类都被索引,所以选择一个你知道在你要找的实体中的对象将会加快你的搜索。uid属性也被编入索引,所以我们还想过滤我们正在寻找的条目的uid。我们的搜索过滤器如下所示:
(&(&(objectclass=person)(uid=jsmith))(exampleActive=TRUE)),
这些是从里到外读的;让我们开始第一部分。
&(objectclass=person)(uid=jsmith)
这意味着过滤对象类别为person AND uid is jsmith的条目。第二部分是这样的:
(&(<first match>)(exampleActive=TRUE))
这里,我们在第一次匹配时进行匹配,并且账户是活动的,如exampleActive=TRUE所示。&操作符表示我们正在搜索一个和另一个。我们也可以使用|符号来表示我们想要搜索其中一个。
我们指定我们希望开始搜索的 DIT 树的基础,-b ou=people, dc=example,dc=com,,我们的搜索范围是-s sub,或者它下面的所有内容。最后,我们在寻找简的普通名字,或cn。该搜索的结果将如下所示:
ldap_initialize( ldap://ldap.example.com )
filter: (&(&(objectclass=person)(uid=jsmith))(exampleActive=TRUE))requesting: cn
# extended LDIF
#
# LDAPv3
# base <ou=people,dc=example,dc=com> with scope subtree
# filter: (&(&(objectclass=person)(uid=jsmith))(exampleActive=TRUE))
# requesting: cn
#
# jsmith, People, example.com
dn: uid=jsmith,ou=people,dc=example,dc=com
cn: Jane Smith
# search result
search: 3
result: 0 Success
# numResponses: 2
# numEntries: 1
在这里,您可以看到我们已经返回了我们正在寻找的 DN 和那个条目的公共名称。接下来,我们来看看删除条目。
从 LDAP 目录中删除条目
另一件你需要经常做的事情是删除 LDAP 目录中的条目。要删除条目,使用ldapdelete命令。同样,这采用了与ldapadd和ldapsearch相同的参数。要删除多个条目,您可以输入文本文件,也可以单独删除条目。假设我们在一个新的users.ldif文件中有以下条目,我们想删除它们。
uid=jbob,ou=people,dc=example,dc=com uid=tbird,ou=people,dc=example,dc=com
我们现在可以将这两个条目添加到名为deluser.ldif的文件中,然后运行带有-f参数的ldapdelete命令,如下所示:
ldapdelete -xvW -D uid=ataylor,ou=people,dc=example,dc=com \
-h ldap.example.com -Z -f deluser.ldif
ldap_initialize( ldap://ldap.example.com )
deleting entry "uid=jbob,ou=people,dc=example,dc=com"
deleting entry "uid=tbird,ou=people,dc=example,dc=com"
因此,这些条目已不在我们的目录中,并已被删除。
Note
OpenLDAP 不区分大小写,这意味着uid=jsmith,ou=people,dc=example,dc=com与uid=jSmith,ou=people,dc=example,dc=com被同等对待。尝试添加两个 Jane Smiths,一个带有小写 S,一个带有大写 S,将返回重复错误。
密码策略覆盖
通过设置密码策略覆盖,我们可以更好地控制密码老化和更改历史。如前所述,覆盖层为 OpenLDAP 服务器提供了额外的功能。我们希望将我们的密码时效设置为7776000 (90 天(秒)】,将我们的密码历史设置为3,这意味着我们将存储用户提供的前三个密码,这样他们就不能一直使用同一个密码。密码至少需要八个字符。
添加我们的策略覆盖
是时候添加我们的密码策略覆盖了。如前所述,覆盖层提供了 OpenLDAP 服务器通常不提供的某些附加功能。在本例中,我们声明了 ppolicy 覆盖,它有助于管理我们的密码。
我们将创建一个名为ppolicy.ldif的文件,其内容如下:
dn: ou=policies,dc=example,dc=com
objectClass: organizationalUnit
ou: policies
dn: olcOverlay={0}ppolicy,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: {0}ppolicy
olcPPolicyDefault: cn=default,ou=policies,dc=example,dc=com
olcPPolicyHashCleartext: FALSE
olcPPolicyUseLockout: TRUE
olcPPolicyForwardUpdates: FALSE
首先,您可以看到我们创建了一个organizationalUnit,一种在我们的 DIT 中收集类似项目的方式,称为ou=policies,dc=example,dc=com。这提供了放置我们稍后将定义的默认策略的结构。
ppolicy 覆盖提供了某些功能,可让您更好地控制 LDAP 服务器上的密码安全性。OpenLDAP 本身不提供密码管理特性,比如密码到期和密码历史。这个覆盖允许您声明一个策略,或者将不同的策略与 DIT 树的不同部分相关联。这里我们用 DN cn=default,ou=Policies,dc=example,dc=com声明一个默认策略。我们还声明希望使用策略的锁定功能。这允许我们在请求客户端被锁定时向其发回消息。这可以为攻击者提供信息,然后他们将知道用户名是否存在,所以您可能需要关闭它。我们在这个阶段也关闭HashCleartext和ForwardUpdate。
我们现在必须定义实际的策略,该策略设置我们希望在密码体系中实施的值。为此,我们需要将以下 LDIF 添加到我们的 LDAP 服务器:
dn: cn=default,ou=policies,dc=example,dc=com
objectClass: top
objectClass: device
objectClass: pwdPolicy
cn: default
pwdAttribute: userPassword
pwdMaxAge: 7776000
pwdExpireWarning: 6912000
pwdInHistory: 3
pwdCheckQuality: 1
pwdMinLength: 8
pwdMaxFailure: 4
pwdLockout: TRUE
pwdLockoutDuration: 1920
pwdGraceAuthNLimit: 0
pwdFailureCountInterval: 0
pwdMustChange: TRUE
pwdAllowUserChange: TRUE
pwdSafeModify: FALSE
我们现在已经将密码策略添加到我们的 LDAP 服务器中,它有一些基本设置,如密码年龄(90 天,以秒为单位)和历史密码(3)。这个覆盖现在将使所有密码帐户遵守密码策略。
我们将使用ldapadd来应用这个ppolicy.ldif文件。在本例中,由于我们正在修改 SLAPD 数据库配置,我们将使用本地 root 用户的权限来进行这一更改:
$ sudo ldapadd -H ldapi:/// -Y EXTERNAL -f ppolicy.ldif
Note
参见man slapo-ppolicy页了解密码策略覆盖的更多详情。
测试您的访问控制列表
由于访问控制列表功能不正确,您有时会遇到权限问题。有一个叫做slapacl的工具可以用来测试你的 ACL。此工具通过要授予访问权限的 DN 来测试对属性和对象类的访问权限。例如,如果我们想要确保 DN cn=webadmin,ou=meta,dc=example,dc=com,即我们在认证期间用来绑定我们的 web 服务的用户,拥有对我们的用户 Angela Taylor 的userPassword属性的auth访问权,我们将发出以下命令:
sudo slapacl -F /etc/ldap/slapd.d \
-b uid=ataylor,ou=people,dc=example,dc=com \
-D cn=webadmin,ou=meta,dc=example,dc=com \
-o tls_ssf=128 \
-v userPassword/auth
slapacl命令需要sudo访问。您需要在 Ubuntu 主机上使用-F /etc/ldap/slapd.d指定您想要测试的slapd config目录;在 CentOS 主机上,您需要使用/etc/openldap/slapd.d目录。-b uid=ataylor,ou=people,dc=example,dc=com DN 是我们想要在其上测试我们的访问的 DN。-D cn=webadmin,ou=meta,dc=example,dc=com是我们要确认的 DN 是否有auth访问uid=ataylor,ou=people,dc=example,dc=com的 DN。–o允许我们提供与slapd访问相关的选项。在这种情况下,因为我们需要模拟我们的 TLS 安全强度因子,所以我们添加了tls_ssf=128。–v用于详细说明。我们指定想要测试的属性和认证级别,在本例中是针对访问权限auth的属性userPassword。如您所知,我们至少需要对 DN cn=webadmin,ou=meta,dc=example,dc=com的auth访问权,以便与userPassword进行认证。如果我们成功了,我们会得到以下结果:
authcDN: "cn=webadmin,ou=meta,dc=example,dc=com"
auth access to userPassword: ALLOWED
这证实了我们的访问控制列表中的行像我们预期的那样工作。
olcAccess: {1}to attrs=userPassword,shadowLastChange,entry,member
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" tls_ssf=128 auth
我们将进行测试,看看我们是否可以对同一个属性进行写访问,以确认我们的访问控制列表中没有安全漏洞。
sudo slapacl -F /etc/ldap/slapd.d \
-b uid=ataylor,ou=people,dc=example,dc=com \
-D cn=webadmin,ou=meta,dc=example,dc=com \
-o tls_ssf=128 \
-v userPassword/write
authcDN: "cn=webadmin,ou=meta,dc=example,dc=com"
write access to uid: DENIED
这是我们所期望的:我们应该被拒绝除了auth访问和以下的一切。您还可以传入其他选项,这些选项允许您根据诸如peernames和ssf之类的东西来测试访问。
了解访问控制列表情况的另一个有用的方法是,在测试时在访问控制列表中包含以下内容,这可能很难做到正确:
access to * by * search
您可以将此与修改您的SLAPD中的日志配置结合起来,将您的日志级别更改为如下所示:
olcLogLevel: 416
这将显示搜索过滤器和访问控制列表处理,以及连接管理和配置文件处理。您可以使用journalctl命令来访问日志。当一个请求进来时,它将产生如下输出:
$ journalctl –xe –u slapd
slapd[1350]: conn=1011 op=2 SRCH base="ou=people,dc=example,dc=com" scope=2 deref=0 filter=”(&(objectClass=person)(uid=jsmith))"
slapd[1350]: conn=1011 op=2 SRCH attr=uid
...<snip>...
slapd[1350]: => access_allowed: read access to "uid=jsmith,ou=people,dc=example,dc=com" "uid" requested
slapd[1350]: => acl_get: [1] attr uid
slapd[1350]: => acl_mask: access to entry "uid=jsmith,ou=people,dc=example,dc=com", attr "uid" requested
slapd[1350]: => acl_mask: to value by "uid=ataylor,ou=people,dc=example,dc=com", (=0)
slapd[1350]: <= check a_dn_pat: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
slapd[1350]: <= check a_dn_pat: *
slapd[1350]: <= acl_mask: [2] applying +0 (break)
slapd[1350]: <= acl_mask: [2] mask: =0
slapd[1350]: => dn: [3] ou=people,dc=example,dc=com
slapd[1350]: => acl_get: [3] matched
slapd[1350]: => acl_get: [3] attr uid
slapd[1350]: => acl_mask: access to entry "uid=jsmith,ou=people,dc=example,dc=com", attr "uid" requested
slapd[1350]: => acl_mask: to value by "uid=ataylor,ou=people,dc=example,dc=com", (=0)
slapd[1350]: <= check a_dn_pat: cn=webadmin,ou=meta,dc=example,dc=com
slapd[1350]: <= check a_group_pat: cn=admins,ou=groups,dc=example,dc=com
slapd[1350]: <= acl_mask: [2] applying write(=wrscxd) (stop)
slapd[1350]: <= acl_mask: [2] mask: write(=wrscxd)
slapd[1350]: => slap_access_allowed: read access granted by write(=wrscxd)
slapd[1350]: => access_allowed: read access granted by write(=wrscxd)
第一行显示了请求的搜索字符串SRCH base="ou=people,dc=example,dc=com" scope=2 deref=3 filter="(&(objectClass=*)(uid=jsmith))",我们正在寻找属性 UID。
输出还显示了发出请求的用户,uid=ataylor,ou=people,dc=example,dc=com。您可以看到,访问请求的过程从搜索ou=people,dc=example,dc=com开始,然后通过check a_group_pat: cn=admins,ou=groups,dc=example,dc=com最终接受搜索(和读取),这给了 ataylor 写访问权限。
通过日志、slapacl和ldapsearch工具以及有用的 OpenLDAP 邮件列表的组合,您可以实现复杂的访问控制列表。让我们看看可以用来管理 LDAP 服务器的其他工具,包括我们刚刚提到的ldapsearch工具。
备份您的 LDAP 目录
基于文本的文件非常适合构建或恢复 LDAP 目录。一旦实现了您的目录,我们建议您设置一个脚本,该脚本可能会定期将您的 LDAP 数据库输出到一个文本文件中并保存它。在第十四章中,我们向您介绍了 Bareos 备份服务器,并向您展示了在备份 MySQL 数据库时如何使用Client Run Before Job和Client Run After Job选项。您可以对 LDAP 数据库做类似的事情,如清单 16-4 所示。
#!/bin/bash
case $1 in
start)
slapcat -b dc=example,dc=com -l /var/lib/ldap/backup.ldif
if [ $? -eq 0 ] ; then
echo "backup successful"
else
echo "backup failed"
exit 1;
fi
;;
stop)
if [ -e /var/lib/ldap/backup.ldif ] ; then
rm -f /var/lib/ldap/backup.ldif
if [ $? -eq 0 ] ; then
echo "removal of file successful"
else
echo "failed to remove file"
exit 1;
fi
fi
;;
esac
exit 0
Listing 16-4.slapcat LDIF Dump
为了获得一个完美的备份,您可能希望在运行清单 16-4 中的命令之前停止 OpenLDAP 目录服务器;然而,这并不总是可能的,热备份比完全不备份更可取。
使用Client Run Before Job脚本,使用slapcat命令将 LDAP 数据库转储到磁盘上的一个文件中。然后你让 Bareos 做备份;Bareos 可以通过运行Client Run After Job脚本删除它(参见清单 16-5 )。
Job {
Name = ldap.example.com
Client = ldap-fd
Enabled = yes
JobDefs = "DefaultLinux"
Client Run Before Job = "/usr/local/bin/ldap_backup start"
Client Run After Job = "/usr/local/bin/ldap_backup stop"
}
Listing 16-5.The Job Definition for Bareos Backup Service
这是基于这样一个条件:您已经在您的ldap.example.com主机上的/usr/local/bin中安装了 LDAP 备份脚本。
恢复您的 LDAP 数据库只需在您的主机上恢复文件,并运行带有以下参数的slapadd命令(在此过程中应该关闭 OpenLDAP):
$ slapadd -b dc=example,dc=com -F /etc/ldap/slapd.d -l restored.ldif.backup.file
这里,我们将 LDAP 数据库恢复到了上次保存时的状态。因为 LDAP 没有预写日志,所以您不能像在全功能的事务关系数据库中那样重放对 LDAP 目录服务器的最新更新,所以定期备份很重要。我们建议您至少每晚做一次备份。
通过文本文件管理你的目录服务器会变得很无聊。幸运的是,如果您是那些喜欢基于 web 的 GUI 为您做所有繁琐工作的人之一,我们有一个解决方案,我们将在接下来讨论这个问题。
LDAP 帐户管理器:基于 Web 的 GUI
有几个工具可以用来管理 LDAP 目录。我们决定在本书中关注其中的一个,LDAP 客户管理器(LAM)。这是一个基于 web 的 GUI,可以减轻更新文本文件的管理负担。它有两个版本:免费版和需要付费的企业版。如果您发现自己不喜欢使用这个工具,您可能希望尝试其他一些工具,例如:
- 亮度:
http://luma.sourceforge.net/ - GQ:
http://sourceforge.net/projects/gqclient/ - phpldapAdmin:
http://phpldapadmin.sourceforge.net/ - web2ldap:
https://web2ldap.de/web2ldap.html - LDAPAdmin:
https://github.com/ibv/LDAP-Admin(Linux 版)
有些是旧的,有些是新的。OpenLDAP 这些年来没有太大的变化,所以您可以自由地尝试您选择的任何一个。我们选择向您展示 LAM,因为它不仅设计用于管理 LDAP,还用于提供用户帐户。它允许您基于易于遵循的模板创建用户。它也足够灵活,允许您集成 Samba 用户管理,如果您愿意的话。
安装和配置
LAM 可以从 Ubuntu 的在线仓库下载。它已经发布了几个版本,如果你正在寻找一个更新的版本,你可以从网站的下载页面( https://www.ldap-account-manager.org/lamcms/releases )或者从 Debian 仓库获得.deb包。
对于 LAM,您需要在您的主机上安装高于或等于 5.2.4 的 PHP 版本。
要安装它,您可以发出以下命令:
$ sudo aptitude install php-mcrypt php-zip ldap-account-manager apache2
在撰写本文时,Ubuntu 资源库(5.2-1ubuntu1)中提供的版本不支持 Xenial 安装的默认 PHP (7.x)。解决方案是首先从下载页面安装.deb包,然后手动安装,就像我们使用 CentOS 一样。
对于 CentOS 主机,您必须从网站( https://www.ldap-account-manager.org/lamcms/releases )下载 Fedora/CentOS rpm。这将链接到一个 SourceForge 下载,您可以获得直接链接的副本(从 URL 中删除镜像信息)。然后使用yum安装软件包,如下例所示:
$ sudo yum install -y httpd php php-ldap php-xml
$ sudo yum install -y http://downloads.sourceforge.net/project/lam/LAM/5.5/ldap-account-manager-5.5-0.fedora.1.noarch.rpm
CentOS 安装将所有的 LAM 文件放在/usr/share/ldap-account-manager下。所有的配置文件都安装在/var/lib/ldap-account-manager/config/下。
LAM 相当容易配置。在 Ubuntu 上,一些配置文件被安装到/etc/ldap-account-manager中。您将看到 Apache web 服务器的示例配置和配置文件/etc/ldap-account-manager/config.cfg,其中包含 LAM 安装的默认用户名和密码。
$ sudo vi /etc/ldap-account-manager/config.cfg
# password to add/delete/rename configuration profiles
password: {SSHA}tj1yDeQfLJbmISXwh8JfjMb2ro3v5u44
# default profile, without ".conf"
default: lam
在config.cfg文件中,您可以看到我们已经将自己的密码设置为{SSHA}tj1yDeQfLJbmISXwh8JfjMb2ro3v5u44(注意您的所有权和权限)。我们还需要更改以下文件来添加我们自己的 LDAP 目录细节。首先,我们复制一份文件/var/lib/ldap-account-manager/config/lam.conf。然后我们用粗体字对/var/lib/ldap-account-manager/config/lam.conf文件进行修改。
$ sudo vi /var/lib/ldap-account-manager/config/lam.conf
# LDAP Account Manager configuration
# server address (e.g. ldap://localhost:389 or ldaps://localhost:636)
ServerURL: ldaps://ldap.example.com
# list of users who are allowed to use LDAP Account Manager
Admins: cn=admin,dc=example,dc=com
# password to change these preferences via webfrontend
Passwd: somepassword
# suffix of tree view
treesuffix: dc=example,dc=com
# maximum number of rows to show in user/group/host lists
maxlistentries: 30
# default language (a line from config/language)
defaultLanguage: en_GB.utf8:UTF-8:English (Great Britain)
# Number of minutes LAM caches LDAP searches.
cachetimeout: 5
# Module settings
modules: posixAccount_minUID: 1000
modules: posixAccount_maxUID: 30000
modules: posixAccount_minMachine: 50000
modules: posixAccount_maxMachine: 60000
modules: posixGroup_minGID: 1000
modules: posixGroup_maxGID: 20000
modules: posixGroup_pwdHash: SSHA
modules: posixAccount_pwdHash: SSHA
在这个文件的第一部分,我们添加了 LDAP 目录的细节,包括连接细节、树信息、Posix UID、GID 和机器号。当我们在 LAM 界面中创建新用户时,会用到 UID 和 GIDs 它们将在前面列出的范围内递增。
# List of active account types.
activeTypes: user,group,host
types: suffix_user: ou=people,dc=example,dc=com
types: attr_user: #uid;#givenName;#sn;#uidNumber;#gidNumber
types: modules_user: person,posixAccount,shadowAccount,exampleClient
types: suffix_group: ou=groups,dc=example,dc=com
types: attr_group: #cn;#gidNumber;#memberUID;#description
types: modules_group: posixGroup
types: suffix_host: ou=hosts,dc=example,dc=com
types: attr_host: #cn;#description;#uidNumber;#gidNumber
types: modules_host: account,posixAccount
# Access rights for home directories scriptRights: 750
在文件的最后一部分,我们详细说明了我们希望在activeTypes部分启用的帐户类型以及包含这些帐户的 LDAP 分支。LAM 管理工具将使用这些详细信息在 LDAP 服务器中为我们创建用户帐户。
为 LAM 添加 Apache 虚拟主机
我们将向我们的 web 服务器添加一个 Apache 虚拟主机来托管我们的 LAM 站点。在第十一章中,我们向你展示了如何建立一个 Apache 虚拟主机。web 服务可以在任何主机上运行;它不必与 LDAP 服务器在同一个主机上,但是在我们的例子中,情况就是这样。我们选择将这个站点放在我们的主机上,IP 地址为 192.168.0.1,DNS 名称为ldap.example.com,也称为headoffice.example.com。
Note
我们的headoffice.example.com主机现在可能超载了安全虚拟主机(https),我们可能不得不选择一个非标准端口,比如 8443,来运行我们的ldap.example.com网站。或者,我们可以在完全不同的主机上运行它。
在我们的 Ubuntu 主机上,我们将有如下配置:这个虚拟主机的主要部分已经由 LAM 包提供,可以在/etc/ldap-account-manager/apache.conf中找到。我们将把这个文件包含在我们的VirtualHost信息中。VirtualHost号将被放置在/etc/apache2/sites-available并被命名为ldap.example.com.conf。在 CentOS 上,我们会将文件包含在/etc/httpd/conf.d/目录中。
$ sudo vi /etc/apache2/sites-available/ldap.example.com.conf
<VirtualHost 192.168.0.1:443>
ServerName ldap.example.com
SSLEngine on
SSLCertificateFile /etc/ldap/certs/ldap.example.com.cert
SSLCertificateKeyFile /etc/ldap/certs/ldap.example.com.key
SSLCACertificateFile /etc/ldap/certs/cacert.pem
LogFormat "%v %l %u %t \"%r\" %>s %b" comonvhost
CustomLog /var/log/apache2/ldap.example.com_access.log comonvhost
ErrorLog /var/log/apache2/ldap.example.com_error.log
Loglevel debug
Include /etc/ldap-account-manager/apache.conf
</VirtualHost>
我们在<VirtualHost> </VirtualHost>标签之间创建了一个VirtualHost。在这个VirtualHost中,我们添加了指向我们的/etc/ldap/certs目录的 TLS/SSL 密钥,并在/var/log/apache2目录中创建了单独的日志文件,以帮助诊断与这个VirtualHost相关的任何问题。
我们已经包含了(Include /etc/.../apache.conf)LAM 包提供的 Apache 配置文件。这允许该包由包管理器管理并相应地更新。
在 Ubuntu 上,我们需要启用站点,并确保 SSL 也已启用。我们通过以下方式做到这一点:
$ sudo a2ensite ldap.example.com.conf
$ sudo a2enmod ssl
在 CentOS 主机上,/etc/httpd/conf.d/目录中有一个lam.apache.conf文件。您可能希望将 Ubuntu 示例中的VirtualHost指令添加到该文件的副本中,以包含 SSL 和日志指令。如果不这样做,LAM GUI 将从 http://ldap.example.com/lam 可用。有关管理 CentOS 虚拟主机的更多信息,请参考第十一章。
接下来,我们启动 Apache web 服务器,将浏览器指向 https://ldap.example.com/lam 。我们现在看到的是 LAM 配置工具的登录页面,如图 16-4 所示。
图 16-4。
LAM login page
请注意右上角的 LAM 配置链接。这用于对 LAM 配置工具执行一般维护。在登录页面,您将被要求输入您添加到/var/lib/ldap-account-manager/config.cfg的密码。您可以在这里更改管理员的常规登录设置和密码。
您在图 16-4 中看到的admin用户就是我们在/var/lib/ldap-account-manager/config/lam.conf中指定的rootDN。当我们输入rootDN的密码时,我们会看到已经配置好的用户,如图 16-5 所示。
图 16-5。
Front page of the LAM web GUI
我们现在将使用标准配置文件创建一个新用户。简档用作创建用户的模板。我们首先点击 New User 按钮,调出如图 16-6 所示的页面。
图 16-6。
Creating a new user
我们现在需要单击“个人”选项卡,并填写所需的详细信息。我们将在个人选项卡中输入的唯一详细信息是名字、姓氏和描述(见图 16-6);当然,您可以根据需要添加更多细节。
在 Unix 选项卡中,我们填写添加 Unix/Linux 帐户所需的详细信息,如图 16-7 所示。如果我们不输入 UID 号,将根据我们在lam.conf文件中指定的限制为我们生成一个。我们将用户附加到 staff 组,如果组已经存在,我们可以通过单击“Edit groups”按钮添加更多用户。
图 16-7。
Linux(Unix) details
在启用阴影部分之前,我们需要启用阴影扩展。完成后,您将看到图 16-8 中的屏幕。
图 16-8。
Shadow details
在“Shadow”选项卡中,我们将保持默认值不变。你可以在图 16-9 中看到这些默认值。
图 16-9。
User add complete
要完成创建我们的用户,我们返回到主选项卡并单击 Create Account 按钮。然后我们会看到一个如图 16-9 所示的确认屏幕,显示操作成功。
如图 16-10 所示,我们的新用户tbird已经创建完毕。
图 16-10。
Listing the new user
您可以使用 LAM 在 LDAP 目录中添加和删除组和主机条目,我们将留给您自己进一步探索。记住,LAM 不是唯一的 LDAP 管理工具。如果您不喜欢这个 LDAP 管理客户端来管理您的 LDAP 服务,我们建议您尝试我们前面提到的其他客户端之一。
有关配置 LAM 的更多文档,请访问:
与其他服务的集成
部署 LDAP 服务器的主要目的是能够将需要认证的不同服务集成到一个认证服务中。我们希望在尽可能多的服务中使用相同的用户名和密码。这使我们能够更好地管理用户,并允许我们在所有服务中设置通用的密码管理策略,从而提供更高的安全性。
我们的第一步将是集中我们的 Linux 认证,以便我们所有的 Linux 桌面和服务器共享相同的认证凭证。接下来,我们将展示如何向 web 服务添加 LDAP 认证。最后,我们将展示一个基于 web 的应用程序如何将 LDAP 用于其认证服务。
单点登录:集中式 Linux 认证
我们现在将向您展示如何在您的 Linux 主机上集中您的用户帐户。拥有几个 Linux 主机,每个主机上有几个用户帐户,管理起来会很麻烦。密码可能会不同步,并且当用户离开您的公司时,您可能不会将他们从您的所有主机上删除,从而产生潜在的安全风险。为了简化这种用户管理,您可以通过将 Linux 主机指向 LDAP 服务器来集中您的认证服务。为了向您展示如何做到这一点,我们将首先安装必要的软件,然后检查配置中使用的文件。好的一面是,您应该能够使用发行版提供的身份验证工具来配置使单点登录工作的必要文件。
我们需要修改我们的访问列表。我们需要匿名访问某些条目。当我们对 LDAP 服务器进行身份验证时,我们需要首先访问某些条目。有两种方法可以做到这一点;第一种是使用绑定 DN 和密码,就像代理一样,绑定并访问条目,第二种是匿名绑定,其中不进行绑定(记住绑定是身份验证的另一种说法)。如果我们使用代理绑定 DN,我们需要在每台连接的主机上设置明文密码。在本练习中,我们选择不这样做。
除了简单身份验证,您还可以选择使用其他几种身份验证方法。如果你愿意,你可以用 SASL 或 Kerberos 设置认证,我们在第十三章讨论了这些认证方法。
访问控制列表现在看起来像这样。你会看到粗体的变化;我们已经给了 anonymous 读取某些属性的能力,这些属性是在认证过程中查找的,并且已经给了auth访问userPassword属性的权限。我们还防止用户更改他们不应该拥有写权限的某些属性,比如uidNumber、homeDirectory等等。
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to *
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by * none break
-
add: olcAccess
olcAccess: {1}to attr=entry,member,objectClass,uid,uidNumber,gidNumber,homeDirectory,cn,shadowWarning,modifyTimestamp
by group.exact="cn=admins,ou=Groups,dc=example,dc=com" tls_ssf=128 write
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" tls_ssf=128 read
by anonymous tls_ssf=128 read
by self tls_ssf=128 read
-
add: olcAccess
olcAccess: {2}to attrs=userPassword
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" tls_ssf=128 auth
by group.exact="cn=admins,ou=Groups,dc=example,dc=com" tls_ssf=128 write
by self tls_ssf=128 write
by anonymous tls_ssf=128 auth
by * none stop
-
add: olcAccess
olcAccess: {3}to dn.children="ou=People,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=Groups,dc=example,dc=com" write
by self write
by users read
-
add: olcAccess
olcAccess: {4}to dn.children="ou=Groups,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=Groups,dc=example,dc=com" write
-
add: olcAccess
olcAccess: {5}to dn.children="ou=meta,dc=example,dc=com"
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" read
by group.exact="cn=admins,ou=Groups,dc=example,dc=com" write
by self read
-
add: olcAccess
olcAccess: {6}to dn.children="ou=Hosts,dc=example.com"
by group.exact="cn=admins,ou=Groups,dc=example,dc=com" write
by dn.exact="cn=webadmin,ou=meta,dc=example,dc=com" search
-
add: olcAccess
olcAccess: {7}to * by * none
设置 sssd
为了从我们的 Linux 客户机对 LDAP 服务器进行认证,我们将安装一个名为sssd的包。它被设计为一个守护进程,将根据包括 LDAP 在内的各种身份验证服务进行身份验证。
在 Ubuntu 上,您需要安装以下软件包:
$ sudo aptitude install sssd
当然,在 CentOS 上,您可以使用 YUM 安装相同的包名。它们也采用相同的配置。我们需要创建一个名为/etc/sssd/sssd.conf的文件。此文件需要设置 0600 权限。
该文件本身将包含以下内容:
[sssd]
config_file_version = 2
services = nss, pam
domains = LDAP
[domain/LDAP]
cache_credentials = true
id_provider = ldap
auth_provider = ldap
ldap_uri = ldap://ldap.example.com
ldap_search_base = dc=example,dc=com
ldap_id_use_start_tls = true
ldap_tls_reqcert = demand
ldap_tls_cacert = /etc/ssl/certs/cacert.pem
chpass_provider = ldap
ldap_chpass_uri = ldap://ldap.example.com
entry_cache_timeout = 6
ldap_network_timeout = 2
ldap_group_member = uniquemember
ldap_pwdlockout_dn = cn=ppolicy,ou=policies,dc=example,dc=com
ldap_access_order = lockout
该文件中的主要指令可以在man sssd.conf和sssd-ldap中找到。但是第一部分告诉sssd我们将使用nss和pam来运行我们的 LDAP 域。在 LDAP 域部分,我们有id和auth的提供者,然后是连接细节,包括 TLS 设置和密码策略细节。
当 Linux 寻找一条信息,比如主机或密码时,它会检查一个名为/etc/nsswitch.conf的文件,看看在哪里可以找到这条信息。需要用以下信息更新nsswitch.conf文件,告诉它对passwd、group和shadow文件使用sssd;这是登录所需的信息。
passwd: files sss
group: files sss
shadow: files sss
gshadow: files
hosts: files mdns4_minimal [NOTFOUND=return] dns
networks: files
protocols: db files
services: db files sss
ethers: db files
rpc: db files
# pre_auth-client-config # netgroup: nis
netgroup: nis sss
sudoers: files sss
左边是我们要寻找的信息,右边是我们在哪里寻找这些信息以及寻找它们的顺序。例如,当我们需要通常包含在/etc/passwd文件中的信息(如用户名)时,我们首先查看该文件。如果在文件中没有找到用户名,我们就查询sssd守护进程(或sss)。这同样适用于group和shadow。
我们需要更新的下一部分是 PAM 身份验证模块。我们需要允许我们通过 PAM 使用sss(d)进行认证。在 Ubuntu 上,我们通过改变以下内容来做到这一点(我们已经从示例中删除了注释):
/etc/pam.d/common-auth
auth [success=3 default=ignore] pam_unix.so nullok_secure
auth [success=2 default=ignore] pam_sss.so use_first_pass
auth [success=1 default=ignore] pam_ldap.so use_first_pass
auth requisite pam_deny.so
auth required pam_permit.so
/etc/pam.d/common-password
password requisite pam_pwquality.so retry=3
password [success=3 default=ignore] pam_unix.so obscure use_authtok try_first_pass sha512
password sufficient pam_sss.so use_authtok
password [success=1 user_unknown=ignore default=die] pam_ldap.so use_authtok try_first_pass
password requisite pam_deny.so
password required pam_permit.so
password optional pam_gnome_keyring.so
/etc/pam.d/common-account
account [success=2 new_authtok_reqd=done default=ignore] pam_unix.so
account [success=1 default=ignore] pam_ldap.so
account requisite pam_deny.so
account required pam_permit.so
account sufficient pam_localuser.so
account [default=bad success=ok user_unknown=ignore] pam_sss.so
/etc/pam.d/common-session
session [default=1] pam_permit.so
session requisite pam_deny.so
session required pam_permit.so
session optional pam_umask.so
session required pam_unix.so
session optional pam_sss.so
session optional pam_ldap.so
session optional pam_systemd.so
session required pam_mkhomedir.so skel=/etc/skel umask=0022
当你在 Ubuntu 上安装了sssd之后,这些文件就会自动更新,我们不需要修改它们。你会注意到在common-session文件的末尾,我们允许认证用户创建他们自己的主目录,并用/etc/skel的内容填充它们。
在 CentOS 上,您可能需要自己将这些pam指令添加到/etc/pam.d/system-auth中。这两个发行版略有不同,但基本相同。
auth required pam_env.so
auth sufficient pam_fprintd.so
auth sufficient pam_unix.so nullok try_first_pass
auth sufficient pam_sss.so use_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so
account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account [default=bad success=ok user_unknown=ignore] pam_sss.so
account required pam_permit.so
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password sufficient pam_sss.so use_authtok
password required pam_deny.so
session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session optional pam_mkhomedir.so skel=/etc/skel umask=0022
session required pam_unix.so
我们在第五章中讨论了配置 PAM,但是我们将在接下来的“PAM 如何工作”一节中进一步讨论。
现在我们已经配置了 PAM 和sssd,我们只需要确保我们可以从客户端连接到 LDAP 服务器。在我们的sssd.conf文件中,我们指定了ldap_tls_cacert。我们需要确保将/etc/ssl/certs/cacert.pem安装在正确的位置;否则,我们将被 LDAP 服务器拒绝。
一旦我们将cacert.pem放在正确的位置,我们就可以测试是否可以查询 LDAP 服务器上的用户。
$ grep ataylor /etc/passwd
在我们的主机上,这没有返回任何结果,这意味着用户ataylor还没有被创建,但是我们在 LDAP 配置中创建了她。我们将使用名为getent的命令来查询passwd文件和sssd(根据我们在nsswitch.conf中的配置指示;getent是查询那些条目的工具)。
$ getent passwd ataylor
ataylor:*:1002:1000:Angela Taylor:/home/ataylor:
我们已经返回了 Angela Taylor 的用户详细信息,包括 UID/GID 和主目录信息。这意味着我们可以成功地与 LDAP 服务器对话并返回信息。
下一步是证明我们可以以她的身份登录。为此,我们将使用su命令,或超级用户命令。此命令允许您以 root 用户身份登录或登录到另一个帐户。当我们发出这个命令时,我们将被要求提供 Angela 的密码。
jsmith@au-mel-ubuntu-2:∼$ su - ataylor
Password:
ataylor@au-mel-ubuntu-2:∼$
在这里,我们已经成功地登录到一个只存在于 LDAP 中的用户。我们第一次登录时,已经创建了一个主目录。现在,任何 LDAP 用户都可以登录我们的主机并创建他们的主目录。
我们可以进一步细化我们的sssd.conf文件来过滤某些用户,比如只有当他们将exampleActive设置为TRUE时。
ldap_access_filter = (exampleActive=TRUE)
如果您使用的是 Ubuntu Unity 桌面,您需要对以下文件进行更改,以允许其他用户从桌面登录:
/usr/share/lightdm/lightdm.conf.d/50-unity-greeter.conf
[Seat:*]
greeter-session=unity-greeter
greeter-show-manual-login=true
这将允许你在重启 Ubuntu 桌面后看到如图 16-11 所示的屏幕。输入用户名后,输入密码,如图 16-12 所示。
图 16-12。
Providing the LDAP password
图 16-11。
Logging in via LDAP to the desktop
您也可以使用 CentOS 提供 LDAP 用户名和密码,而无需进行任何特殊配置。
PAM 是如何工作的
正如在第五章中所解释的,Linux 可以使用可插拔认证模块(PAM)来认证你的 LDAP 服务。PAM 针对 LDAP 服务器为主机提供身份验证、授权和密码更改功能。PAM 通过位于/etc/pam.d目录中的文件进行配置。如第五章所述,CentOS 主机上的主 PAM 文件是/etc/pam.d/system-auth文件。清单 16-6 显示了在您的主机上建立 LDAP 认证所需的设置示例。
auth required pam_env.so
auth sufficient pam_fprintd.so
auth sufficient pam_unix.so nullok try_first_pass
auth sufficient pam_sss.so use_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so
account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account [default=bad success=ok user_unknown=ignore] pam_sss.so
account required pam_permit.so
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password sufficient pam_sss.so use_authtok
password required pam_deny.so
session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session optional pam_mkhomedir.so skel=/etc/skel umask=0022
session required pam_unix.so
Listing 16-6.PAM Settings for system-auth on CentOS
这个文件是为您生成的,除非有充分的理由,否则您不应该自己修改它。从清单 16-6 可以看出,该文件由四个独立的管理组组成:auth、account、password和session。
看看下面一行,这是auth管理组的一个例子:
auth sufficient pam_sss.so use_first_pass
该组通常通过某种密码质询-响应机制来验证用户。sufficient控制值表示,如果该模块成功,则认为用户通过了身份验证。pam_sss.so是要使用的 PAM 共享对象,即决定如何对用户进行身份验证的代码。最后,use_first_pass是一个可选的语法,它告诉你不要再次询问你的密码,而是使用栈中较高模块提供的第一个密码。
在 Ubuntu 主机上,对应的文件是/etc/pam.d目录下的common-auth、common-password、common-session和common-account。
Note
你可以在系统管理员指南中阅读更多关于 PAM 的内容: www.linux-pam.org/Linux-PAM-html/Linux-PAM_SAG.html 。
PAM 针对 LDAP 服务进行身份验证的另一个核心文件是/etc/nsswitch.conf。该文件要求passwd、group和 shadow 关键字具有以下值:
passwd: files ldap
group: files ldap
shadow: files ldap
正如我们已经解释过的,它们告诉 PAM 使用什么认证数据库以及使用它们的顺序。因此,当我们寻找通常会在/etc/passwd中找到的信息时,我们会首先使用主机上的文件,然后使用 LDAP。同样的道理也适用于group和shadow。PAM 和nsswitch.conf文件应该由您的发行版提供的认证配置工具来配置。
Note
使用不同的身份验证服务时,您可能需要映射某些属性。当认证服务需要 OpenLDAP 通常不提供的某个属性时,例如 AD 服务器需要的属性,就完成了属性的映射。你可以在 https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Windows_Integration_Guide/sssd-ad-integration.html 找到做这件事的红帽文档。
LDAP 和 Apache 认证
现在让我们看看如何让我们的 web 服务器使用我们的 LDAP 服务器来认证客户端。当客户试图访问 https://ldap.example.com 网站时,他们将被要求输入其 LDAP 用户名和密码才能获得访问权限。我们将对我们的 web 服务器做两件事来实现这一点:通过在我们的 web 主机上启用 SSL 来确保我们与 web 服务器的所有通信的安全,并将 LDAP 细节添加到ldap.example.com虚拟主机。
Note
第十一章讨论了 Apache 虚拟主机。
我们将假设它是从ldap.example.com主机上运行的,并且没有其他 Apache 服务在其上运行。
接下来,让我们检查我们将对我们的ldap.example.com虚拟主机文件进行的更改。
$ sudo vi /etc/apache2/sites-available/ldap.example.com.conf
LDAPTrustedGlobalCert CA_BASE64 /etc/ldap/certs/cacert.pem
LDAPTrustedMode TLS
<VirtualHost 192.168.0.1:443>
ServerName ldap.example.com
SSLEngine on
SSLCertificateFile /etc/ldap/certs/ldap.example.com.cert
SSLCertificateKeyFile /etc/ldap/certs/ldap.example.com.key
SSLCACertificateFile /etc/ldap/certs/cacert.pem
LogFormat "%v %l %u %t \"%r\" %>s %b" comonvhost
CustomLog /var/log/apache2/ldap.example.com_access.log comonvhost
ErrorLog /var/log/apache2/ldap.example.com_error.log
Loglevel debug
<Location /lam >
AuthType Basic
AuthName "LDAP example.com"
AuthBasicProvider ldap
AuthLDAPBindAuthoritative on
AuthLDAPURL ldap://ldap.example.com/ou=people,dc=example,dc=com?uid?sub
AuthLDAPBindDN cn=webadmin,ou=meta,dc=example,dc=com
AuthLDAPBindPassword <thewebadminpasswordincleartext>
Require valid-user
Require ldap-group cn=admins,ou=groups,dc=example,dc=com
</Location>
Include /etc/ldap-account-manager/apache.conf
</VirtualHost>
Note
在 CentOS 主机上,该文件可以在/etc/httpd/conf.d/vhost.conf中找到,这取决于您在 CentOS 上管理虚拟主机的方式。
在<VirtualHost>标签中,我们添加了一个Location指令。Location指令说,任何匹配/lam的 URI 现在都会触发以下配置,提示用户通过 LDAP 进行身份验证:
AuthType Basic
AuthName "LDAP example.com"
AuthBasicProvider ldap
AuthLDAPBindAuthoritative on
AuthLDAPURL ldap://ldap.example.com/ou=people,dc=example,dc=com?uid?sub
AuthLDAPBindDN cn=webadmin,ou=meta,dc=example,dc=com
AuthLDAPBindPassword Zf3If7Ay
Require valid-user
Require ldap-group cn=admins,ou=groups,dc=example,dc=com
我们将AuthType设置为Basic,将AuthName设置为LDAP example.com. AuthType来定义认证方法,您可以在Basic和Digest之间进行选择。LDAP 认证要求Basic. AuthName是弹出的认证窗口中的名称。
AuthBasicProvider ldap定义我们将要使用的服务器,在本例中是 LDAP 服务器,以提供我们的认证机制。我们通过指定AuthzLDAPAuthoritative on来表明我们希望 LDAP 服务器成为接受或拒绝访问的权威服务。接下来是我们将用于认证服务的 LDAP URL,AuthLDAPURL ldap:// ldap.example.com/ou=people,dc=example,dc=com?uid?sub。它指定了我们搜索的基础,ou=people,dc=example,dc=com;我们感兴趣的属性,uid;以及我们搜索的范围,sub。在这里,您现在可以看到我们在哪里使用了cn=webadmin,ou=meta,dc=example,dc=com元帐户,该帐户将绑定到我们的 LDAP 服务器,同时还提供了密码。您不必以明文形式提供密码;如果这让你不舒服,你也可以试试其他方法:
最后,我们指定我们需要一个有效的用户,并且认证用户也必须属于 LDAP 组cn=admin,ou=groups,dc=example,dc=com。
Note
要了解关于 LDAP 和 Apache 认证的更多信息,请阅读以下内容: https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html 。
在我们继续之前,我们需要确保模块被添加到我们的 web 主机中,在 Ubuntu 上我们要做以下事情:
$ sudo a2enmod authnz_ldap
$ sudo a2enmod ldap
$ sudo a2enmod ssl
对于 CentOS,我们需要确保安装了包mod_ssl和mod_ldap;这将在conf.modules.d目录下创建文件。
$ cat /etc/httpd/conf.modules.d/01-ldap.conf
# This file configures the LDAP modules:
LoadModule ldap_module modules/mod_ldap.so
LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
我们现在需要重启我们的 Apache 服务。
$ sudo systemctl restart httpd
我们现在使用 web 浏览器连接到 LAM web GUI,地址如下: https://ldap.example.com/lam 。
在图 16-13 中,您可以看到 Apache 提供的认证挑战。我们已经输入了ataylor的uid,我们知道他是cn=admins,ou=groups,dc=example,dc=com组的成员,这是我们的 Apache 配置所要求的。
图 16-13。
The Apache request for username and password
您现在应该能够访问 LAM 了,并且应该在 Apache 日志中看到成功的登录。
...authorization result of Require ldap-group cn=admins,ou=groups,dc=example,dc=com...
...auth_ldap authenticate: accepting ataylor....
...authorization result of Require valid-user : granted...
这表明 LDAP 服务器正在使用用户名ataylor验证我们的请求,并测试这个用户是cn=admin,ou=groups,dc=example,dc=com组的成员。这种详细程度是由虚拟主机LogLevel指令中的调试日志选项提供的。
摘要
在本章中,我们讨论了什么是目录服务器以及目录信息树中的条目是如何组织的。我们向您展示了如何配置和安装 OpenLDAP 目录服务器,并使用用户帐户和管理帐户填充它。我们讨论了模式、索引和访问控制列表。我们向您展示了如何使用 OpenLDAP 提供的各种客户机工具来查询和管理 LDAP 服务器。现在,您可以设置一个 web GUI 来管理 LDAP 目录,并将 LDAP 集成到您的网络和现有应用程序中。
现在,您应该能够执行以下操作:
- 在 Ubuntu 和 CentOS 主机上安装和配置 OpenLDAP
- 了解和配置访问控制列表
- 查询和管理您的 LDAP 目录
- 安装和配置 LAM web GUI
- 为 Linux 到 LDAP 设置单点登录
- 将 Apache web 服务器配置为使用 LDAP 身份验证来验证客户端访问
正如我们已经说过的,目录服务可以在你的网络中扮演一个中心角色,而且关于这个主题还有很多东西我们在本章中还没有涉及到。我们建议您购买一本关于该主题的书籍,阅读位于 www.openldap.org 的在线文档,并使用邮件列表来帮助您进一步了解该领域的知识。
在下一章,你将会读到性能监控和优化。