资讯动态

PAM认证模块实战:从faillock到pam_exec的配置与排障

发布时间:2026/10/23 1:28:00 来源:尧图企业网站定制
那是我第三次为同一个问题半夜爬起来线上十几台主机所有普通用户都登录不了root 却可以服务全部正常。凭经验先查了 /etc/pam.d/sshd发现有人在 sshd 这个服务文件里把 pam_deny.so 放到了 pam_unix.so 前面。就这么一个不起眼的顺序问题把整个认证链路卡死了。这种场景只要你管过 Linux 服务器早晚会遇到。PAMPluggable Authentication Modules是 Linux 认证体系的骨架所有登录、su、sudo、切换用户请求最终都要过一遍 PAM 堆栈。它由模块、配置文件、控制标志三部分组成理解它不难难的是在生产环境里把模块组合对、顺序排对、坑避开。这是系列第九篇基础部分前几篇已经聊过这篇我不打算重复官方文档只讲运维实战里最容易被忽视的模块协同和故障现场。内容适合两类人一类是已经在用 PAM 但没深入过细节的系统管理员另一类是遇到过“用户突然登不上”但不知道怎么查的运维。不管你是哪一种看完这篇应该都能对 PAM 建立一套从配置到排障的完整思路。1. 先盘一盘PAM 的配置到底在管什么1.1 配置文件与模块的对应关系PAM 的配置入口在 /etc/pam.d/ 目录下每个服务名对应一个文件比如 login、sshd、su、sudo、passwd。系统真正执行认证时不是单独调用某个模块而是按文件中记录的堆栈顺序从上到下依次调用一堆 pam_*.so 模块。我见过不少新手拿着 /etc/pam.d/system-auth 改了半天发现 sshd 根本不走这份配置为什么因为 sshd 服务文件里写的是其他路径或者它调用了 password-auth。你改 system-auth 改得再仔细只要 sshd 没引用它就相当于白改。这一点是整个 PAM 配置里最容易闹乌龙的地方。每个配置行的结构很简单type control module-path module-argumentstype 决定这个模块参与哪个认证阶段常见的有 auth验证身份、account检查账户是否可用、password修改密码、session登录后会话初始化。control 是控制标志决定模块鉴权失败后怎么处理。module-path 是 .so 文件路径通常写相对路径 pam_xxx.so。后面跟参数。1.2 为什么说 PAM 是“认证流水线”而不是“认证开关”我们经常把 PAM 理解成一道门其实不准确。门只有开和关两种状态但 PAM 是一条流水线每个模块都是流水线上的一道检查工序。有的工序负责验证密码有的负责检查账户是否过期有的负责记录审计日志有的负责给会话加资源限制。各工序之间通过控制标志互相联动形成一个综合判定结果。举个例子用户 SSH 登录时sshd 服务文件里通常有两条 auth 类型的模块pam_env.so 负责初始化环境变量pam_unix.so 负责核验密码。如果 pam_unix.so 判定密码错误前面 pam_env.so 做的一切初始化都没用认证依然失败。反过来pam_env.so 失败也不代表认证失败因为它只是调味料不是主菜。这个“流水线”思维特别重要。你后面配置 faillock、time、limits、exec 这些模块时所有行为都是在这条流水线上叠加工序。顺序、控制标志、参数任何一个地方出问题表现出的症状往往很诡异比如密码明明是对的但就是登不上或者某个用户只有在特定时间段才能登录。下一节我挑几个实战中最常碰到的模块逐个拆开讲。2. pam_faillock把暴力破解挡在认证门外2.1 为什么密码正确也登录不了——先说清 faillock 的触发链路很多第一次接触 faillock 的人都会遇到这个困惑我把 faillock 加进配置后故意输错三次密码然后连正确密码都登不进去了。他们以为配置写错了其实这恰恰是 faillock 在正常工作。faillock 的机制是在连续失败达到阈值后将该用户标记为“锁定”状态。之后该用户再发起认证即使密码输入正确也会在 auth 阶段直接被拒绝。这通常是为了防止攻击者对 SSH 密码进行无限次尝试但也意味着生产环境里配置不当很容易把正常用户锁在门外。配合 faillock 工作的状态目录默认在 /var/run/faillock/每个用户生成一个文件里面记录失败次数、失败时间和来源 IP。你可以随时用 faillock --user 用户名 查看状态用 faillock --user 用户名 --reset 手动清零。2.2 完整的 faillock 配置长什么样以 RHEL 系的发行版为例faillock 要同时出现在 system-auth 和 password-auth 两个文件里而且必须同时出现在 auth 和 account 两个阶段。只写一半是无效的这是我见过最多的问题。/etc/pam.d/system-auth示例auth required pam_faillock.so preauth audit silent deny5 unlock_time900 auth sufficient pam_unix.so nullok try_first_pass auth [defaultdie] pam_faillock.so authfail audit deny5 auth sufficient pam_faillock.so authsucc audit deny5 account required pam_faillock.sodeny5连续失败 5 次锁定。unlock_time900锁定 900 秒后自动解锁。preauth在密码验证之前先检查锁定状态如果已锁定直接拒绝不再问密码。authfail密码验证失败后记录一次失败。authsucc认证成功后清空失败记录。注意 authfail 和 authsucc 所在行的控制标志是[defaultdie]和sufficient。这个设计是故意的authfail 只要执行就一定失败并中断后续模块确保失败次数被累计下来authsucc 则负责在认证成功时立即放行并清除旧记录不让后边模块画蛇添足。2.3 配置不生效的几个常见原因我在客户环境里排查过好几次“为什么配好了 faillock 还是被爆破”的问题总结下来多半是以下三个原因第一个sshd 服务文件里没有引用 system-auth 或 password-auth而是自己写了一套精简配置faillock 根本没被加载。需要确认 /etc/pam.d/sshd 文件内容确实包含对公共策略文件的引用。第二个只配了 auth 阶段没有配 account 阶段的那一行。faillock 的锁定状态检查实际上发生在 account 阶段不写就等于没检查。第三个状态目录权限异常。faillock 运行时会向 /var/run/faillock/ 写入文件如果该目录所有者不是 root 或权限设置过严模块会静默失败既锁不了人也不报错。建议检查目录权限为 0755属主 root。提示测试时不要拿生产业务账号试先用一个临时账号在测试机验证完整流程确认阈值、锁定、解锁都符合预期再上线。2.4 阈值和锁定时长怎么定很多运维拍脑袋写 deny3结果周一早上发现十几个人被锁运维电话被打爆。核心原则是阈值要大于正常用户输错密码的“容错空间”锁定时间要短到不至于影响工作又长到能拖慢攻击者。一般建议场景deny 值unlock_time说明普通业务服务器5900兼顾安全与可用性内网跳板机31800暴露面大但用户可控root 账号31800要额外用 even_deny_root 开启对 root 的锁定权限最大的 root 账号默认不参与锁定防止攻击者锁定管理员后造成无法恢复的局面。如果内网环境确认安全可以考虑even_deny_root参数同时设置root_unlock_time单独给 root 一个解锁时间。建议不要把 root_unlock_time 设得和普通用户一样短否则一次误操作真的能让你半夜去机房。注意faillock 只能缓解在线密码猜测攻击对离线破解无能为力。生产环境建议搭配 fail2ban 一类工具在多次尝试后直接对来源 IP 做网络层封禁这样能有效减少 SSH 进程的资源消耗。但配置时注意只封临界 IP避免误伤公司出口 IP否则全公司的人都会来找你。3. pam_time 与 pam_limits时间窗口和资源边界3.1 用 pam_time 限制登录时段pam_time 是一个容易被忽略的模块它能在认证阶段检查“现在是否允许该用户在此时登录”这一条件。配置不在 pam 目录里而在 /etc/security/time.conf 文件里格式为services;ttys;users;times分号分隔四段分别是服务名、终端名、用户名、允许的时间段。时间段用字母表示星期几比如 Mo、Tu、We、Th、Fr、Sa、Su以及 Wd工作日和 Al所有天。时间范围用 24 小时制比如 0800-1800。多条规则可叠加规则之间用 | 分隔。举个运维场景研发团队每周五下午要发版希望发版窗口内只有运维人员能登服务器。login;tty*;!admin;!Fr1800-2000 sshd;*;!admin;!Fr1800-2000这条规则的意思是对 login 和 sshd 两个服务除 admin 组外的所有用户在周五 18:00 到 20:00 之间禁止登录。这里的 ! 表示排除或取反。使用前需要先在对应服务文件里加一行account required pam_time.so不加载模块time.conf 写再多也不会生效。这个模块限制的是“新建立的会话”已经登录的连接不受影响所以长连接不会因为过了时间窗被踢掉设计上还是合理的。3.2 pam_limits 与 ulimit 的微妙关系每个用户登录后进程能打开多少文件、能开启多少进程、能占多大内存这些看似是 shell 内建命令 ulimit 的事其实背后真正做限制的是 pam_limits.so 模块。登录时 PAM 会读取 /etc/security/limits.conf 和 /etc/security/limits.d/ 下的配置把它们应用到新会话。shell 只是把最终结果展示给你看而已。limits.conf 常用配置* soft nofile 65535 * hard nofile 65535 * soft nproc 4096 * hard nproc 4096nofile单进程可打开的文件描述符数量高并发服务通常需要调大。nproc用户可创建的进程数限制过小会导致“fork: Cannot allocate memory”的错误。core是否允许生成核心转储文件排查程序崩溃时需要放开。memlock锁定内存的最大值数据库类应用常用。注意 soft 和 hard 的区别soft 是普通用户可以直接修改的上限hard 是普通用户能调整的最高边界。业务进程通常调 soft 和 hard 到同一值避免运行时因为软限制不够而报错。3.3 一个实际踩过的坑nproc 限制被误用有次我给一台服务器上的应用账号配了 nproc 限制本来是想限制应用进程数量结果没过多久整个系统出现周期性假死。排查后发现是某个日志处理进程 fork 出大量子进程触发了 nproc 限制然后该用户下所有进程都无法创建新线程连负责清理日志的定时任务都跟着遭殃。问题根源是 limits.conf 中 nproc 只对该用户生效但日志进程归属同一个用户互相牵连。更稳妥的做法是单独建一个系统账号跑日志任务与应用账号隔离或者提高 nproc 阈值只对明显异常的进程做单独限制。另外要特别留意limits 只在包含 pam_limits.so 的会话阶段才会生效。有的精简配置里 session 阶段没有 pam_limits.so就会出现“我明明改了 limits.conf 但一切照旧”的状况。可以用 ulimit -n 检查当前会话再用cat /proc/pid/limits确认关键进程的实际限制值。4. pam_exec把自定义逻辑嵌入认证流程4.1 用脚本做认证的“外挂”PAM 的乐趣在于它可以扩展出无数个性化的逻辑pam_exec 就是这扇门。它的作用是当 PAM 走到某个阶段时调用外部脚本并把当前用户的认证信息作为环境变量传给脚本。这样一来你可以实现登录 IP 记录、异地登录提醒、动态封禁异常来源、把审计日志同步到内部平台等能力。我常用 pam_exec 做一件事登录成功通知。每次有人 SSH 登录服务器脚本读取 PAM_USER、PAM_RHOST 等变量把登录事件推送到企业微信机器人类似的通道也可以用任意内部通知接口。异地登录、凌晨登录这类异常行为几分钟内就能发现。先看基本用法。在服务文件里加一行session required pam_exec.so /usr/local/bin/login-notify.sh在 auth 阶段也可以调用 pam_exec但参数不同。认证阶段的脚本可以影响认证结果脚本退出码非 0 时PAM 会认为本次检查失败。这是一个非常强大的能力但也非常危险稍后细说。4.2 pam_exec 实际示例登录通知脚本写一个 bash 脚本#!/usr/bin/env bash # /usr/local/bin/login-notify.sh notify_urlhttp://your-internal-msg-service/send username${PAM_USER:-unknown} src_ip${PAM_RHOST:-unknown} service${PAM_SERVICE:-unknown} tty${PAM_TTY:-unknown} time_now$(date %Y-%m-%d %H:%M:%S) payload{\msg\:\[SSH登录提醒] 用户 ${username} 从 ${src_ip} 登录服务器, 服务 ${service}, 终端 ${tty}, 时间 ${time_now}\} curl -s -m 5 -X POST -H Content-Type: application/json -d ${payload} ${notify_url} /dev/null 21 || true脚本放在 /usr/local/bin 下并加执行权限。curl 失败时用|| true兜底避免因通知服务抖动影响登录流程。这点很重要外部服务不可控绝不能因为通知失败导致用户无法登录。pam_exec 在认证阶段能获取的环境变量名称含义PAM_USER发起认证的用户名PAM_RHOST远程主机 IP本地登录时为空PAM_SERVICE服务名如 sshd、suPAM_TYPE当前阶段如 auth、account、sessionPAM_TTY终端设备名4.3 借助 pam_exec 做动态封禁的注意点如果不想部署 fail2ban也可以自己写个脚本监控登录失败。思路是把 pam_exec 挂在 auth 阶段脚本统计来源 IP 的失败次数超过阈值就写入防火墙规则。这个方案的好处是完全可控规则、阈值、封禁时长全自己说了算还能对接已有的资产管理系统。但有两个坑必须提醒。第一脚本执行环境非常干净PATH 可能只有 /usr/bin:/bin命令路径要写绝对路径不能用相对路径去指望环境里有某个第三方命令。第二认证阶段的脚本要尽可能简短它运行在认证链路里执行时间过长会影响所有用户的登录体验建议将耗时操作异步化。提示pam_exec 默认以 root 身份执行脚本里所有文件操作都要谨慎。尤其不要用来源 IP、用户名等外部输入直接拼命令或拼路径严格过滤后再使用避免拼接问题演变成安全漏洞。4.4 一个反面案例脚本返回码导致全员无法登录有次同事在 auth 阶段挂了一个脚本作用是检查用户是否在允许名单里。脚本本身逻辑没问题但他在脚本最后用 grep 判断时忘记处理 grep 无匹配时的退出码。结果名单里的用户能登录名单外的用户登录时脚本返回非零导致认证失败。更糟的是他测试时只测了名单内的用户上线后才发现其他人全被挡在外面。教训有两条一是脚本里所有命令都要清楚返回码含义并能被正确吞掉比如在脚本末尾显式exit 0表示脚本本身执行成功由业务逻辑决定是否阻断二是改完配置后的测试用例要覆盖“正向路径”和“反向路径”只测正常流程不测拒绝流程永远是隐患。5. 控制标志与堆栈顺序模块的前后顺序能决定整条链路的命运5.1 四种控制标志语义对比PAM 配置里每个模块后面都跟着控制标志它决定检查结果的“表决权重”。很多故障看起来毫无头绪最后追到根上都是控制标志用错或顺序颠倒。先列一下四种标志标志失败后果成功后果适用场景required本模块失败最终认证失败但继续执行后续模块继续执行后续模块必须通过的基础检查requisite本模块失败立即中断整个认证流程继续执行后续模块核心安全闸门如密码校验sufficient本模块失败继续成功则直接通过认证跳过后续模块立即放行辅助认证或双因子之一optional无论结果如何都不影响最终判定不改变最终判定仅记录信息的附加模块用生活的例子来理解进一栋大楼有三道门第一道查证件第二道刷卡第三道是安检。如果三道都是 required任何一道失败你都被拦下但拦下之前门卫还是会让你把三道走完为的是审计记录。如果第一道是 requisite证件不对就直接请你出去后面两道不用走。如果第二道是 sufficient只要刷卡成功后面安检都不看了这对安全要求高的场景可能不合适。5.2 顺序错误可能导致的两个典型故障之前那个把 pam_deny.so 放到 pam_unix.so 前面的案例就是典型的顺序错误。pam_deny.so 的作用是无条件拒绝认证不管密码是否正确它直接让认证失败。把它放在密码校验之前所有用户都会登录失败。另一种更隐蔽的问题发生在 sufficient 标志上。某些服务文件里在密码验证之前放了一个 pam_permit.so并标注为 sufficient。pam_permit.so 无条件返回成功配合 sufficient 就直接放行了。这种配置如果是为了调试而临时加的用完后没有移除就会变成任何人都可以免密登录后果非常严重。5.3 推荐的 PAM 堆栈组织逻辑一个健康的服务配置应该按照认证阶段、账户阶段、会话阶段分层组织。以 SSH 登录为例大致是auth先做 pam_faillock 预检再做 pam_unix 密码校验再检查是否锁定 account检查账户有效期、锁定状态、登录时间窗 session初始化环境、应用资源限制、记录登录审计每一层只做该层的事。把 session 阶段的事情塞进 auth 阶段比如在 auth 阶段调 pam_limits.so轻则无效重则因为模块加载失败把认证链路搞崩。把 account 阶段的事情塞进 auth 阶段如登录时间检查就会发生用户已经被判定锁定却还在来回尝试验证密码的浪费。5.4 用 pam_control 公用文件统一管理为了避免重复维护多个服务文件很多发行版引入了公用 PAM 配置文件机制。RHEL 系的 system-auth 和 password-auth 就是这种设计。sshd、login、su 等服务文件里只需引用这些公用文件改动一处即可生效。使用这种机制时要特别注意一个习惯不要手工在服务文件里大改尽量通过发行版提供的集中管理工具来调整。现代的 authselect 或 pam-config 能帮助生成和管理 PAM 模板比直接改配置文件更安全也能避免模板更新时被自定义内容覆盖的情况。如果你还在用老办法直接改文件建议尽早迁移到这些工具上。6. 排查 PAM 问题的三板斧日志、状态、试验环境6.1 第一板斧看日志PAM 认证失败时系统日志里通常会有明确记录。具体位置取决于发行版常见的有 /var/log/secure 和 /var/log/auth.log也可以统一用 journalctl 查。敢搜日志比乱改配置有用得多因为 PAM 模块在执行时会输出失败原因比如常见提示“ Authentication failure”、“User not known”、“Permission denied”。一个典型的排障思路是先用journalctl -u sshd -n 200看 SSH 认证的最新几十条日志确认失败发生在哪个阶段。是密码验证没过还是账户锁定还是因为时间窗、资源限制被拒日志定位完再检查对应模块的配置文件效率会高很多。6.2 第二板斧查状态文件有些模块有自己的运行状态比如 faillock 的状态在 /var/run/faillock/ 目录tally2 的旧式状态在 /var/log/tallylog。排查“用户被锁定”类问题时先看状态目录里有没有对应记录再确认它是否是预期的锁定。还有一点如果系统配置了 SSSD 或 LDAP 这类集中认证本地 PAM 堆栈执行完之后认证结果可能还会被远端系统覆盖。这时候日志里可能出现“账号锁定”但本地状态文件没有记录的情况需要联系统一认证平台查询。不能只盯着本地文件看。6.3 常见故障速查表故障现象可能位置排查方向所有用户无法登录认证阶段顺序检查服务文件是否混入 pam_deny 或 permit 放行密码正确但被拒pam_unix / pam_faillock查 faillock 状态、密码策略、LDAP 链路特定用户无法登录account 阶段检查账户锁定、有效期、时间窗登录后没有资源限制session 阶段确认会话阶段是否加载 pam_limits.so登录慢 / 卡顿外部脚本或网络调用检查 pam_exec 脚本是否阻塞、超时sudo 无法执行sudo 服务文件检查 /etc/pam.d/sudo 引用和 secure_path6.4 第三板斧在可恢复的环境中测试PAM 配置一旦出错最轻微的影响是单个用户无法登录最严重的是 root 都被锁死。所以我在任何生产环境改动 PAM 之前一定会先做三件事第一备份原配置文件注意连同 limits.conf、time.conf 等关联文件一起备份。第二同时开启两个终端一个专门用来测试另一个保留原样用来回滚。如果测试终端把配置搞坏至少还有一个终端可以恢复文件。第三尽量先在一个隔离环境或测试机上完整演练一次改动流程确认无误再在生产执行。6.5 最危险的故障root 被锁死后的恢复思路这个情况一旦发生系统层面几乎无解因为所有本地登录通道都要过 PAM。但可以通过系统引导阶段绕开正常认证流程来恢复。常见做法是进入单用户模式或救援模式直接挂载根文件系统修改 /etc/pam.d/ 下的配置文件。有些环境可能要求在引导参数里追加 init/bin/bash 来进入最小 shell。不过这类操作依赖发行版具体的引导方式不同环境差异比较大而且现在很多系统默认启用了加密分区恢复成本可能很高。我也遇到过设备连 KVM 都没有、只能通过厂商管理口操作的情况折腾了几个小时才把配置改回来。所以管 root 身份去动 PAM 时建议全程佩戴“安全绳”改完先别退出当前会话新开一个窗口验证 root 可以正常登录再利落地结束所有测试会话。这是我从多次事故里总结出的唯一可靠流程。6.6 使用 authselect 和 pam-config 间接调整新一代发行版普遍不再推荐直接改配置文件而是提供集中管理工具。使用 authselect 时配置以 profile 的方式管理用户只需启用某个 profile 后再应用特定 feature它会在内部生成对应的 PAM 文件。这样能极大减少手工配置带来的语法错误风险也让后续升级时不会因为自定义文件冲突而丢失改动。习惯直接编辑配置文件的老手可能会觉得这些工具不透明但我的体会是它们在模板层面做了很多校验比手工改更稳。如果你要在合规环境里管理大批主机用工具管理 PAM 配置再配合配置管理系统统一分发比一台一台手改要可靠得多。7. 关于 PAM 的最后一组实在话改 PAM 配置这件事风险永远是高过收益的除非你明确知道自己要解决什么问题否则不要为了“看起来安全”而堆一堆模块上去。我见过把 faillock、time、limits、exec 全部一股脑塞进一个服务文件的做法最终因为模块之间互相牵制导致各种诡异故障。PAM 的精髓在于最小化只加能解决实际问题的模块每一个模块的加入都要经过完整的测试。如果你刚接触 PAM想练手又怕影响线上环境可以在 /etc/pam.d/ 里新建一个测试服务文件比如 test-auth然后在命令行里用 pamtester 工具模拟认证。这样所有模块改动都发生在孤立环境中不会波及真实服务。我每次写新的模块组合都会先在 test-auth 上验证完整流程再把它应用到正式服务这套方法从没出过岔子。安全认证本质上是一个“层层设防”的问题PAM 给了我们足够的灵活性也要求我们对每个细节负责。希望这篇能帮你少走一些我走过的弯路也欢迎把你踩过的 PAM 坑在评论区写出来很多你想不通的现象可能刚好别人见过答案。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑