OpenLDAP 统一账号实战:让一批服务器共用一套账号登录

服务器从几台涨到几十台后,账号管理会先于业务出问题:新人入职要在每台机器建账号、配密钥,离职时漏删一台就是一个长期后门,出了操作事故连「这个 uid 是谁」都要靠记忆对。统一账号是标准解法——账号、组、sudo 权限集中在一套目录服务里,主机只做查询和缓存。本文用 OpenLDAP 做服务端、SSSD 做客户端,把从目录建模、条目导入到主机接入、离线登录的整条链路走通。示例服务端与客户端均为 Rocky Linux 9,目录域名用 example.com 占位,部署前请替换成自己的内网域名。

目录模型速览

LDAP 是一棵树,理解四个概念就够用。DN(Distinguished Name)是条目的全路径,类似文件绝对路径,如 uid=zhangsan,ou=people,dc=example,dc=com,从叶到根逐级写;OU(Organizational Unit)是容器,惯例上用 ou=people 放人、ou=groups 放组;每个条目由 objectClass 决定它能有哪些属性,Linux 登录需要的类是 posixAccount,它的关键属性包括 uid(登录名)、uidNumber(数字 UID,全目录唯一且永不复用)、gidNumber(主组 GID)、homeDirectory、loginShell,密码哈希则放在同属 shadowAccount 体系的 userPassword 里。组用 posixGroup,成员写在 memberUid 里(存登录名而非 DN)。先在纸上把命名、UID 号段(如从 10000 起分配)和组划分定下来再动手,目录一旦投产,改结构比改配置麻烦得多。

前置条件

  • 一台目录服务器(示例主机名 ldap.example.com),客户端能解析并访问其 389/636 端口;
  • 一套 CA 与服务端证书:生产环境请用内部 CA 签发,禁止长期使用明文 389 做密码校验;
  • 各主机时间同步(TLS 校验与日志对齐都依赖它);
  • 规划好基础 DN、管理员 DN(cn=admin,dc=example,dc=com)与 UID/GID 号段,并准备好管理员口令的安全保管方式。

第一步:部署服务端

两种口径二选一:图省事、用容器(如 osixia/openldap 镜像)适合快速落地与迁移,配置全走环境变量与 LDIF 挂载;讲究与系统集成、要长期自维护的,用发行版的 slapd 包安装,本文后续操作两者通用。容器方式的核心参数是指定组织、域名与管理员口令,并把数据目录与配置目录挂出来持久化:

docker run -d --name openldap --restart always \
  -p 389:389 -p 636:636 \
  -e LDAP_ORGANISATION="Example Ops" \
  -e LDAP_DOMAIN="example.com" \
  -e LDAP_ADMIN_PASSWORD="替换为强口令" \
  -e LDAP_TLS_VERIFY_CLIENT="never" \
  -v /data/ldap/db:/var/lib/ldap \
  -v /data/ldap/config:/etc/ldap/slapd.d \
  osixia/openldap:1.5.0

包安装方式则是 dnf install -y openldap-servers openldap-clients 后,用 slapd.conf 或 cn=config 在线配置初始化基础 DN。无论哪种方式,初始化后先验证管理员绑定可用,后面所有导入都靠它:

ldapwhoami -x -H ldap://ldap.example.com \
  -D "cn=admin,dc=example,dc=com" -W

第二步:导入组织与用户条目

条目用 LDIF 文件批量导入,文件纳入 Git 管理,目录变更就有了审计记录。先建 OU 与组,再建用户;userPassword 先写占位,导入后统一用 ldappasswd 设密,不要把明文或可逆的哈希散落在脚本里。

# base.ldif:组织单元与组
dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups

dn: cn=ops,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: ops
gidNumber: 10000
memberUid: zhangsan

# user.ldif:一个用户条目
dn: uid=zhangsan,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Zhang San
sn: Zhang
uid: zhangsan
uidNumber: 10001
gidNumber: 10000
homeDirectory: /home/zhangsan
loginShell: /bin/bash
userPassword: {SSHA}由 ldappasswd 生成后回填或直接用命令设密
ldapadd -x -H ldap://ldap.example.com \
  -D "cn=admin,dc=example,dc=com" -W -f base.ldif
ldapadd -x -H ldap://ldap.example.com \
  -D "cn=admin,dc=example,dc=com" -W -f user.ldif
# 给用户设密码(交互输入,不要写进命令行历史)
ldappasswd -x -H ldap://ldap.example.com \
  -D "cn=admin,dc=example,dc=com" -W \
  -S "uid=zhangsan,ou=people,dc=example,dc=com"

两个纪律:uidNumber 一经分配永不回收给别人(文件属主只认数字,换人复用等于继承前任所有文件);离职处理是禁用或删除条目并同步回收,而不是改个密码留着。

第三步:客户端接入 SSSD

客户端不要用老式的 pam_ldap/nss_ldap 直连,用 SSSD:它统一做身份查询、认证与缓存,目录短暂不可达时凭缓存还能登录(有取舍,下文说)。装包后写 /etc/sssd/sssd.conf(权限必须 600,否则服务拒绝启动),核心是 id_provider 与 auth_provider 都指向 ldap、给出 URI 与搜索基准 DN、强制 StartTLS,并开启凭据缓存:

dnf install -y sssd sssd-ldap oddjob-mkhomedir
chmod 600 /etc/sssd/sssd.conf
[sssd]
services = nss, pam, sudo
domains = example.com

[domain/example.com]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://ldap.example.com
ldap_search_base = dc=example,dc=com
ldap_id_use_start_tls = false
ldap_tls_reqcert = demand
ldap_tls_cacert = /etc/pki/tls/certs/internal-ca.pem
cache_credentials = true
enumerate = false

几处口径:用了 ldaps(636)就把 ldap_id_use_start_tls 关掉,两者是二选一的加密方式,叠加会报错;enumerate 保持 false,靠精确查询工作,几十台机器没必要全量拉目录;cache_credentials = true 是离线登录的前提,但它意味着目录里禁用账号后、缓存过期前仍可能登录,高安全场景要把缓存有效期(offline_credentials_expiration)压短并接受「目录不可达时新账号登不上」的代价。随后把 NSS 与 PAM 接上,并让首次登录自动建家目录:

# /etc/nsswitch.conf 关键行改为:
# passwd:     files sss
# group:      files sss
authselect select sssd with-mkhomedir --force   # RHEL 系口径
systemctl enable --now sssd oddjobd

第四步:进阶项(按需)

SSH 公钥也可以进目录:给用户条目加 ldapPublicKey 对象类、公钥存进 sshPublicKey 属性,客户端 sshd 配 AuthorizedKeysCommand 调用 SSSD 提供的 sss_ssh_authorizedkeys 工具取钥,这样换密钥只改目录一处,sshd_config 里把 AuthorizedKeysFile 置空避免本地文件与目录双通道并存。sudo 规则同理可进目录:目录侧启用 sudo 模式(sudoers 条目以 sudoRole 形式存储在 ou=SUDOers 下),客户端在 sssd 的 services 里加 sudo、nsswitch 的 sudoers 行写 files sss,即可把「哪个组能在哪批主机上 sudo」收敛到目录统一管理;存量环境迁移时请分批灰度,先观察一周再关本地 sudoers。

验证

# 服务端:能查到条目
ldapsearch -x -H ldap://ldap.example.com -b "dc=example,dc=com" "(uid=zhangsan)" dn uidNumber
# 客户端:身份解析与登录各验一次
getent passwd zhangsan
id zhangsan
sssctl user-show zhangsan
ssh zhangsan@node-01   # 用目录密码登录,并确认家目录自动创建

再补一个断网演练:临时在客户端屏蔽目录服务器地址,已登录过的账号应能凭缓存登录,新账号应登录失败——两个结果都符合预期才算缓存策略配对了。

常见问题

  • ldap_bind: Invalid credentials (49):多半是绑定 DN 写错(管理员是 cn=admin 不是 uid=admin)、口令不对或条目 DN 拼错。先用 ldapwhoami 以管理员绑定验证,再逐级缩小到用户 DN;不要一上来怀疑密码策略。
  • TLS 证书校验失败(Can't contact LDAP server / certificate verify error):检查证书的 SAN 是否包含客户端填写的主机名、CA 文件路径是否正确、是否误把自签证书当正式证书用。ldap_tls_reqcert = demand 下校验是硬性的,临时改成 never 只能用于定位问题,不能作为长期配置。
  • getent passwd 查不到用户,但 ldapsearch 能查到:看 SSSD 域日志(/var/log/sssd/)多半是搜索基准 DN 写错、过滤属性缺失,或 NSS 没接 sss。注意 getent passwd(不带用户名)默认不枚举是正常现象,要带具体用户名查。
  • 目录一挂谁都登不上,或禁用账号还能登:前者是 cache_credentials 没开或 oddjob/sssd 顺序问题;后者是缓存有效期太长。按安全要求在两者之间取口径,并写进运维手册,别等事故时才讨论。

小结

统一账号的难点不在装软件,而在规矩先行:DN 结构、UID 号段、组的划分、离职回收流程先定下来,OpenLDAP 与 SSSD 只是把规矩执行下去的工具。落地顺序建议是「目录建模 → 条目导入留痕 → 一台试点主机接 SSSD → 灰度到全部主机 → 再考虑公钥与 sudo 进目录」,每一步都能回滚,账号体系才托得住生产。

发表回复

后才能评论