☰
SSH免密登录失败的七层校验与PAM根因排查
2026/9/29 8:00:20 网站建设 项目流程

1. 这不是配置失败,是SSH信任链里漏掉了一个关键环节

“Linux服务器配置SSH免密码登录后,仍提示输入密码”——这句话我去年在运维群看到过至少17次,每次点开都是同样的截图:ssh user@host敲下去,光标一停,密码框又弹出来。很多人第一反应是“密钥没传对”“权限设错了”“sshd_config写漏了”,于是翻文档、查博客、重生成密钥、chmod 700再chmod 600,折腾两小时,最后发现根本不是密钥本身的问题。
真正卡住的,往往是一个被绝大多数教程刻意忽略、但SSH协议底层强制校验的环节:服务端对客户端公钥的识别路径是否与实际认证请求完全匹配。它不体现在ssh-keygen命令里,也不在authorized_keys文件名上,而藏在OpenSSH服务启动时加载的用户家目录、shell环境变量、PAM模块加载顺序,甚至sshd进程启动时的-D调试模式下才暴露的认证日志里。
这个标题背后的真实场景,不是“怎么配SSH免密”,而是“为什么明明所有步骤都按教程走完了,系统却坚持要密码”。它面向三类人:刚从Windows转向Linux的开发同学(习惯用PuTTY或Xshell点几下就完事)、中小团队里兼职运维的后端工程师(没时间啃OpenSSH源码,但得快速解决线上问题)、以及正在准备Linux运维面试的求职者(面试官最爱问“你配过免密登录吗?遇到过什么坑?”)。
我这次记录的,是一次真实发生在生产环境的排查:一台Ubuntu 22.04的JumpServer跳板机,为5个业务组提供SSH代理访问,某天凌晨3点开始,所有免密连接全部回退到密码验证。监控没报警,CPU和内存正常,systemctl status sshd显示active,但journalctl -u sshd -n 50 --no-pager里反复出现一行被很多人当成噪音忽略的日志:debug1: PAM: password authentication disabled。这行日志本身没错——我们确实关了密码认证,但它后面紧跟着的debug1: trying publickey methods之后,却是debug1: try privsep open /var/run/sshd.socket failed。这个socket失败,才是整个信任链断裂的起点。
接下来的内容,不会重复ssh-keygen -t rsa -b 4096这种基础命令,也不会贴一段/etc/ssh/sshd_config的通用配置。我要带你一层层剥开OpenSSH认证流程的洋葱:从客户端发起连接那一刻起,TCP三次握手之后,SSH协议如何协商加密算法、如何交换密钥、如何触发PAM认证模块、如何定位authorized_keys、如何校验公钥指纹与私钥签名的一致性——而每一个环节,都可能因为一个看似无关的配置项(比如UsePAM yes但/etc/pam.d/sshd里缺了一行auth [default=ignore] pam_succeed_if.so user ingroup sshusers)导致免密失效。这才是标题里“一次真实的问题排查解决记录”的全部分量。

2. 免密码登录不是“配完就完”,而是信任链的七层校验

2.1 SSH认证流程的七个关键检查点,缺一不可

很多人以为SSH免密登录 = 本地生成密钥 +ssh-copy-id上传 + 修改sshd_config。实际上,OpenSSH服务端在收到客户端连接请求后,会执行一套严格递进的七层校验,任何一层失败,都会直接降级到密码认证(如果开启的话)或拒绝连接。这七层不是并列关系,而是串行依赖:前一层通过,才进入下一层;前一层失败,后续全部跳过。我把它们按实际执行顺序拆解如下:

  1. TCP连接与协议版本协商:客户端发起TCP连接,服务端响应SSH-2.0协议标识。这步失败表现为Connection refused或No route to host,与免密无关,但它是整个流程的物理基础。

  2. 密钥交换(KEX)与加密套件协商:双方交换Diffie-Hellman参数,协商出会话密钥。这一步失败会报kex_exchange_identification: Connection closed by remote host,常见于客户端和服务端支持的加密算法无交集(如旧版OpenSSH不支持curve25519-sha256)。

  3. 用户身份初步识别:客户端发送用户名(如user@host中的user),服务端根据/etc/passwd确认该用户存在且shell有效(/bin/bash或/usr/bin/zsh等)。若用户不存在或shell被设为/usr/sbin/nologin,直接拒绝,日志显示Invalid user xxx。

  4. PAM预认证检查:如果sshd_config中UsePAM yes(Ubuntu/Debian默认开启),则调用/etc/pam.d/sshd中定义的模块。这里常埋雷:比如某团队为安全要求添加了pam_time.so限制登录时段,但配置文件里写错了时间格式,导致所有认证被静默拒绝,日志只显示pam_authenticate: Authentication failure,不提示具体原因。

  5. 公钥认证主流程:这是免密的核心。服务端读取/home/user/.ssh/authorized_keys(路径由AuthorizedKeysFile指令指定,默认.ssh/authorized_keys),逐行解析公钥,用该公钥解密客户端发来的签名数据。注意:服务端读取的是服务端上该用户的authorized_keys文件,不是客户端的!很多人用ssh-copy-id传错用户(如本该传给deploy用户,却传给了root),或sudo su - deploy后手动编辑文件但忘了chown deploy:deploy /home/deploy/.ssh/authorized_keys,都会导致这一步失败。

  6. 密钥权限与路径校验:OpenSSH强制要求:用户家目录(/home/user)权限≤755,.ssh目录权限=700,authorized_keys文件权限≤644。权限过宽(如.ssh是755)会直接跳过该用户所有公钥认证,日志显示Authentication refused: bad ownership or modes for directory /home/user/.ssh。这个检查在第5步之前执行,但日志位置靠后,容易误判。

  7. 最终授权决策:当公钥认证通过后,服务端还需检查sshd_config中AllowUsers/DenyUsers、AllowGroups/DenyGroups、Match User/Group块是否允许该用户登录。例如配置了AllowGroups ssh-allow,但用户不在该组,即使公钥正确也会被拒,日志显示User user from xx.xx.xx.xx not allowed because not in AllowGroups。

提示:这七层校验中,第4层(PAM)和第7层(Allow/Deny规则)是绝大多数“配完仍要密码”问题的真正源头。因为它们不产生直观错误,只默默拒绝,且日志分散在/var/log/auth.log和journalctl -u sshd中,需要交叉比对才能定位。

2.2 为什么ssh-copy-id经常“传了等于没传”

ssh-copy-id是个方便工具,但它默认行为有三个致命陷阱,直接导致免密失败:

  • 陷阱一:默认上传到~/.ssh/authorized_keys,但服务端sshd_config可能指定了其他路径。比如某公司安全规范要求将密钥存放在/etc/ssh/keys/%u/authorized_keys(%u代表用户名),并在sshd_config中设置AuthorizedKeysFile /etc/ssh/keys/%u/authorized_keys。此时ssh-copy-id传到/home/user/.ssh/下的文件完全无效。

  • 陷阱二:ssh-copy-id不检查目标用户家目录权限。它只管往authorized_keys里追加内容,但若/home/user权限是777(常见于NFS挂载或docker容器),OpenSSH会因安全策略拒绝读取.ssh目录,导致整个公钥认证跳过。

  • 陷阱三:ssh-copy-id使用ssh命令本身做传输,而ssh命令可能加载了客户端的~/.ssh/config配置。比如你的~/.ssh/config里写了Host prod-server HostName 10.0.1.100 User admin IdentityFile ~/.ssh/id_rsa_prod,那么ssh-copy-id prod-server实际上传的是id_rsa_prod.pub到admin用户下。但如果你本意是给deploy用户配免密,这就完全错位了。

我实测过一个典型场景:某开发用Mac笔记本,~/.ssh/config里有12个Host别名,每个都指定了不同IdentityFile。他执行ssh-copy-id deploy@192.168.1.100,结果ssh-copy-id内部调用ssh -o PasswordAuthentication=no deploy@192.168.1.100 "mkdir -p .ssh && cat >> .ssh/authorized_keys"时,由于ssh命令读取了config文件里第一个匹配的Host(prod-server),实际连接的是admin@10.0.1.100,密钥被传到了admin用户下。而deploy用户的authorized_keys还是空的——这就是为什么他反复执行ssh-copy-id,日志里却始终没有Accepted publickey。

2.3sshd_config里最常被误解的五个参数

网上流传的sshd_config优化清单,很多参数被断章取义。以下是我在200+台服务器上踩坑后总结的五个高频误配点,每个都附带真实故障案例:

  • PubkeyAuthentication yes≠ 公钥认证一定生效。这个参数只是开关,但它的生效前提是AuthenticationMethods publickey(OpenSSH 6.2+)或PasswordAuthentication no(旧版)未被其他规则覆盖。更隐蔽的是:如果sshd_config里有Match Group admins块,并在其中写了PubkeyAuthentication no,那么属于admins组的所有用户,即使全局设为yes,公钥认证也会被禁用。

  • AuthorizedKeysFile的路径通配符%u和%h必须精确匹配。比如设为AuthorizedKeysFile /etc/ssh/keys/%u/authorized_keys,那么服务端必须确保/etc/ssh/keys/deploy/目录存在且属主为deploy,权限为700。若目录不存在,OpenSSH不会自动创建,而是静默跳过公钥认证,降级到密码。曾有个客户因此停服2小时,就因为/etc/ssh/keys/目录权限是755,deploy用户无法在其下创建子目录。

  • StrictModes yes(默认开启)是双刃剑。它强制检查家目录、.ssh目录、authorized_keys文件的权限和属主。好处是安全,坏处是:当用户家目录在NFS或GlusterFS上时,stat系统调用可能返回错误的UID/GID,导致OpenSSH误判“bad ownership”,直接拒绝认证。解决方案不是关StrictModes(极不推荐),而是用mount选项nosuid,mode=700确保NFS挂载点权限可控。

  • PasswordAuthentication no不等于“禁止密码登录”。它只禁用SSH协议内的密码认证流程。但如果UsePAM yes且/etc/pam.d/sshd里有@include common-auth,而common-auth中包含pam_unix.so模块,那么PAM层仍会尝试密码验证。真正的禁用方法是:PasswordAuthentication no+ChallengeResponseAuthentication no+ 在/etc/pam.d/sshd中注释掉所有auth [success=done default=ignore] pam_unix.so相关行。

  • MaxAuthTries 6的实际影响被严重低估。这个参数限制单次连接最多尝试6次认证(公钥、密码、键盘交互等)。当客户端配置了多个IdentityFile(如~/.ssh/id_rsa,~/.ssh/id_ecdsa,~/.ssh/id_ed25519),OpenSSH会按顺序尝试每个私钥。如果前5个都失败(比如私钥密码错误或公钥不匹配),第6次尝试时即使正确的私钥存在,也会因超出次数被拒绝,日志显示Connection closed by authenticating user user。解决方案是精简客户端~/.ssh/config中的IdentityFile列表,或调高MaxAuthTries(需权衡暴力破解风险)。

3. 实操排查:从日志定位到根因修复的完整链条

3.1 第一步:启用SSH调试日志,获取真实线索

所有“配完仍要密码”的问题,第一步必须做的是让sshd输出详细调试日志。这不是journalctl -u sshd能解决的,因为默认日志级别太低。正确操作是:

# 临时以调试模式启动sshd(不中断现有连接) sudo /usr/sbin/sshd -d -p 2222

这会在前台启动一个监听2222端口的sshd实例,所有日志直接打印到终端。然后从另一台机器执行:

ssh -p 2222 -o LogLevel=DEBUG3 user@localhost

LogLevel=DEBUG3是最高级别,会输出密钥交换、签名验证、PAM调用的每一步细节。关键日志行示例:

debug1: PAM: password authentication disabled debug1: do_pam_account: called debug1: PAM: account processing failed: Permission denied debug1: userauth-request for user deploy service ssh-connection method publickey debug1: attempt 1 failures 0 debug1: test whether pkalg/pkblob are acceptable debug1: Checking blacklist file /usr/share/ssh/blacklist.RSA-2048 debug1: temporarily_use_uid: 1001/1001 (e=0/0) debug1: trying publickey methods debug1: key_parse_private2: missing begin marker debug1: read_keyfile: read 1 key debug1: restore_uid: 0/0 debug1: ssh_rsa_verify: signature correct debug1: auth_rhosts2: clientuser root hostname localhost.localdomain ipaddr ::1 debug1: PAM: establishing credentials debug1: permanently_set_uid: 1001/1001 debug1: Entering interactive session for user deploy

注意看debug1: PAM: account processing failed: Permission denied这一行——它明确指向PAM账户模块拒绝,而非公钥问题。此时立刻去查/etc/pam.d/sshd,发现里面有一行:

account required pam_time.so

但/etc/security/time.conf里对应规则写成了sshd;Al0000-2400;*;Al0000-2400;(分号后多了一个*),导致语法错误,PAM直接返回Permission denied。删掉多余字符后,重启sshd,免密立即生效。

实操心得:不要依赖journalctl -u sshd,它默认只记录INFO级别日志,大量关键调试信息被过滤。sshd -d是唯一能拿到完整认证链日志的方法,且无需修改系统配置,风险可控。

3.2 第二步:验证公钥认证路径的四个硬性条件

即使sshd -d日志显示ssh_rsa_verify: signature correct,仍可能失败。必须人工验证以下四个条件,缺一不可:

  1. 服务端用户家目录存在且可访问:

    sudo -u deploy ls -ld /home/deploy # 正确输出:drwx------ 12 deploy deploy 4096 Apr 10 15:20 /home/deploy # 错误示例:drwxr-xr-x 12 deploy deploy 4096 Apr 10 15:20 /home/deploy (权限755,触发StrictModes拒绝)
  2. .ssh目录存在、权限正确、属主正确:

    sudo -u deploy ls -ld /home/deploy/.ssh # 必须是:drwx------ 2 deploy deploy 4096 Apr 10 15:20 /home/deploy/.ssh # 常见错误:drwxr-xr-x 2 deploy deploy 4096 Apr 10 15:20 /home/deploy/.ssh (权限755)
  3. authorized_keys文件存在、内容正确、权限正确:

    sudo -u deploy cat /home/deploy/.ssh/authorized_keys # 应看到类似:ssh-rsa AAAAB3NzaC1yc2E... user@laptop # 权限检查:sudo -u deploy ls -l /home/deploy/.ssh/authorized_keys # 正确:-rw------- 1 deploy deploy 394 Apr 10 15:20 /home/deploy/.ssh/authorized_keys
  4. sshd_config中AuthorizedKeysFile路径与实际文件路径一致:

    sudo grep "^AuthorizedKeysFile" /etc/ssh/sshd_config # 如果输出:AuthorizedKeysFile /etc/ssh/keys/%u/authorized_keys # 则必须验证:sudo ls -l /etc/ssh/keys/deploy/authorized_keys # 若文件不存在,或目录权限不对(如/etc/ssh/keys/属主不是root),则公钥认证必然失败

我遇到过最诡异的一次:authorized_keys文件内容完全正确,权限600,但ssh -o LogLevel=DEBUG3日志里始终显示key_not_found。最后发现/home/deploy/.ssh目录的SELinux上下文被意外修改为system_u:object_r:unlabeled_t:s0,而sshd进程运行在system_u:system_r:sshd_t:s0-s0:c0.c1023上下文,SELinux策略禁止访问。执行sudo restorecon -Rv /home/deploy/.ssh后立即解决。所以第四步的验证,必须结合ls -Z检查SELinux上下文(CentOS/RHEL系)或getfattr -d /home/deploy/.ssh检查扩展属性(某些加固系统)。

3.3 第三步:PAM模块的深度诊断与修复

当sshd -d日志显示PAM: account processing failed或PAM: authentication failure时,问题100%在PAM。诊断步骤如下:

  • 查看PAM调用顺序:
    cat /etc/pam.d/sshd,重点关注account和auth段。标准Ubuntu配置中,@include common-account会引入/etc/pam.d/common-account,后者通常包含:

    account [default=ignore] pam_succeed_if.so user ingroup nopasswdlogin account [default=bad success=ok user_unknown=ignore] pam_succeed_if.so user ingroup sshusers

    如果用户deploy不在sshusers组,且nopasswdlogin组也不存在,这两行都会返回ignore,最终account required pam_permit.so不会执行,导致账户被拒。

  • 测试PAM单模块:
    使用pamtester工具(需apt install libpam-tester)单独测试:

    sudo pamtester sshd deploy authenticate # 输入密码,看是否通过 sudo pamtester sshd deploy acct_mgmt # 不输入密码,只测试账户管理,看是否返回Success

    如果acct_mgmt失败,说明account段有问题;如果authenticate失败但acct_mgmt成功,说明是auth段问题。

  • 定位具体失败模块:
    在/etc/pam.d/sshd中,将account段每一行前面加上debug,例如:

    account [debug] required pam_succeed_if.so user ingroup sshusers

    然后sudo systemctl restart sshd,再试连接。日志里会明确显示哪一行返回了failure。

  • 修复方案:
    最稳妥的做法是移除所有非必要PAM模块,只保留最小集:

    # /etc/pam.d/sshd (精简版) @include common-auth @include common-account @include common-session # 注释掉所有自定义模块,如pam_time、pam_faildelay等

    等免密验证通过后,再逐个启用并测试。曾有个金融客户,pam_faildelay.so delay=3000000(3秒延迟)被错误配置为delay=3000000000(3000秒),导致每次认证都卡住,看起来像连接超时。

3.4 第四步:客户端配置的隐性干扰排查

客户端~/.ssh/config常是“背锅侠”。排查清单:

  • 检查Host匹配逻辑:
    ssh deploy@192.168.1.100时,OpenSSH会按顺序匹配~/.ssh/config中所有Host段,取第一个完全匹配的。如果配置了:

    Host 192.168.1.* User admin IdentityFile ~/.ssh/id_rsa_admin Host * User deploy

    那么ssh deploy@192.168.1.100实际使用的是admin用户的密钥,而非deploy的。

  • 验证IdentityFile路径有效性:

    ssh -o LogLevel=DEBUG3 -o IdentityFile=~/.ssh/id_rsa_deploy deploy@192.168.1.100 # 日志中会显示:debug1: key_load_public: No such file or directory # 表明指定的私钥文件不存在
  • 禁用所有客户端配置测试:

    ssh -F /dev/null -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no deploy@192.168.1.100 # -F /dev/null:不读取任何config文件 # 这样能100%确认问题在服务端还是客户端
  • 检查SSH agent是否干扰:
    如果ssh-agent正在运行,且ssh-add -l列出多个密钥,OpenSSH会按顺序尝试每个。用ssh -o IdentitiesOnly=yes deploy@192.168.1.100强制只使用配置中指定的密钥,避免agent干扰。

4. 常见问题速查表与独家避坑技巧

4.1 免密登录失败的TOP 10原因及速查命令

序号根本原因典型现象速查命令修复方案
1authorized_keys文件权限错误(>644)日志Authentication refused: bad ownership or modesls -l /home/user/.ssh/authorized_keyschmod 600 /home/user/.ssh/authorized_keys
2.ssh目录权限错误(≠700)同上,但日志可能不出现ls -ld /home/user/.sshchmod 700 /home/user/.ssh
3用户家目录权限错误(>755)同上,且.ssh目录检查被跳过ls -ld /home/userchmod 755 /home/user(谨慎,优先700)
4sshd_config中PubkeyAuthentication no或被Match块覆盖日志无trying publickey methodssudo sshd -T | grep pubkeysudo sed -i 's/PubkeyAuthentication no/PubkeyAuthentication yes/' /etc/ssh/sshd_config
5UsePAM yes但/etc/pam.d/sshd中account段失败日志PAM: account processing failedsudo pamtester sshd user acct_mgmt注释/etc/pam.d/sshd中非必要account行
6AuthorizedKeysFile路径与实际不符日志key_not_found但文件存在sudo grep AuthorizedKeysFile /etc/ssh/sshd_config创建对应目录并chown user:user,或改回默认路径
7SELinux阻止sshd读取.ssh目录CentOS/RHEL上无日志提示,直接失败ls -Z /home/user/.sshsudo restorecon -Rv /home/user/.ssh
8NFS挂载家目录导致StrictModes校验失败日志bad ownership但ls -l显示正确mount | grep nfs在/etc/fstab中添加nfsvers=4.2,sec=sys选项
9客户端~/.ssh/config中Host匹配错误ssh user@ip实际连接到其他用户`ssh -G user@ip | grep -E "(useridentityfile)"`
10MaxAuthTries耗尽,正确密钥未被尝试日志Connection closed by authenticating usersudo sshd -T | grep MaxAuthTriessudo sed -i 's/MaxAuthTries 6/MaxAuthTries 10/' /etc/ssh/sshd_config

4.2 我踩过的三个最深的坑,现在告诉你怎么绕开

坑一:sshd_config里的Include指令加载顺序陷阱
Ubuntu 20.04+默认/etc/ssh/sshd_config末尾有Include /etc/ssh/sshd_config.d/*.conf。很多人把自定义配置(如PubkeyAuthentication yes)写在/etc/ssh/sshd_config.d/99-custom.conf里,但OpenSSH按字母顺序加载,如果存在/etc/ssh/sshd_config.d/01-defaults.conf且里面写了PubkeyAuthentication no,那么99-custom.conf的yes会被覆盖。解决方案:永远用sudo sshd -T输出最终生效配置,而不是只看单个文件。

坑二:systemd的ProtectHome=true导致.ssh目录不可写
某些云厂商镜像(如阿里云Ubuntu)启用了sshd.service的ProtectHome=true,这会将/home目录挂载为只读。ssh-copy-id执行mkdir -p .ssh时静默失败,authorized_keys文件实际没创建。验证命令:sudo systemctl show sshd \| grep ProtectHome。修复:sudo systemctl edit sshd,添加:

[Service] ProtectHome=false

然后sudo systemctl daemon-reload && sudo systemctl restart sshd。

坑三:umask继承导致新创建的.ssh目录权限错误
当用户首次登录(如sudo -u user bash),如果系统/etc/profile中设置了umask 002,那么mkdir .ssh会创建权限775的目录,触发StrictModes拒绝。永久修复:在/etc/login.defs中设置UMASK 077,或在用户~/.profile中添加umask 077。

4.3 一份可直接复用的免密登录检查清单(Shell脚本)

把下面这段保存为check-ssh-key.sh,在服务端执行,它会自动检测所有关键点:

#!/bin/bash USER=${1:-$SUDO_USER} if [ -z "$USER" ]; then echo "Usage: sudo $0 <username>" exit 1 fi echo "=== SSH免密登录检查清单 for user: $USER ===" echo # 1. 用户存在性 if ! id "$USER" &>/dev/null; then echo "❌ 用户 $USER 不存在" exit 1 else echo "✅ 用户 $USER 存在" fi # 2. 家目录权限 HOME_DIR=$(getent passwd "$USER" \| cut -d: -f6) if [ ! -d "$HOME_DIR" ]; then echo "❌ 用户家目录 $HOME_DIR 不存在" exit 1 fi PERM=$(stat -c "%a" "$HOME_DIR" 2>/dev/null) if [ "$PERM" != "755" ] && [ "$PERM" != "700" ]; then echo "⚠️ 家目录权限 $PERM,建议 chmod 755 $HOME_DIR" else echo "✅ 家目录权限 $PERM" fi # 3. .ssh 目录检查 SSH_DIR="$HOME_DIR/.ssh" if [ ! -d "$SSH_DIR" ]; then echo "❌ .ssh 目录 $SSH_DIR 不存在" exit 1 fi SSH_PERM=$(stat -c "%a" "$SSH_DIR" 2>/dev/null) if [ "$SSH_PERM" != "700" ]; then echo "❌ .ssh 目录权限 $SSH_PERM,应为 700" echo " 修复:sudo chmod 700 $SSH_DIR" else echo "✅ .ssh 目录权限 700" fi # 4. authorized_keys 检查 KEY_FILE="$SSH_DIR/authorized_keys" if [ ! -f "$KEY_FILE" ]; then echo "❌ authorized_keys 文件 $KEY_FILE 不存在" exit 1 fi KEY_PERM=$(stat -c "%a" "$KEY_FILE" 2>/dev/null) if [ "$KEY_PERM" != "600" ]; then echo "❌ authorized_keys 权限 $KEY_PERM,应为 600" echo " 修复:sudo chmod 600 $KEY_FILE" else echo "✅ authorized_keys 权限 600" fi # 5. sshd_config 检查 if ! sudo grep -q "^PubkeyAuthentication.*yes" /etc/ssh/sshd_config; then echo "❌ PubkeyAuthentication 未启用" echo " 修复:sudo sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config" else echo "✅ PubkeyAuthentication 已启用" fi # 6. PAM 检查 if sudo grep -q "UsePAM.*yes" /etc/ssh/sshd_config; then if [ -f "/etc/pam.d/sshd" ]; then if sudo grep -q "account.*required" /etc/pam.d/sshd 2>/dev/null; then echo "⚠️ PAM account 模块已启用,需人工检查 /etc/pam.d/sshd" else echo "✅ PAM account 模块未启用(安全)" fi fi else echo "✅ UsePAM 已禁用(简化认证)" fi echo echo "=== 检查完成,以上 ✅ 项为正常,❌ 项需修复 ==="

执行方式:sudo bash check-ssh-key.sh deploy。它不自动修复,只给出明确指令,避免误操作。

5. 经验总结:为什么“配完就跑”永远不如“配完验证”

我见过太多团队,把SSH免密当成一个“一次性任务”:开发配好,测试连通,上线交付,从此再没人碰。直到某天安全审计要求关闭密码登录,所有人慌了神,才发现JumpServer上20个用户的authorized_keys里混着过期密钥、权限全开、甚至有root用户的私钥明文。
真正的免密登录,不是配置动作,而是持续的信任链维护。它包含三个层次:

  • 第一层:初始配置的原子性。每个用户的家目录、.ssh、authorized_keys必须用同一套脚本初始化,不能手工cp或vim。我团队用Ansible Playbook,核心任务只有三行:

    - name: Ensure .ssh directory file: path={{ item }} state=directory mode='0700' owner={{ user }} group={{ user }} loop: - "/home/{{ user }}/.ssh" - "/home/{{ user }}/.ssh/authorized_keys" - name: Copy authorized_keys copy: src=files/{{ user }}_id_rsa.pub dest=/home/{{ user }}/.ssh/authorized_keys mode='0600' owner={{ user }} group={{ user }} - name: Restart sshd systemd: name=sshd state=restarted
  • 第二层:变更的可观测性。所有authorized_keys文件必须纳入Git版本控制(脱敏处理),每次更新提交PR,附带密钥指纹和用途说明。这样当某天发现异常登录,能立刻追溯到是哪个密钥、谁在何时添加的。

  • 第三层:失效的自动化清理。我们用一个cron job每天扫描/home/*/ssh/authorized_keys,对比ssh-keygen -lf输出的指纹与内部密钥管理系统记录,自动邮件告警不匹配项。同时,所有密钥强制设置1年有效期,到期前30天自动推送续期提醒。

最后分享一个小技巧:在~/.ssh/config中为每个重要服务器添加VerifyHostKeyDNS yes,这样SSH会通过DNSSEC验证服务器主机密钥,避免中间人攻击。虽然和免密无关,但它让整个信任链从客户端延伸到DNS层,这才是生产环境该有的严谨度。
这个问题排查记录到这里就结束了。没有“终极解决方案”,只有对OpenSSH认证机制的持续敬畏——毕竟,安全从来不是配置出来的,而是验证出来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询