T2、PAM 策略
适用于使用 Linux-PAM 的本地账号认证。Linux-PAM 是应用调用的认证框架;auth 栈验证凭据,account 栈检查账号是否获准使用服务,password 栈处理改密,session 栈设置登录会话。发行版与验收边界见下表。本文按本地账号 + SSH给出可操作示例;密码质量一节针对本机改密。pamcheck 是测试账号,server.example 是示例主机名,执行前换成实际值。一次只选择一个场景,从该节的第一步做到回退验证或验收完成。
| 要做什么 | 看哪一节 | 影响范围 |
|---|---|---|
| 限制某个账号通过 SSH 登录 | 2 | 仅 SSH |
| 调整 SSH 登录用户的文件句柄数 | 3 | 新 SSH 会话 |
| 设置新密码复杂度 | 4 | 经过本机 PAM 的改密操作 |
| 密码输错后锁定 | 5 | RHEL/Rocky 的所选认证栈;Debian/Ubuntu 示例仅 SSH |
| SSH 密码加动态验证码 | 6 | 该主机的全部 SSH 登录;需先盘点所有登录方式 |
最后核验日期:2026-10-15。执行账号为目标主机现有的管理员,通过 sudo 修改配置;平台运维团队维护 /etc/pam.d/、/etc/security/ 和 SSH 配置。各场景相互独立,不能把后一节的配置直接叠加到前一节的测试状态。配置生效后,SSH 进程通过服务名 sshd 读取 /etc/pam.d/sshd,再按其中的引用加载共享栈和策略文件。
| 系统与路径 | 认证配置入口 | 本次验收范围 |
|---|---|---|
| Rocky Linux 8.10 / 9.8 | authselect 管理共享栈,sshd 文件承载 SSH 专用规则 |
原生系统隔离副本,真实 SSH、普通用户改密及五项策略回退 |
| Debian 13 | pam-auth-update 管理 common-* |
原生系统隔离副本,真实 SSH、普通用户改密及五项策略回退 |
| Ubuntu 22.04 LTS | 同上 | 原生系统隔离副本,真实 SSH、普通用户改密及五项策略回退 |
| Debian 12、Ubuntu 24.04 / 26.04 LTS | 同上 | 固定摘要容器内的发行版用户空间;不代替该版本主机的 systemd、SELinux、重启或带外恢复验收 |
RHEL、Rocky 其他主版本、非 x86_64 架构、外部身份源和自定义认证栈未纳入本次签收,不继承上表结果。已安装系统采用发行版支持的软件包,不应为套用上游最新版本而单独替换系统 PAM。现场固定包版本、SSH 有效配置、备份和登录结果应随变更记录归档。可复核的平台包版本、测试入口与结果见 回归验收记录。
1、开始前:准备测试和回退
1、在与生产同发行版、同主版本、同认证方式的测试机先做一遍。测试机若还没有 pamcheck,先执行 getent passwd pamcheck;确认无输出后创建:
bash <<'PAM_CREATE_TEST_ACCOUNT'
set -Eeuo pipefail
printf '[执行] PAM 步骤 01\n'
if getent passwd pamcheck >/dev/null; then
printf '账号 pamcheck 已存在;停止创建,按既有账号路径验收。\n' >&2
exit 1
fi
sudo useradd -m -s /bin/bash pamcheck
sudo passwd pamcheck </dev/tty
printf 'PAM_CREATE_TEST_ACCOUNT_OK\n'
PAM_CREATE_TEST_ACCOUNT
生产机使用已纳管的测试账号,不要为了测试随意新增可远程登录账号。先确认它当前可以通过要测试的方式登录;下面的弱密码、错误密码测试都只对这个账号进行。
2、在生产操作时保留当前管理员 SSH 会话,确认带外控制台或虚拟机控制台可用;另开一个终端做新登录测试。不要先退出原会话。
3、在目标主机的当前管理员终端建立备份目录,记下打印的路径。每节会指出应备份的文件;每做一个场景建立一个新目录,避免同名备份被覆盖。
bash <<'PAM_PREPARE_BACKUP'
set -Eeuo pipefail
printf '[执行] PAM 步骤 02\n'
cat /etc/os-release
BACKUP_DIR=$(sudo mktemp -d /root/pam-backup.XXXXXXXX)
printf 'export BACKUP_DIR=%q\n' "$BACKUP_DIR"
printf 'PAM_PREPARE_BACKUP_OK\n'
PAM_PREPARE_BACKUP
脚本会打印一条 export BACKUP_DIR=... 命令;在当前管理员终端执行打印出的完整命令,再进入所选场景。该变量必须导出,后续 Bash 子进程才可读取。换终端时执行记录的同一条导出命令。每个独立场景使用新备份目录;重复执行备份时保留首次副本,不覆盖原始状态。
4、PAM 文件的改动通常从下一次认证起生效;改动 SSH 配置后才需要检查语法并重载 SSH。sshd -t 不检查 PAM 规则是否正确,最终以新连接的正反测试为准。测试 SSH 时,在另一终端或另一台机器执行文中的 ssh 命令,不要在已有 SSH 会话中用 su 代替。
任一步的前提或预期结果不符合时,停止后续命令,从保留的会话恢复本次修改的文件并重新测试。不要继续叠加下一项策略。
发行版入口:RHEL/Rocky Linux 的 system-auth、password-auth 常由 authselect 生成,不能手改生成文件。Debian/Ubuntu 的 common-* 常由 pam-auth-update 管理。SSH 服务专用的改动放在 /etc/pam.d/sshd。若主机接入 AD、LDAP、SSSD、其他 MFA 或自定义 PAM 栈,先核对实际入口;本文的默认栈示例不能直接套用。
2、限制指定账号登录 SSH
1、核对并备份。 运行 sudo sshd -T | grep '^usepam ',必须看到 usepam yes;否则停止。再执行:
bash <<'PAM_BACKUP_SSH_ACCESS'
set -Eeuo pipefail
printf '[执行] PAM 步骤 03\n'
: "${BACKUP_DIR:?请先在当前终端执行备份脚本打印的 export 命令}"
sudo test -d "$BACKUP_DIR"
sudo test -e "$BACKUP_DIR/sshd" || sudo cp -a /etc/pam.d/sshd "$BACKUP_DIR/"
printf 'PAM_BACKUP_SSH_ACCESS_OK\n'
PAM_BACKUP_SSH_ACCESS
2、检查规则文件。 /etc/security/sshd-access.conf 由目标主机运维创建,只有第 2 节第 4 步新增的 pam_access.so accessfile=... 读取它,不替换共享 access.conf。下面两行是首次演练的完整文件。先运行 sudo ls -l /etc/security/sshd-access.conf;提示文件不存在属于正常情况。若文件已存在,先执行 sudo test -e "$BACKUP_DIR/sshd-access.conf" || sudo cp -a /etc/security/sshd-access.conf "$BACKUP_DIR/" 和 sudo sed -n '1,120p' /etc/security/sshd-access.conf;有现行业务规则时,先梳理原规则,不能直接覆盖。用 sudoedit /etc/security/sshd-access.conf 建立服务专用规则。首次演练先拒绝测试账号:
-:pamcheck:ALL
+:ALL:ALL
3、设置权限。
bash <<'PAM_ACCESS_FILE_PERMISSIONS'
set -Eeuo pipefail
printf '[执行] PAM 步骤 04\n'
sudo chown root:root /etc/security/sshd-access.conf
sudo chmod 0644 /etc/security/sshd-access.conf
printf 'PAM_ACCESS_FILE_PERMISSIONS_OK\n'
PAM_ACCESS_FILE_PERMISSIONS
4、接入规则。 用 sudoedit /etc/pam.d/sshd,在第一条 account 规则之前加一行;原有规则保持原样:
account required pam_access.so accessfile=/etc/security/sshd-access.conf
5、验收。 在另一终端执行 ssh pamcheck@server.example 应被拒绝;另一名正常账号的新 SSH 登录应成功。若 pamcheck 配有公钥,也测试其公钥方式,仍须被拒绝。演练通过后,才把规则中的 pamcheck 改成实际要限制的账号,并重复正反测试。已有 SSH 会话不会因此断开。
回退:策略不符合预期或演练完成
从保留的管理员会话执行
sudo cp -a "$BACKUP_DIR/sshd" /etc/pam.d/sshd;用新 SSH 连接确认恢复。若原规则文件存在,执行sudo cp -a "$BACKUP_DIR/sshd-access.conf" /etc/security/sshd-access.conf;若是本次新建,确认已没有 PAM 引用后再归档。仅改 PAM 文件,无须重启 SSH。适用边界: 规则按首次匹配处理;第二行允许其他用户继续接受后续 PAM/SSH 检查,不代表强制放行。此专用规则文件只由上面新增的 SSH 规则读取。
3、限制 SSH 会话的文件句柄数
1、核对入口。 先找 pam_limits.so,并沿 SSH 的 session include / @include 检查实际会话栈:
sudo grep -R -n pam_limits.so /etc/pam.d
若目标 SSH 会话栈里已有 pam_limits.so,不要重复添加。若没有,先执行 sudo test -e "$BACKUP_DIR/sshd" || sudo cp -a /etc/pam.d/sshd "$BACKUP_DIR/",再用 sudoedit /etc/pam.d/sshd 在 session 规则中加入一次 session required pam_limits.so。
2、写入限制。 本节文件由目标主机运维维护,供 SSH 会话栈中的 pam_limits.so 按用户名读取;下面是仅含测试账号规则的完整文件,后续回退复用该文件名。若 /etc/security/limits.d/90-pamcheck-nofile.conf 已存在,先执行 sudo test -e "$BACKUP_DIR/90-pamcheck-nofile.conf" || sudo cp -a /etc/security/limits.d/90-pamcheck-nofile.conf "$BACKUP_DIR/" 并保留原有其他规则。用 sudoedit /etc/security/limits.d/90-pamcheck-nofile.conf 写入:
pamcheck soft nofile 4096
pamcheck hard nofile 8192
3、验收。 从另一台机器执行:
ssh pamcheck@server.example 'ulimit -Sn; ulimit -Hn'
新会话应依次输出 4096、8192。若不符,检查是否已有针对该账号的其他 limits.conf / limits.d 规则、用户 shell 启动脚本是否再调整 ulimit,以及 SSH 的 session 栈是否真的调用模块。
回退:策略不符合预期或演练完成
若规则文件原本存在,执行
sudo cp -a "$BACKUP_DIR/90-pamcheck-nofile.conf" /etc/security/limits.d/90-pamcheck-nofile.conf;若是本次新建,执行sudo rm /etc/security/limits.d/90-pamcheck-nofile.conf。若本次还加了pam_limits.so行,执行sudo cp -a "$BACKUP_DIR/sshd" /etc/pam.d/sshd。重新登录后检查ulimit,旧会话的值不会自动改变。适用边界:
pam_limits作用于经过 PAM 打开的会话,不改变由 systemd 直接启动的服务;后者要在其 systemd 单元中设置资源限制。
4、设置新密码质量
1、核对并备份。 测试账号须使用本机改密路径。若策略文件已存在,先备份:
bash <<'PAM_BACKUP_PASSWORD_QUALITY'
set -Eeuo pipefail
printf '[执行] PAM 步骤 05\n'
: "${BACKUP_DIR:?请先在当前终端执行备份脚本打印的 export 命令}"
sudo test -d "$BACKUP_DIR"
if test -e /etc/security/pwquality.conf; then
sudo test -e "$BACKUP_DIR/pwquality.conf" || sudo cp -a /etc/security/pwquality.conf "$BACKUP_DIR/"
fi
printf 'PAM_BACKUP_PASSWORD_QUALITY_OK\n'
PAM_BACKUP_PASSWORD_QUALITY
先检查 /etc/security/pwquality.conf.d/ 中的生效配置和实际模块行;enforcing = 0 会关闭强制拒绝,不能只靠长度项判断策略已生效。
Debian/Ubuntu 还要执行 sudo test -e "$BACKUP_DIR/common-password" || sudo cp -a /etc/pam.d/common-password "$BACKUP_DIR/",因为安装模块可能自动启用 pwquality profile 并改变这个共享文件。安装前同时记录 dpkg-query -W libpam-pwquality、grep -n pam_pwquality.so /etc/pam.d/common-password 和当前 pam-auth-update 选择状态,判定“本次启用”必须依据安装前记录。软件源须有效且包含所需包;离线现场由交付方准备发行版签名软件包及依赖,不把上网下载作为现场必经条件。
2、确认模块接入,按发行版选一条路径。 RHEL/Rocky Linux 运行下面三条只读命令;最后一条应在实际改密栈里找到 pam_pwquality.so。找不到时停止:不能只写策略文件,也不能直接修改 authselect 生成文件。
bash <<'PAM_CHECK_ROCKY_QUALITY'
set -Eeuo pipefail
printf '[执行] PAM 步骤 06\n'
authselect current
sudo authselect check
grep -n pam_pwquality.so /etc/pam.d/system-auth /etc/pam.d/password-auth
printf 'PAM_CHECK_ROCKY_QUALITY_OK\n'
PAM_CHECK_ROCKY_QUALITY
Debian/Ubuntu:先运行 grep -n pam_pwquality.so /etc/pam.d/common-password 记录当前状态。显式安装词典及生成工具可避免关闭 APT 推荐依赖的系统出现 error loading dictionary、导致所有新密码均被拒绝;不得用 dictcheck = 0 绕过该故障。无论 profile 是否已启用,先确认词典文件存在;缺失时执行下列安装命令补齐依赖。若已启用,不要重复启用;若未启用,先执行 sudo apt-get install libpam-pwquality cracklib-runtime wamerican,再执行 ls -l /usr/share/pam-configs/pwquality 确认 profile 存在。只有文件存在时才继续:
bash <<'PAM_ENABLE_DEBIAN_QUALITY'
set -Eeuo pipefail
printf '[执行] PAM 步骤 07\n'
test -r /var/cache/cracklib/cracklib_dict.pwd
test -r /var/cache/cracklib/cracklib_dict.pwi
test -r /var/cache/cracklib/cracklib_dict.hwm
sudo pam-auth-update --enable pwquality
grep -nE 'pam_pwquality|pam_unix' /etc/pam.d/common-password
printf 'PAM_ENABLE_DEBIAN_QUALITY_OK\n'
PAM_ENABLE_DEBIAN_QUALITY
应看到 pam_pwquality.so,并确认其后仍有负责保存密码的 pam_unix.so;检查失败先回退,不继续改策略文件。
3、设置策略,两类系统相同。 以下为目标键的合并项,不是覆盖全文件的模板;它由 pam_pwquality.so 在改密时读取,不需要重启 SSH。用 sudoedit /etc/security/pwquality.conf 设置本次示例策略;要求新密码至少 16 个字符,并至少各有一个大写字母、小写字母、数字和其他字符。已有其他策略项时只改目标项,不要写出重复且互相冲突的键:
minlen = 16
ucredit = -1
lcredit = -1
dcredit = -1
ocredit = -1
4、验收。 先检查实际 pam_pwquality.so 行是否另带 minlen、ucredit 等参数;模块参数会覆盖文件中的同名项,应先解决冲突。以普通账号 pamcheck 登录后运行 passwd:输入当前密码后,分别验证 15 个字符但四类齐全、16 个字符以上但缺任一类别的密码均被拒绝;16 个字符以上且满足全部现行质量检查的新密码应成功。成功后使用新密码建立 SSH 连接,确认密码确已保存。可以从管理员终端执行 sudo -iu pamcheck 进入该账号,再在其 shell 内运行 passwd;不能在 root 身份下用 sudo passwd pamcheck 代替。不要把密码写进命令行或日志。
回退:策略不符合预期或演练完成
若策略文件原本存在,执行
sudo cp -a "$BACKUP_DIR/pwquality.conf" /etc/security/pwquality.conf;原本没有时,只撤销自己写入的策略项,若文件由新装的软件包提供,不要删掉包管理的配置。Debian/Ubuntu 仅当本次才启用了pwqualityprofile 时,执行sudo pam-auth-update --remove pwquality;--remove在软件包仍保留时撤销该 profile 的接入;不得卸载模块文件后再撤销。Ubuntu 22.04 不支持--disable,不可替换为该参数。检查common-password中已无本次新增的pam_pwquality.so行,并对照安装前备份保留其他模块;模块仍在或生成器报告本地改动时立即停止,不使用--force覆盖其他配置。再次以普通用户改密验证。策略回退不会恢复已经修改的用户密码;测试账号密码由账号所有者通过受控流程重新设置,不回拷整个/etc/shadow。适用边界: 这项策略约束新密码的修改,不追溯旧密码。root 修改他人密码的默认处理不同,不能用
sudo passwd pamcheck替代普通用户自助改密测试。外部身份源可能不经过本机改密栈。
5、密码失败后锁定账户
先选路径。 5.1 节用于使用 authselect 的 RHEL/Rocky Linux;5.2 节只用于本地账号、默认 common-auth、SSH 的 auth 段只有 @include common-auth 的 Debian/Ubuntu 主机。5.2 节只限制 SSH 的 PAM 密码认证,不覆盖 su、控制台等服务。两条路径都会读取共享的 /etc/security/faillock.conf;如果已有其他服务使用 pam_faillock.so,改阈值也会影响它们,应先核对现行策略。
5.1、RHEL/Rocky Linux
1、记录 authselect current 的 profile 和已有 feature;authselect check 必须成功:
bash <<'PAM_CHECK_AUTHSELECT'
set -Eeuo pipefail
printf '[执行] PAM 步骤 08\n'
authselect current
sudo authselect check
printf 'PAM_CHECK_AUTHSELECT_OK\n'
PAM_CHECK_AUTHSELECT
2、仅当该 profile 支持且尚未启用 with-faillock 时,再执行:
bash <<'PAM_ENABLE_FAILLOCK'
set -Eeuo pipefail
printf '[执行] PAM 步骤 09\n'
sudo authselect enable-feature with-faillock
sudo authselect check
grep -n pam_faillock.so /etc/pam.d/system-auth /etc/pam.d/password-auth
printf 'PAM_ENABLE_FAILLOCK_OK\n'
PAM_ENABLE_FAILLOCK
后两条必须成功且能找到模块。检查失败时从保留的会话回退本次 feature,不要覆盖生成的 PAM 文件。若 with-faillock 原本已启用,只核对现有规则,不要把它当作本次新增功能。
5.2、Debian/Ubuntu 本地账号 SSH
1、确认 pamcheck 是本地账号:grep '^pamcheck:' /etc/passwd 必须有输出。在同版本测试机先核对:
bash <<'PAM_CHECK_DEBIAN_AUTH_STACK'
set -Eeuo pipefail
printf '[执行] PAM 步骤 10\n'
grep -nE '^(@include|auth[[:space:]])' /etc/pam.d/sshd
grep -E '^[[:space:]]*auth[[:space:]]' /etc/pam.d/common-auth
printf 'PAM_CHECK_DEBIAN_AUTH_STACK_OK\n'
PAM_CHECK_DEBIAN_AUTH_STACK
第一条必须显示 SSH 的 auth 段只有单独一行 @include common-auth;第二条必须依次对应下列默认三行,末尾至多另有一行 auth optional pam_cap.so,否则停止:
auth [success=1 default=ignore] pam_unix.so nullok
auth requisite pam_deny.so
auth required pam_permit.so
此处省略发行版自动生成的注释及对齐空格。如果发现 pam_sss.so、LDAP、AD、其他 MFA、自定义选项或额外 auth 规则,不能套用下面的替换。再运行 sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication) ':两项都为 no 时,本示例没有可测试的 SSH 密码路径;不要为了测试开启密码登录。
2、先备份,再确认查询命令存在:
bash <<'PAM_BACKUP_SSH_FAILLOCK'
set -Eeuo pipefail
printf '[执行] PAM 步骤 11\n'
: "${BACKUP_DIR:?请先在当前终端执行备份脚本打印的 export 命令}"
sudo test -d "$BACKUP_DIR"
sudo test -e "$BACKUP_DIR/sshd" || sudo cp -a /etc/pam.d/sshd "$BACKUP_DIR/"
command -v faillock
printf 'PAM_BACKUP_SSH_FAILLOCK_OK\n'
PAM_BACKUP_SSH_FAILLOCK
若缺少 faillock 命令,安装 libpam-modules-bin 后再继续;再运行 sudo find -L /usr -name pam_faillock.so -print,确认模块文件存在。用 sudoedit /etc/pam.d/sshd,只把那一行 @include common-auth 替换成:
auth required pam_faillock.so preauth silent
auth [success=1 default=bad] pam_unix.so
auth [default=die] pam_faillock.so authfail
auth sufficient pam_faillock.so authsucc
auth required pam_deny.so
若原 common-auth 还有 auth optional pam_cap.so,在上例的 authsucc 之前加回同一行。原 /etc/pam.d/sshd 中的 account、password、session 行均不改。此替换使 SSH 不再跟随 common-auth 的后续改动,升级软件包或调整全局认证策略后必须复查。
5.3、两条路径共同操作
1、设置阈值。 先查看 /etc/security/faillock.conf 是否存在,并备份原有文件:
bash <<'PAM_BACKUP_FAILLOCK_POLICY'
set -Eeuo pipefail
printf '[执行] PAM 步骤 12\n'
: "${BACKUP_DIR:?请先在当前终端执行备份脚本打印的 export 命令}"
sudo test -d "$BACKUP_DIR"
if sudo test -e /etc/security/faillock.conf; then
sudo test -e "$BACKUP_DIR/faillock.conf" || sudo cp -a /etc/security/faillock.conf "$BACKUP_DIR/"
fi
sudo grep -R -n pam_faillock.so /etc/pam.d
printf 'PAM_BACKUP_FAILLOCK_POLICY_OK\n'
PAM_BACKUP_FAILLOCK_POLICY
/etc/security/faillock.conf 由目标主机运维维护,供所有已接入的 pam_faillock.so 读取,faillock 查询和复位复用其计数目录。以下是顶层键合并项,不是完整文件;用 sudoedit /etc/security/faillock.conf 设置示例阈值,保留原文件中其他已生效的选项。模块行参数会覆盖该文件同名项,发现冲突先停止:
deny = 5
fail_interval = 900
unlock_time = 600
2、验收。 先用 sudo sshd -T 查看目标版本是否输出 persourcepenalties;OpenSSH 9.8 起默认还有按来源地址累计的临时惩罚。该惩罚与按账号计数的 pam_faillock 相互独立。使用不同客户端来源完成失败计数、锁定后正确密码和正常账号测试;若只能共用一个来源,必须等 SSH 日志确认的来源惩罚到期后再做下一次判定。不能因 TCP 连接被提前拒绝就认定 PAM 已锁定,也不能通过关闭来源防护完成验收。
从另一终端用 pamcheck 对目标服务输入错误密码。SSH 测试可用 ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password pamcheck@server.example;如果本机只启用了键盘交互密码认证,则把首选方法换成 keyboard-interactive。每次连接可能允许多次输入密码,按实际密码提示次数计数;用 sudo faillock --user pamcheck 核对失败记录。达到阈值后,即使输入正确密码也应被拒绝。另一正常账号必须仍可登录。锁定后等待完整 600 秒自动解锁,等待期间每 30 秒记录剩余时间,不反复认证;到期后用正确密码建立新连接。确认恢复后,再产生一次失败记录以验证人工复位。最后清除测试计数,并用正确密码确认 pamcheck 恢复:
bash <<'PAM_RESET_FAILLOCK'
set -Eeuo pipefail
printf '[执行] PAM 步骤 13\n'
sudo faillock --user pamcheck --reset
sudo faillock --user pamcheck
printf 'PAM_RESET_FAILLOCK_OK\n'
PAM_RESET_FAILLOCK
回退:策略不符合预期或演练完成
5.1 节路径若本次才启用 feature,执行
sudo authselect disable-feature with-faillock,再运行sudo authselect check;5.2 节路径执行sudo cp -a "$BACKUP_DIR/sshd" /etc/pam.d/sshd。若阈值文件原本存在,执行sudo cp -a "$BACKUP_DIR/faillock.conf" /etc/security/faillock.conf;若本次新建,确认没有其他用途后移除本次文件。执行sudo faillock --user pamcheck --reset清除测试计数,并从新连接验证。faillock --reset不会清除 OpenSSH 来源惩罚;同一来源仍被拒绝时,查看 SSH 日志并等待实际惩罚到期。若管理员已被锁定,从保留会话或控制台处理。适用边界:
deny是失败计数阈值,实际从哪次尝试开始拒绝取决于模块和栈顺序,以上正反测试不可省。SSH 公钥认证通常不计入密码失败次数。faillog不查询pam_faillock的计数。
6、SSH 密码加 TOTP
本节影响该主机全部 SSH 登录,包括管理员和自动化任务。目标是 SSH 键盘交互中的 TOTP + 原有 PAM 密码;公钥单独登录将失效。先盘点所有使用 SSH 的账号、脚本与客户端,确保需要继续登录的人员均已领取自己的 TOTP 密钥,自动化任务已有替代接入方案。在测试机先完整演练,再安排生产切换。
1、检查现状。 运行 timedatectl status,确认 System clock synchronized: yes,且客户端认证器时间正确;未同步时先修复时间服务,不继续登记。运行 sudo sshd -T | grep -E '^(usepam|kbdinteractiveauthentication|passwordauthentication|authenticationmethods) ',记录当前值。查看 /etc/pam.d/sshd 的 auth 段,确认原密码认证确实经过 PAM;若有 SSSD、LDAP、额外 MFA、自定义跳转规则或公钥与密码组合策略,先停止套用本例。
2、安装并为每人登记。 Debian/Ubuntu 执行 sudo apt-get install libpam-google-authenticator。RHEL/Rocky Linux 先确认本机获准的软件源提供该模块及 google-authenticator 命令,再用本机包管理器安装;不能假定基础仓库自带。运行 sudo find -L /usr -name pam_google_authenticator.so -print,必须找到模块文件。
在测试机以测试账号本人身份登记;生产机上每个需要 SSH 登录的交互账号也要本人完成登记:
sudo -iu pamcheck
在该账号的登录 shell 内单独运行登记向导,终端输出含密钥与恢复码,禁止录屏或转存到普通日志。向导选择基于时间的验证码、禁止重复使用验证码并启用速率限制;按提示在认证器中登记密钥并验证当前验证码,单独保管恢复码:
google-authenticator
向导完成后核验密钥文件权限:
bash <<'PAM_TOTP_SECRET_PERMISSIONS'
set -Eeuo pipefail
printf '[执行] PAM 步骤 14\n'
chmod 600 "$HOME/.google_authenticator"
test -O "$HOME/.google_authenticator"
test "$(stat -c %a "$HOME/.google_authenticator")" = 600
printf 'PAM_TOTP_SECRET_PERMISSIONS_OK\n'
PAM_TOTP_SECRET_PERMISSIONS
再退出测试账号的 shell,返回原管理员终端:
exit
同一个已成功使用的验证码不得再次登录。确认 ~/.google_authenticator 归该用户所有,且登录认证阶段能读取该用户家目录。未登记用户会被拒绝,不能用 nullok 放行。
3、备份再修改 PAM。 执行 sudo test -e "$BACKUP_DIR/sshd" || sudo cp -a /etc/pam.d/sshd "$BACKUP_DIR/" 和 sudo test -e "$BACKUP_DIR/sshd_config" || sudo cp -a /etc/ssh/sshd_config "$BACKUP_DIR/"。若实际还会修改 /etc/ssh/sshd_config.d/ 下的文件,先单独备份那些文件并记录路径。用 sudoedit /etc/pam.d/sshd,在第一条 auth 规则或 @include common-auth 之前加入:
auth required pam_google_authenticator.so
原有密码认证规则、account 与 session 规则保留。此位置使 PAM 先询问 TOTP,再询问密码;若测试机实际提示顺序不同,检查本机 PAM 栈,不要按预想继续上线。
4、修改 SSH 配置。 用 sudoedit /etc/ssh/sshd_config 调整配置;若配置由 Include 文件控制,修改实际生效的文件并备份它。最终对目标连接必须生效:
UsePAM yes
KbdInteractiveAuthentication yes
PasswordAuthentication no
AuthenticationMethods keyboard-interactive:pam
执行 sudo sshd -t,必须无报错;再执行:
sudo sshd -T | grep -E '^(usepam|kbdinteractiveauthentication|passwordauthentication|authenticationmethods) '
按字段名核对:usepam yes、kbdinteractiveauthentication yes、passwordauthentication no、authenticationmethods keyboard-interactive:pam;不依赖输出顺序。若有 Match,再用 sudo sshd -T -C user=pamcheck,host=客户端主机名,addr=客户端来源IP 核对测试账号的最终值;host 是客户端主机名,不能填服务器名。所有受影响账号的条件都要覆盖。配置不符就停下,不要重载。
5、重载并验收。 RHEL/Rocky Linux 执行 sudo systemctl reload sshd;Debian/Ubuntu 执行 sudo systemctl reload ssh。从另一终端运行:
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=keyboard-interactive pamcheck@server.example
应先输入 TOTP、再输入密码,且正确组合能登录。向导启用了同一验证码禁重用时,后续成功测试至少等到下一个 30 秒时间窗;速率限制生效时,按密钥文件中的限速窗口等待,不能连续复用同一验证码制造拒绝结果。分别测试错误 TOTP、错误密码、缺少 TOTP、未登记用户、重复使用已成功的 TOTP、只提供公钥或只使用 SSH 独立密码方法均被拒绝;每个负向用例后,用未使用的新验证码再次确认正常登录,避免把锁定或限速误判为策略生效。再用实际业务客户端测试正常用户。若第 5 节同时启用,测试后运行 sudo faillock --user pamcheck --reset,避免留下锁定计数。所有检查完成前保留原管理员会话。
回退:策略不符合预期或演练完成
从保留的会话执行
sudo cp -a "$BACKUP_DIR/sshd" /etc/pam.d/sshd、sudo cp -a "$BACKUP_DIR/sshd_config" /etc/ssh/sshd_config;若还改过sshd_config.d文件,也恢复其各自备份。运行sudo sshd -t,按发行版重载 SSH,再从新连接确认原登录方式恢复。清理本次测试造成的faillock计数。适用边界:
PasswordAuthentication no关闭的是 SSH 独立密码方法,PAM 仍能通过键盘交互询问密码。家目录在认证阶段不可读、已有多因素或复杂认证栈时,本例不适用。TOTP 错误未必计入密码失败次数。目标若是“公钥 + TOTP”,需要另一套AuthenticationMethods和 PAM 设计,不能使用本节配置。
7、故障排查顺序
1、先保住入口: 保留原管理员会话,必要时从控制台恢复本次修改文件;不要反复尝试会增加锁定计数的账号。
2、确认影响范围: sudo、su、passwd、sshd 各有入口;只测试发生故障的服务。sshd -t 通过仍可能是 PAM 认证失败。
3、查本机日志: 先用 journalctl 查目标服务;传统日志常见于 RHEL/Rocky Linux 的 /var/log/secure、Debian/Ubuntu 的 /var/log/auth.log。
4、回收测试状态: 恢复本次改动文件或 profile,清除测试账号的 faillock 计数;从新会话同时验证正常用户成功、应拒用户失败。
创建测试账号或登记向导中途失败时,先从保留会话核对账号和密钥状态;只清理本次创建、且没有其他消费者的产物,再重新开始。测试机上的 pamcheck 若确为本次新建且不再使用,确认其家目录没有其他数据后再执行 sudo userdel -r pamcheck;生产机上原本存在的测试账号不得按此命令删除。中止 TOTP 上线时,核对本次生成的密钥和恢复码是否还被其他服务使用,再按本单位密钥销毁流程处理。
交付条件:与生产同版本的测试机已经通过每节的正反测试,生产机有已验证的回退入口,备份路径和变更前后结果均已记录。缺少其中任一项时不要把高风险认证变更当作完成。
8、官方依据
以下固定版本资料用于核对配置含义;发行版回移补丁的实际行为以验收记录中的包版本和该包随附手册为准。
- Linux-PAM 1.7.1:pam_access:SSH 的
account栈、服务专用访问文件。 - Linux-PAM 1.7.1:pam_limits:按登录会话应用资源限制。
- Linux-PAM 1.7.1:pam_faillock:
preauth、authfail、authsucc的调用顺序及失败清零。 - libpwquality 1.4.5:PAM 模块:普通用户改密、负数 credit 和 root 行为。
- authselect 1.2.6:password-auth 模板:
with-faillock的认证、账号栈与生成入口。 - Google Authenticator PAM 1.11:模块接入、密钥归属、
nullok和认证会话。 - OpenSSH 9.9p2:sshd_config:
UsePAM、AuthenticationMethods、键盘交互与来源惩罚。 - Ubuntu 22.04:pam-auth-update(8):以
--remove撤销 profile,保留本地改动;该版本不支持--disable。
