PAM 认证手动可用,作为 OpenVPN 服务却失败
从 root shell 可用、在 systemd 下却失败的 PAM 登录,指向的是 helper,而不是密码。把权限、文件访问和 SELinux 分别来看。
用 PAM 做认证时,手动运行客户端可以通过,一旦 systemd 启动同一个配置就会以 AUTH_FAILED 失败。这两次运行之间密码没有变化;变化的是围绕向 PAM 发问的那个进程的一整套约束。PAM 通过一个 helper 作答,而该 helper 改变自身凭据的能力,恰恰是加固过的 unit 会去掉的东西。先找到这个 helper 以及它需要的边界,再去动能力列表。
unix_chkpwd 是回答 PAM 提问的那个二进制
在本地密码认证中,pam_unix 很少自己去读哈希文件。它会执行 unix_chkpwd,发行版把这个二进制装成 setuid root 或 setgid shadow,好让一个本无权读 /etc/shadow 的进程能代查一个密码。去掉取得该身份的能力,helper 就无法完成校验,模块于是返回一个普通的认证失败。
于是在客户端能看到的所有日志里,这个故障都像是凭据问题。陷阱就在这里:客户端报告的是 PAM 告诉它的结果,而 PAM 只报告认证栈说了不行。以 root 手动运行 helper 同样证明不了什么,因为它走的是服务根本不会用到的权限路径。先读 systemd 实际加载的 unit,再读系统日志中对应那次尝试的记录。这两个视角能把沙箱问题和密码错误问题区分开。
先读 unit,再读密码日志
打包的 unit 可能带着你并没有写过的 drop-in,而生效的配置是合并后的结果。按 systemd 眼中的样子打印这个 unit,连同决定 helper 能否自我提权的沙箱设置一起看。这一阶段只读检查就够了。
systemctl cat [email protected]
systemctl show [email protected] -p User -p Group -p CapabilityBoundingSet -p AmbientCapabilities -p NoNewPrivileges
journalctl -u [email protected] -b第一条命令显示 unit 及其 drop-in 来自哪里,这很重要,因为几个月前加上的覆盖项活得比它为什么存在的那段记忆更久。第二条打印接下来两节所依赖的沙箱取值。
把你看到的与同一发行版、同一版本的正常主机对比。User、CapabilityBoundingSet 或 NoNewPrivileges 上的差异是具体线索;说明性注释上的差异不是。改动之前先把这些值记下来,因为事后再拿一个会变的目标做对比,什么也证明不了。如果服务以无特权账户运行、且边界集合为空,helper 几乎没有可用的余地。
凭据变换、文件访问和策略是三道边界
先看 exec 期间发生了什么。setuid 二进制或带 file capabilities 的文件会触发凭据变换,因此得到的身份并不是调用者的延续。边界集合封顶了这类二进制能取得的东西,而 NoNewPrivileges 直接阻断 setuid、setgid 和 file capabilities 的获取。这三者中的任何一条都能让 helper 停摆,同时服务的其他功能毫发无损。
接着把宽泛的自主访问绕过与窄口的读访问分开。CAP_DAC_OVERRIDE 几乎在任何地方都跳过自主访问检查,而 CAP_DAC_READ_SEARCH 覆盖读取与目录遍历,并带有自己额外的权能。它们是作用范围不同的两种工具,哪一个都不等于让 PAM 读 shadow 文件这种写法。
最后要问,强制策略到底允不允许这个动作。Linux 能力并不能覆盖 SELinux,持有某项能力也证明不了类型强制规则如何。把文件访问被拒和能力被拒当作不同的证据,因为它们指向不同的修法,常常也指向不同的负责人。要读记录的每一个字段,而不是只看 denied 这一个词。哪个可执行文件执行了动作、它在哪个域中运行、碰到什么类型的对象、涉及哪个对象类别、策略拒绝了哪项许可——每一条都以不同方式缩小答案。
从审计日志里把拒绝捞出来
在 enforcing 模式的主机上,审计线索是唯一记录拒绝的地方。查询近期事件,并按照失败登录的时间戳去读,而不是按照你开始排查的时间。
ausearch -m AVC,USER_AVC -ts recent空结果不是无罪的证据。它要么说明审计没有在采集,要么说明你的时间窗错过了那次尝试,在排除策略之前,这两种可能都得排除。
Red Hat 的 SELinux 排障指南 把标签与配置检查放在生成放行规则之前,这个顺序对这个问题同样正确。标签标错的 helper 二进制与策略缺口会产生相似的拒绝,但需要不同的修复。为标签错误生成放行规则会掩盖缺陷,让下一次软件包更新再把它带回来。写任何策略之前,先确认已安装 helper 上的标签。
用一次成功和一次被拒的登录来证明改动
只改你已验证过的那道边界,然后测试两种结果。该通过认证的账户必须成功,密码错误的账户必须仍然失败。如果一项改动让所有尝试都成功,那是删掉了认证,而不是修好了它。
做一次真实连接尝试,随后立刻读 journal。能证明修好的那一行,是这次尝试对应的 pam_unix(openvpn:auth) 条目:它应当显示认证成功,并且不应该有任何 AVC 记录落在同一秒。
journalctl -u [email protected] -n 30 --no-pager
ss -tnp | grep openvpn能用的隧道还需要重新连接,因为沙箱改动破坏第二次连接的概率比第一次更高。重连之后检查已建立的 socket,并确认仍然是同一个 unit 在提供服务。