T2、PAM 认证

作者: Brinnatt 分类: 小工具 发布时间: 2025-01-14 17:52

适用使用 Linux-PAM 的 RHEL/Rocky Linux、Debian 12/13、Ubuntu 22.04/24.04/26.04 LTS。本文按本地账号 + SSH给出可操作示例;密码质量一节针对本机改密。pamcheck 是测试账号,server.example 是示例主机名,执行前换成实际值。一次只选择一个场景,从该节的第一步做到回退验证或验收完成。

要做什么 看哪一节 影响范围
限制某个账号通过 SSH 登录 1 仅 SSH
调整 SSH 登录用户的文件句柄数 2 新 SSH 会话
设置新密码复杂度 3 经过本机 PAM 的改密操作
密码输错后锁定 4 RHEL/Rocky 的所选认证栈;Debian/Ubuntu 示例仅 SSH
SSH 密码加动态验证码 5 该主机的全部 SSH 登录;需先盘点所有登录方式

开始前:准备测试和回退

  1. 在与生产同发行版、同主版本、同认证方式的测试机先做一遍。测试机若还没有 pamcheck,先执行 getent passwd pamcheck;确认无输出后创建:

    sudo useradd -m -s /bin/bash pamcheck
    sudo passwd pamcheck

    生产机使用已纳管的测试账号,不要为了测试随意新增可远程登录账号。先确认它当前可以通过要测试的方式登录;下面的弱密码、错误密码测试都只对这个账号进行。

  2. 在生产操作时保留当前管理员 SSH 会话,确认带外控制台或虚拟机控制台可用;另开一个终端做新登录测试。不要先退出原会话。

  3. 在目标主机的当前管理员终端建立备份目录,记下打印的路径。每节会指出应备份的文件;每做一个场景建立一个新目录,避免同名备份被覆盖。

    cat /etc/os-release
    BACKUP_DIR=$(sudo mktemp -d /root/pam-backup.XXXXXXXX)
    printf 'BACKUP_DIR=%s\n' "$BACKUP_DIR"

    后续命令中的 $BACKUP_DIR 只在当前终端有效。换终端时先用上一步记录的实际路径重新设置它,例如 BACKUP_DIR=/root/pam-backup.实际目录名。

  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 栈,先核对实际入口;本文的默认栈示例不能直接套用。

1. 限制指定账号登录 SSH

  1. 核对并备份。 运行 sudo sshd -T | grep '^usepam ',必须看到 usepam yes;否则停止。再执行:

    sudo cp -a /etc/pam.d/sshd "$BACKUP_DIR/"
  2. 检查规则文件。 先运行 sudo ls -l /etc/security/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. 设置权限。

    sudo chown root:root /etc/security/sshd-access.conf
    sudo chmod 0644 /etc/security/sshd-access.conf
  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 规则读取。

2. 限制 SSH 会话的文件句柄数

  1. 核对入口。 先找 pam_limits.so,并沿 SSH 的 session include / @include 检查实际会话栈:

    grep -n pam_limits.so /etc/pam.d/sshd /etc/pam.d/common-session \
     /etc/pam.d/system-auth /etc/pam.d/password-auth 2>/dev/null

    若目标 SSH 会话栈里已有 pam_limits.so,不要重复添加。若没有,先执行 sudo cp -a /etc/pam.d/sshd "$BACKUP_DIR/",再用 sudoedit /etc/pam.d/sshd 在 session 规则中加入一次 session required pam_limits.so。

  2. 写入限制。 若 /etc/security/limits.d/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 单元中设置资源限制。

3. 设置新密码质量

  1. 核对并备份。 测试账号须使用本机改密路径。若策略文件已存在,先备份:

    if test -e /etc/security/pwquality.conf; then
     sudo cp -a /etc/security/pwquality.conf "$BACKUP_DIR/"
    fi

    Debian/Ubuntu 还要执行 sudo cp -a /etc/pam.d/common-password "$BACKUP_DIR/",因为安装模块可能改变这个共享文件。

  2. 确认模块接入,按发行版选一条路径。 RHEL/Rocky Linux 运行下面三条只读命令;最后一条应在实际改密栈里找到 pam_pwquality.so。找不到时停止:不能只写策略文件,也不能直接修改 authselect 生成文件。

    authselect current
    sudo authselect check
    grep -n pam_pwquality.so /etc/pam.d/system-auth /etc/pam.d/password-auth

    Debian/Ubuntu:先运行 grep -n pam_pwquality.so /etc/pam.d/common-password 记录当前状态。若已启用,不要重复启用;若未启用,先执行 sudo apt-get install libpam-pwquality,再执行 ls -l /usr/share/pam-configs/pwquality 确认 profile 存在。只有文件存在时才继续:

    sudo pam-auth-update --enable pwquality
    grep -nE 'pam_pwquality|pam_unix' /etc/pam.d/common-password

    应看到 pam_pwquality.so,并确认其后仍有负责保存密码的 pam_unix.so;检查失败先回退,不继续改策略文件。

  3. 设置策略,两类系统相同。 用 sudoedit /etc/security/pwquality.conf 设置本次示例策略;要求新密码至少 16 个字符,并至少各有一个大写字母、小写字母、数字和其他字符。已有其他策略项时只改目标项,不要写出重复且互相冲突的键:

    minlen = 16
    ucredit = -1
    lcredit = -1
    dcredit = -1
    ocredit = -1
  4. 验收。 先检查实际 pam_pwquality.so 行是否另带 minlen、ucredit 等参数;模块参数会覆盖文件中的同名项,应先解决冲突。以普通账号 pamcheck 登录后运行 passwd:输入当前密码后,不合规新密码应被拒绝,符合策略的新密码应成功。可以从管理员终端执行 sudo -iu pamcheck 进入该账号,再在其 shell 内运行 passwd;不能在 root 身份下用 sudo passwd pamcheck 代替。不要把密码写进命令行或日志。

回退。 若策略文件原本存在,执行 sudo cp -a "$BACKUP_DIR/pwquality.conf" /etc/security/pwquality.conf;原本没有时,只撤销自己写入的策略项,若文件由新装的软件包提供,不要删掉包管理的配置。Debian/Ubuntu 仅当本次才启用了 pwquality profile 时,执行 sudo pam-auth-update --disable pwquality;对照备份检查 common-password,再次以普通用户改密验证。

兼容: 这项策略约束新密码的修改,不追溯旧密码。root 修改他人密码的默认处理不同,不能用 sudo passwd pamcheck 替代普通用户自助改密测试。外部身份源可能不经过本机改密栈。

4. 密码失败后锁定账户

先选路径。 A 用于使用 authselect 的 RHEL/Rocky Linux;B 只用于本地账号、默认 common-auth、SSH 的 auth 段只有 @include common-auth 的 Debian/Ubuntu 主机。B 只限制 SSH 的 PAM 密码认证,不覆盖 su、控制台等服务。两条路径都会读取共享的 /etc/security/faillock.conf;如果已有其他服务使用 pam_faillock.so,改阈值也会影响它们,应先核对现行策略。

A. RHEL/Rocky Linux

  1. 记录 authselect current 的 profile 和已有 feature;authselect check 必须成功:

    authselect current
    sudo authselect check
  2. 仅当该 profile 支持且尚未启用 with-faillock 时,再执行:

    sudo authselect enable-feature with-faillock
    sudo authselect check
    grep -n pam_faillock.so /etc/pam.d/system-auth /etc/pam.d/password-auth

    后两条必须成功且能找到模块。检查失败时从保留的会话回退本次 feature,不要覆盖生成的 PAM 文件。若 with-faillock 原本已启用,只核对现有规则,不要把它当作本次新增功能。

B. Debian/Ubuntu 本地账号 SSH

  1. 确认 pamcheck 是本地账号:grep '^pamcheck:' /etc/passwd 必须有输出。在同版本测试机先核对:

    grep -nE '^(@include|auth[[:space:]])' /etc/pam.d/sshd
    grep -E '^[[:space:]]*auth[[:space:]]' /etc/pam.d/common-auth

    第一条必须显示 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. 先备份,再确认查询命令存在:

    sudo cp -a /etc/pam.d/sshd "$BACKUP_DIR/"
    command -v faillock

    若缺少 faillock 命令,安装 libpam-modules-bin 后再继续;再运行 find -L /lib /lib64 /usr/lib /usr/lib64 -name pam_faillock.so -print 2>/dev/null,确认模块文件存在。用 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 的后续改动,升级软件包或调整全局认证策略后必须复查。

两条路径共同操作

  1. 设置阈值。 先查看 /etc/security/faillock.conf 是否存在,并备份原有文件:

    if sudo test -e /etc/security/faillock.conf; then
     sudo cp -a /etc/security/faillock.conf "$BACKUP_DIR/"
    fi
    grep -R -n pam_faillock.so /etc/pam.d 2>/dev/null

    用 sudoedit /etc/security/faillock.conf 设置示例阈值;保留原文件中其他已生效的选项:

    deny = 5
    fail_interval = 900
    unlock_time = 600
  2. 验收。 从另一终端用 pamcheck 对目标服务输入错误密码。SSH 测试可用 ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password pamcheck@server.example;如果本机只启用了键盘交互密码认证,则把首选方法换成 keyboard-interactive。每次连接可能允许多次输入密码,按实际密码提示次数计数;用 sudo faillock --user pamcheck 核对失败记录。达到阈值后,即使输入正确密码也应被拒绝。另一正常账号必须仍可登录;最后清除测试计数,并用正确密码确认 pamcheck 恢复:

    sudo faillock --user pamcheck --reset
    sudo faillock --user pamcheck

回退。 A 路径若本次才启用 feature,执行 sudo authselect disable-feature with-faillock,再运行 sudo authselect check;B 路径执行 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 清除测试计数,并从新连接验证。若管理员已被锁定,从保留会话或控制台处理。

兼容: deny 是失败计数阈值,实际从哪次尝试开始拒绝取决于模块和栈顺序,以上正反测试不可省。SSH 公钥认证通常不计入密码失败次数。faillog 不查询 pam_faillock 的计数。

5. SSH 密码加 TOTP

本节影响该主机全部 SSH 登录,包括管理员和自动化任务。目标是 SSH 键盘交互中的 TOTP + 原有 PAM 密码;公钥单独登录将失效。先盘点所有使用 SSH 的账号、脚本与客户端,确保需要继续登录的人员均已领取自己的 TOTP 密钥,自动化任务已有替代接入方案。在测试机先完整演练,再安排生产切换。

  1. 检查现状。 运行 timedatectl status,确认系统时间同步。运行 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 命令,再用本机包管理器安装;不能假定基础仓库自带。运行 find -L /lib /lib64 /usr/lib /usr/lib64 -name pam_google_authenticator.so -print 2>/dev/null,必须找到模块文件。

    在测试机以测试账号本人身份登记;生产机上每个需要 SSH 登录的交互账号也要本人完成登记:

    sudo -iu pamcheck
    google-authenticator
    chmod 600 "$HOME/.google_authenticator"
    exit

    命令询问是否使用基于时间的验证码时选择 y,按提示在认证器中登记密钥并验证当前验证码,单独保管恢复码;确认 ~/.google_authenticator 归该用户所有,且登录认证阶段能读取该用户家目录。未登记用户会被拒绝,不能用 nullok 放行。

  3. 备份再修改 PAM。 执行 sudo cp -a /etc/pam.d/sshd /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) '

    四项应依次为 yes、yes、no、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、再输入密码,且正确组合能登录。分别测试错误 TOTP、错误密码、缺少 TOTP、只提供公钥均被拒绝;再用实际业务客户端测试正常用户。若第 4 节同时启用,测试后运行 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 设计,不能使用本节配置。

故障排查顺序

  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 上线时,核对本次生成的密钥和恢复码是否还被其他服务使用,再按本单位密钥销毁流程处理。

交付条件: 与生产同版本的测试机已经通过每节的正反测试,生产机有已验证的回退入口,备份路径和变更前后结果均已记录。缺少其中任一项时不要把高风险认证变更当作完成。

标签云