1. 为什么还要在 CentOS 7 上折腾 LDAP
先把场景说清楚。你手里有一批 CentOS 7 的机器,可能是几台,也可能是几十台,每台上面都跑着不同的服务,每个服务都有自己的账号体系。今天加一个人,你得在 A 机器上useradd,在 B 机器的 Web 后台里建账号,在 C 机器的数据库里再插一条用户记录。人一多,离职一频繁,这套流程迟早出乱子——要么漏删,要么权限对不上。
LDAP 要解决的就是这个问题:把"用户是谁、属于哪个组、能访问什么"这件事从各台机器里抽出来,集中放到一个目录服务里。所有机器都去问同一个地方"这个人的账号密码对不对、他在不在某个组里",本地不再各自维护一份。这就是所谓的集中式身份认证。
CentOS 7 虽然已经进入维护末期,但大量存量生产环境还在跑它,尤其是内网、实验室、传统企业的机房。这些环境往往不方便整体升级,但又确实需要一套统一的账号体系。所以"CentOS 7 下部署 LDAP"这个需求,到今天依然真实存在。
这篇文章面向的是有一定 Linux 基础、但没怎么碰过目录服务的运维或开发人员。我会从零把一套可用的 LDAP 服务搭起来,包括服务端安装、目录结构设计、用户和组的创建、客户端接入、TLS 加密,以及后面最容易出问题的排错环节。全程基于 OpenLDAP 这套开源实现,命令和配置都可以直接抄。
提示:本文所有主机名、域名、IP 均为示例,实际部署时请替换成你自己的规划。生产环境务必先在小范围验证,再批量推开。
2. 部署前的规划:目录树怎么设计决定了后面顺不顺
很多人装 LDAP 的第一步就是yum install,装完发现不知道 base DN 该写什么,用户该往哪个分支放,于是随手建了个dc=test,dc=com,结果后面接应用时各种别扭。目录树的设计是整套体系的地基,值得先花二十分钟想清楚。
2.1 Base DN 与命名后缀的确定
Base DN 是整个目录的根,所有条目都挂在它下面。最常见的做法是用你的域名倒过来写,比如域名是example.com,那 Base DN 就是dc=example,dc=com。如果你的环境根本没有正式域名,也别用dc=test,dc=com这种一看就是临时的名字,建议用一个内部约定的名字,比如dc=corp,dc=internal,至少语义上说得通。
这里有个容易踩的坑:Base DN 一旦定下来,后面改起来非常麻烦,因为所有条目的 DN 都依赖它。所以规划阶段就要定死,别中途改主意。
2.2 目录树的分支结构
一个清晰的分支结构通常长这样:
dc=example,dc=com ├── ou=people # 存放所有用户账号 ├── ou=groups # 存放所有用户组 └── ou=service # 存放服务账号(可选)把用户和组分开放在不同的 OU(组织单元)下,好处是后面做访问控制、写查询过滤条件时逻辑清晰。比如你要查所有用户,直接搜ou=people就行,不用在全目录里捞。
如果组织规模较大,还可以在people下面再按部门细分,比如ou=dev,ou=people、ou=ops,ou=people。但我不建议一上来就分太细,因为 LDAP 的查询是树形遍历,层级越深,维护成本越高。中小规模环境,用户和组各一个 OU 完全够用。
2.3 主机与端口规划
LDAP 默认监听 389 端口(明文或 StartTLS),636 端口是 LDAPS(SSL/TLS 加密)。生产环境强烈建议启用加密,因为明文传输的密码在内网里也是可以被抓包的。
规划时还要考虑:LDAP 服务端是单点还是做主从复制。如果只是小规模内部使用,单点加定期备份可以接受;如果客户端数量多、对可用性有要求,就要考虑主从或多主复制。本文先聚焦单机部署,把基础打牢,复制的话题后面单独展开。
| 规划项 | 建议值 | 说明 |
|---|---|---|
| Base DN | dc=example,dc=com | 用真实或内部约定域名 |
| 用户 OU | ou=people | 所有用户账号 |
| 组 OU | ou=groups | 所有用户组 |
| 管理员 DN | cn=admin,dc=example,dc=com | 目录管理员 |
| 监听端口 | 389 / 636 | 636 用于加密 |
| 数据后端 | mdb | 现代 OpenLDAP 默认后端 |
3. 服务端安装与初始化配置
规划清楚了,动手就有方向。这一节把服务端从裸机装到能跑起来,每一步我都会说明为什么这么做。
3.1 安装 OpenLDAP 相关软件包
CentOS 7 的官方源里就有 OpenLDAP,直接装即可:
yum install -y openldap openldap-servers openldap-clients装完之后,几个关键路径要记住:
/etc/openldap/slapd.d/:这是配置目录,现代 OpenLDAP 用cn=config的方式管理配置,所有配置都是 LDIF 格式的文件,不再直接改slapd.conf。/var/lib/ldap/:数据存储目录,用户数据都在这里。/etc/openldap/schema/:schema 文件目录,定义目录里能存哪些属性。
这里要特别提醒:CentOS 7 的 OpenLDAP 默认使用slapd.d动态配置,网上很多老教程还在教你改/etc/openldap/slapd.conf,那个文件在新版本里根本不存在,照着做只会浪费时间。判断方法很简单,看/etc/openldap/下有没有slapd.d目录,有就以它为准。
3.2 启动服务并设置开机自启
systemctl start slapd systemctl enable slapd systemctl status slapd启动后确认 389 端口在监听:
ss -tlnp | grep 389如果服务起不来,先看日志:
journalctl -u slapd -n 50常见原因是数据目录权限不对。/var/lib/ldap/的属主必须是ldap:ldap,如果之前手动动过这个目录,权限乱了就会启动失败。修复:
chown -R ldap:ldap /var/lib/ldap chmod 700 /var/lib/ldap3.3 导入基础 schema
OpenLDAP 默认只加载了核心 schema,要支持用户和组,还需要导入cosine、nis、inetorgperson这几个:
ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/cosine.ldif ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/nis.ldif ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldif注意这里的-Y EXTERNAL和ldapi:///,这是通过本地 Unix socket 用外部认证方式连接,不需要密码,只有 root 能用。这是初始化阶段的标准做法。
3.4 设置管理员密码并创建 Base DN
先给cn=admin设置密码。先生成一个加密后的密码串:
slappasswd -s YourStrongPassword它会输出一段{SSHA}...开头的字符串,复制下来。然后写一个 LDIF 文件chrootpw.ldif:
dn: olcDatabase={0}config,cn=config changetype: modify add: olcRootPW olcRootPW: {SSHA}你生成的密码串执行:
ldapadd -Y EXTERNAL -H ldapi:/// -f chrootpw.ldif接着创建 Base DN 和管理员条目,写basedomain.ldif:
dn: dc=example,dc=com objectClass: top objectClass: dcObject objectClass: organization o: Example Organization dc: example dn: cn=admin,dc=example,dc=com objectClass: simpleSecurityObject objectClass: organizationalRole cn: admin description: LDAP administrator userPassword: {SSHA}你生成的密码串导入:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f basedomain.ldif-x表示用简单认证,-D指定绑定 DN,-W提示输入密码。这一步成功后,你的目录根就建好了。
注意:
olcRootPW那一步改的是cn=config这个管理后端的密码,和cn=admin的业务管理员密码是两回事。前者用于管理配置,后者用于管理业务数据。两个都设上,别混。
4. 用户与组的批量创建:手工建和脚本建怎么选
目录根建好了,接下来往里放人。少量用户手工建没问题,几十上百个就得靠脚本。这一节两种方式都讲,并说明各自的适用场景。
4.1 手工创建 OU、用户和组
先建两个 OU,写ou.ldif:
dn: ou=people,dc=example,dc=com objectClass: organizationalUnit ou: people dn: ou=groups,dc=example,dc=com objectClass: organizationalUnit ou: groups导入:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f ou.ldif然后建一个用户,写user1.ldif:
dn: uid=zhangsan,ou=people,dc=example,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: zhangsan cn: Zhang San sn: Zhang givenName: San displayName: Zhang San uidNumber: 10001 gidNumber: 10001 homeDirectory: /home/zhangsan loginShell: /bin/bash mail: zhangsan@example.com userPassword: {SSHA}用户密码串这里posixAccount这个 objectClass 很关键,它提供了uidNumber、gidNumber、homeDirectory、loginShell这些 Linux 系统登录必需的属性。没有它,客户端就没法把这个 LDAP 用户映射成本地用户。
建组,写group1.ldif:
dn: cn=dev,ou=groups,dc=example,dc=com objectClass: posixGroup cn: dev gidNumber: 10001 memberUid: zhangsanposixGroup对应 Linux 的组,memberUid里放的是用户的uid,不是完整 DN,这点和groupOfNames那种用 DN 做成员的组不一样,别搞混。
4.2 用脚本批量导入用户
手工建三个五个还行,建五十个就是折磨。更实际的做法是准备一个 CSV,然后用脚本生成 LDIF 再批量导入。
假设有个users.csv:
uid,cn,sn,uidNumber,gidNumber,mail zhangsan,Zhang San,Zhang,10001,10001,zhangsan@example.com lisi,Li Si,Li,10002,10001,lisi@example.com wangwu,Wang Wu,Wang,10003,10001,wangwu@example.com写个 shell 脚本生成 LDIF:
#!/bin/bash while IFS=, read -r uid cn sn uidnum gidnum mail; do [ "$uid" = "uid" ] && continue cat <<EOF dn: uid=$uid,ou=people,dc=example,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: $uid cn: $cn sn: $sn uidNumber: $uidnum gidNumber: $gidnum homeDirectory: /home/$uid loginShell: /bin/bash mail: $mail userPassword: {SSHA}统一初始密码串 EOF echo "" done < users.csv > users.ldif生成后一次性导入:
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f users.ldif这里有个实操心得:批量导入时,uidNumber一定要规划好区间,别和本地系统用户(通常 1000 以下)冲突。建议从 10000 开始,按部门或用途分段,比如 10000-19999 给普通员工,20000-29999 给服务账号。这样后面排查问题时,看一眼 uid 就知道是什么类型的账号。
4.3 验证导入结果
导入完别急着往下走,先查一下确认数据进去了:
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "ou=people,dc=example,dc=com" "(objectClass=posixAccount)" uid cn uidNumber如果能看到你导入的用户列表,说明数据没问题。查不到就检查 DN 拼写、objectClass 是否齐全、导入时有没有报错。
| 操作 | 命令要点 | 常见错误 |
|---|---|---|
| 建 OU | objectClass: organizationalUnit | 忘了 ou 属性 |
| 建用户 | 必须含 posixAccount | 缺 uidNumber 导致无法登录 |
| 建组 | posixGroup + memberUid | 误用完整 DN 做成员 |
| 批量导入 | 脚本生成 LDIF | uidNumber 与系统冲突 |
5. 客户端接入:让 Linux 机器认这套账号
服务端数据齐了,真正的价值体现在客户端能不能用这些账号登录。这一节把一台 CentOS 7 客户端接进来,实现用 LDAP 账号 SSH 登录。
5.1 安装客户端组件
在客户端机器上:
yum install -y nss-pam-ldapd pam_ldap openldap-clients这里要解释一下这几个包的分工。nss-pam-ldapd提供nslcd服务,负责把系统的用户查询(NSS)转发给 LDAP;pam_ldap负责认证环节(PAM)。两者配合,才能实现"查得到用户 + 验证得了密码"。
5.2 配置 nslcd
编辑/etc/nslcd.conf,核心几行:
uri ldap://ldap.example.com/ base dc=example,dc=com binddn cn=admin,dc=example,dc=com bindpw 管理员密码uri指向服务端,base是搜索起点,binddn和bindpw是客户端用来查询目录的账号。生产环境不建议直接用cn=admin,应该建一个只有读权限的专用账号,最小权限原则。
然后改/etc/nsswitch.conf,让系统在查用户和组时也去问 LDAP:
passwd: files sss ldap shadow: files sss ldap group: files sss ldapfiles在前表示先查本地/etc/passwd,查不到再走 LDAP。这个顺序很重要,保证本地系统账号(比如 root)不受影响。
启动服务:
systemctl start nslcd systemctl enable nslcd5.3 验证客户端能否识别 LDAP 用户
getent passwd zhangsan id zhangsan如果getent能返回用户信息,id能列出他的组,说明 NSS 这一层通了。这时候尝试 SSH 登录:
ssh zhangsan@客户端IP能登进去,整套链路就打通了。登不进去,问题通常出在 PAM 配置或密码验证环节,下一节专门讲排查。
5.4 自动创建家目录的坑
LDAP 用户第一次登录时,本地是没有家目录的。默认情况下 SSH 登录会因为找不到家目录而报错或直接踢出。解决办法是启用pam_mkhomedir:
authconfig --enablemkhomedir --update或者手动在/etc/pam.d/system-auth和/etc/pam.d/password-auth里加:
session required pam_mkhomedir.so skel=/etc/skel umask=0022这个坑我见过太多次,很多人卡在"密码明明对,就是登不进去",最后发现是家目录没建。记住这一条,能省你半小时。
6. 加密与安全加固:别让密码裸奔
前面跑通的是明文 LDAP,389 端口上密码是明文的。内网也不是绝对安全,抓包工具一开,密码一览无余。这一节把 TLS 加上。
6.1 生成自签证书
内部环境用自签证书就够了。先生成 CA 和服务器证书:
openssl req -x509 -newkey rsa:2048 -keyout ca.key -out ca.crt -days 3650 -nodes -subj "/CN=Example-CA" openssl req -newkey rsa:2048 -keyout server.key -out server.csr -nodes -subj "/CN=ldap.example.com" openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650把证书放到 OpenLDAP 能读到的位置,注意权限:
mkdir -p /etc/openldap/certs cp server.crt server.key ca.crt /etc/openldap/certs/ chown -R ldap:ldap /etc/openldap/certs chmod 600 /etc/openldap/certs/server.key6.2 配置 slapd 启用 TLS
写一个tls.ldif:
dn: cn=config changetype: modify add: olcTLSCACertificateFile olcTLSCACertificateFile: /etc/openldap/certs/ca.crt - add: olcTLSCertificateFile olcTLSCertificateFile: /etc/openldap/certs/server.crt - add: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/openldap/certs/server.key导入:
ldapmodify -Y EXTERNAL -H ldapi:/// -f tls.ldif重启服务,确认 636 端口在监听:
systemctl restart slapd ss -tlnp | grep 6366.3 客户端改用加密连接
客户端把nslcd.conf里的 uri 改成:
uri ldaps://ldap.example.com/ ssl on tls_reqcert allowtls_reqcert allow表示接受自签证书。如果是正式 CA 签发的证书,可以设成demand更严格。改完重启nslcd,再验证一次登录。
注意:自签证书的
CN必须和客户端连接时用的主机名一致,否则证书校验会失败。要么把主机名写进客户端 hosts,要么在证书里加 SAN 扩展。这个细节不注意,加密配好了反而登不进去。
7. 排错实录:那些让人抓狂的典型故障
LDAP 的报错信息往往很含糊,Invalid credentials背后可能是十种原因。这一节把最常见的几类故障和排查思路整理出来,都是实际踩过的。
7.1 客户端查不到用户
现象:getent passwd zhangsan返回空,但服务端ldapsearch能查到。
排查顺序:
- 先看
nslcd服务状态,systemctl status nslcd,没起来先起来。 - 看
/etc/nsswitch.conf里passwd那行有没有ldap,漏了就加上。 - 看
nslcd.conf里的base和binddn对不对,用ldapsearch手动模拟一次查询。 - 看防火墙,客户端到服务端 389/636 通不通,
telnet ldap.example.com 389试一下。
这四步走完,九成问题能定位。
7.2 密码正确却登录失败
现象:ldapwhoami验证密码通过,但 SSH 登录被拒。
这种多半是 PAM 环节的问题。检查/etc/pam.d/sshd和/etc/pam.d/system-auth里有没有正确引用pam_ldap。另外确认pam_mkhomedir有没有配,家目录建不出来也会导致登录失败。
还有一个隐蔽的坑:shadowAccount的shadowLastChange属性。如果这个值设置不当,PAM 可能认为密码已过期。检查方法:
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "uid=zhangsan,ou=people,dc=example,dc=com" shadowLastChange如果返回 0 或者一个很旧的值,把它更新成当前天数(从 1970-01-01 算起)。
7.3 服务端启动失败
现象:systemctl start slapd失败,日志报unable to open pid file或database environment open failed。
八成是数据目录权限或残留锁文件的问题。处理:
systemctl stop slapd rm -f /var/lib/ldap/__db.* /var/lib/ldap/alock chown -R ldap:ldap /var/lib/ldap systemctl start slapd__db.*是 Berkeley DB 的残留文件,异常关闭后可能损坏,删掉让 slapd 重建。注意这只在确认数据已备份或可重建时操作,别在数据重要时乱删。
7.4 常见错误对照表
| 报错信息 | 可能原因 | 处理方向 |
|---|---|---|
| Invalid credentials | 密码错 / binddn 错 | 用 ldapwhoami 验证 |
| No such object | DN 拼写错 | 逐级 ldapsearch 确认 |
| Can't contact LDAP server | 网络 / 服务未起 | 查端口和防火墙 |
| Insufficient access | 权限不足 | 检查 binddn 权限 |
| Object class violation | 属性与 objectClass 不匹配 | 核对 schema 要求 |
8. 数据备份与日常维护的几个习惯
LDAP 一旦成为认证中心,它挂了所有人都登不上,备份和巡检就是保命的事。
8.1 用 slapcat 做逻辑备份
最可靠的备份方式是slapcat,它把整个目录导出成 LDIF:
slapcat -v -l /backup/ldap-$(date +%Y%m%d).ldif这个命令要在服务停止或只读状态下跑最稳妥,热备时可能有一致性问题。建议写进 crontab,每天凌晨跑一次,保留最近 30 天。
恢复时用slapadd:
systemctl stop slapd slapadd -v -l /backup/ldap-20240101.ldif chown -R ldap:ldap /var/lib/ldap systemctl start slapd8.2 定期巡检要看什么
我一般会盯这几个指标:
- 目录条目总数有没有异常增长或减少,
ldapsearch加-s base看namingContexts。 - 服务进程和端口状态,
systemctl status slapd和ss -tlnp。 - 日志里有没有反复出现的认证失败,可能是有人在暴力尝试。
- 证书到期时间,自签证书也要记着续,别等过期了才发现。
8.3 一个容易被忽略的维护点
cn=config这个配置后端本身也是数据,改了配置之后最好也备份一份:
slapcat -n 0 -l /backup/ldap-config-$(date +%Y%m%d).ldif-n 0表示导出第 0 个数据库,也就是配置库。哪天配置改崩了,这份备份能救急。这个习惯很多人没有,等出事才后悔。
9. 关于这套方案的一些个人体会
搭 LDAP 这件事,技术本身不算特别复杂,难的是把它用对、用稳。我自己在几个环境里折腾下来,最大的感受是:规划阶段的每一分钟,都能在后期省下十分钟的排错时间。Base DN、OU 结构、uid 区间这些定好了,后面接应用、加用户都是顺水推舟;定得随意,后面处处别扭。
另一个体会是,别把 LDAP 当成万能钥匙。它擅长的是"集中存账号、统一认证",但细粒度的权限控制、复杂的授权逻辑,它并不擅长。真要做精细的访问控制,通常还得配合应用层自己的权限系统。把 LDAP 用在它该用的地方,它就很稳。
最后说个实操小技巧:调试阶段多用ldapsearch和ldapwhoami手动验证,别一上来就改客户端配置。服务端数据对不对、密码能不能过,这两条命令一测便知。把服务端确认无误了,再去调客户端,问题范围能缩小一大半。这套思路我在排查任何认证类问题时都用,屡试不爽。