用 PAM 策略把 OpenVPN 登录限制到一个组
专用 PAM 服务只允许 VPN 组成员登录,而不改动系统级登录规则。让 auth-pam 插件指向它,并测试各种结果。
让每个账户都能正常登录,但只允许一个组使用 VPN。PAM 无需第二个密码库就能表达这一差别,而 OpenVPN 插件可以指向一个只包含你预期检查项的服务。诀窍在于构建该服务时不要意外继承系统级堆栈。
这里的演练从插件的服务名开始,构建一套策略,然后测试三种关键结果:成员、持有有效密码的外部人员,以及错误的凭据。
为什么需要独立的 PAM 服务
auth-pam 插件以服务名作为参数。该名称选择 /etc/pam.d 下的一个文件,而文件中的堆栈正是 VPN 登录时执行的检查。若传入系统级登录所用的同名服务,就会继承该堆栈中的每一条决定,包括后来因无关原因添加的决定。
改为创建专用服务。先从一个已经能对目标后端完成认证的可用堆栈出发,例如本地密码文件、SSSD 或目录服务,然后加入资格规则。把两行复制进空文件是常见错误:密码过期、账户锁定等检查可能位于你未复制的堆栈中。
拆分服务也能限制影响范围。共享堆栈中的仅限 VPN 规则可能拒绝所有人的 SSH 或控制台访问,把一次访问变更变成故障。
编写组规则
资格检查属于 account 阶段,在凭据验证之后。像 account required pam_succeed_if.so user ingroup vpnusers 这样的规则,只有通过认证的用户是该组成员时才允许继续处理。
这一行只是片段,不是完整服务。把它整合到每条被接受路径都会到达的位置,并检查它周围的 control 标志。堆栈中更早的 sufficient 或跳转控制可能在自己的规则执行前就结束处理,使限制变成一句建议。
不要出于习惯添加 use_uid。没有它时,模块评估正在被认证的用户;有它时,模块评估 OpenVPN 进程运行所在的账户,而那通常是 root 或服务账户,因此并不是登录的人。
组成员关系来自系统的身份解析,而不是你认识的名字。对目录用户,要确认同一个后端能为该用户回答组查询,因为同名本地组与目录组是两个不同的对象。
顺序与身份陷阱
把组规则放在认证堆栈之后,也放在仍然必须生效的检查之后,但要在你保留的 account 检查之前或与其并列。PAM 按文件顺序评估模块,每一行的 control 值决定失败是否最终。
当心重复的堆栈。常见模式是一个已经包含 account 规则的 include,后面又跟着你自己的组规则,结果在你以为只有一条决定的地方出现两条。要读被包含的文件,而不只是你改过的那个。快速查看实际顺序的方法是自上而下读文件并记下每个 control 值。
保持其他准入要求不变。密码正确并不能替代证书检查、第二因素或连接后授权,本策略也不替代其中任何一项。它只决定该账户是否有资格被考虑用于 VPN 访问。
测试成员、外部人员和错误密码
在认为策略完成前测试三种情况。成员能够成功连接。组外的有效账户能通过密码认证,但被拒绝 VPN 访问。任一账户的错误密码在认证阶段就被拒绝,而不是由组规则拒绝。
把客户端可见的错误和服务器日志放在一起看,因为对连接者而言两种失败可能看起来一样。这一区分对支持很重要:密码正确的外部人员需要变更组,而错误密码需要重置凭据。
编辑 PAM 时保持第二个会话打开,并在变更后同样验证普通 SSH 登录。专用服务的意义在于系统登录应不受影响,而唯一确认办法就是实测。
如果出问题,从副本恢复先前的服务文件。你想要的规则很容易表述:成员资格使账户有资格获得 VPN 访问,而其他所有必需检查仍然生效。