2494 字
约 8 分钟
14
S15-X14 · SSH 与免密登录的排查链路

免密登录失败时,ssh 只会给一句 Permission denied (publickey)。这行字把整条链路压成了一个结果,而链路上有五个环节各自都能产出同一句话:客户端没提供对的密钥、服务端没找到 authorized_keys、权限检查没通过、认证方式没协商上、密钥格式不被接受。判定只能靠 -vvv 的逐步输出。


1. 完整链路:五步

步骤 发生位置 关键日志 失败表现
1 协商算法 双方 peer server KEXINIT 算法不匹配,直接断开
2 选密钥 客户端 Offering public key 一个都没提供
3 试探认证 双方 Server accepts key 提供了但被拒
4 权限检查 服务端 Authentication refused: bad ownership or modes StrictModes 拒绝
5 认证完成 双方 Authentication succeeded 停在 Authentications that can continue

第 2 步由客户端决定,第 3、4 步由服务端决定。所以判断方向的第一条规则是:-vvv 里有没有 Offering public key——没有就是客户端的问题,有就是服务端的问题。


2. -vvv 里只看六行

$ ssh -vvv -p 22 root@TARGET 2>&1 | grep -E 'Offering|accepts|Authentications that can continue|Server host key|Trying private key|refused'
行 含义 下一步
Offering public key: ... ED25519 SHA256:xx 客户端选中并提供了这把 记下指纹,与 authorized_keys 比对
Server accepts key: ... 服务端认为这把可用 接着看是否走到 Authentication succeeded
Authentications that can continue: publickey 服务端允许的方式 说明公钥认证没被禁
Authentications that can continue: password 只允许密码 服务端关了 publickey
Authentication refused: bad ownership or modes for file ... 权限检查失败 按第 3 节修权限
no such identity: /path 客户端指定的私钥不存在 检查 -i 或 config

"提供了但被拒"与"根本没提供"是两条完全不同的排查路线,靠的就是这几行区分。只 -v 看不到 Offering public key 的指纹,必须 -vvv。


3. 权限:服务端的硬性检查

StrictModes(默认 yes)要求家目录、.ssh、authorized_keys 的属主与权限都在允许范围内。

对象 要求 不满足时
家目录 属主是本人,group/other 不可写 bad ownership or modes
~/.ssh 700,属主本人 拒绝读取
~/.ssh/authorized_keys 600,属主本人 拒绝读取
私钥 600 客户端 UNPROTECTED PRIVATE KEY FILE 警告并拒绝使用
$ chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys ~/.ssh/id_ed25519
$ chown -R "$USER":"$USER" ~ ~/.ssh
$ ls -ld ~ ~/.ssh ~/.ssh/authorized_keys

注意家目录权限是 755 或 750 都没问题,只要 group/other 不带写位。775 的家目录就足以让 StrictModes 拒绝——这是"root 能登、普通用户登不上"的常见原因。

SELinux 与 AppArmor 是第二层:SELinux 下文件必须带 ssh_home_t 上下文,从别处拷来的 authorized_keys 可能带错标签。

$ restorecon -Rv ~/.ssh
$ ls -Z ~/.ssh/authorized_keys

属主错在"用 root 建了目录再改属主"的场景里很常见:chown 只改到 .ssh 而漏了里面的文件,或反之。


4. 密钥格式:三种静默失败

问题 表现 处理
新格式私钥(BEGIN OPENSSH PRIVATE KEY)给旧客户端 旧 ssh 不认,报 invalid format 用 ssh-keygen -p -m PEM 转回 PEM
公钥末尾缺换行 authorized_keys 后一条被拼进来 echo >> file 补齐
复制时断行/加了注释 指纹不匹配,Offering 有但被拒 用 ssh-copy-id 或校验指纹
$ ssh-keygen -l -f ~/.ssh/id_ed25519.pub      # 本地公钥指纹
$ awk '{print $2}' ~/.ssh/authorized_keys | while read k; do echo "$k" | ssh-keygen -lf -; done

两侧指纹逐条比对,比肉眼读 base64 可靠得多。authorized_keys 里一行只能有一条公钥;把两条粘在一行(中间没换行)时,整行被当成一条无效记录,两把钥匙同时失效——这是最难发现的一类。


5. 多密钥与 ~/.ssh/config

客户端默认会依次尝试 ~/.ssh/id_rsa、id_ecdsa、id_ed25519 等。机器上有多个同名或用途不同的密钥时,容易"用了错的那把"。

Host blog
    HostName TARGET
    Port 22
    User root
    IdentityFile ~/.ssh/id_ed25519_blog
    IdentitiesOnly yes

Host * !blog
    ServerAliveInterval 30
    ServerAliveCountMax 3
指令 作用 不加会怎样
IdentityFile 指定用哪把私钥 按默认顺序盲试
IdentitiesOnly yes 只提供显式指定的密钥 agent 里的密钥也会被提供给服务端,可能触发失败计数
ServerAliveInterval 发心跳保活 中间设备静默断连,表现为卡住
AddKeysToAgent 首次使用后加入 agent 每次都要输口令

配置匹配是首个匹配胜出,通配符按出现顺序生效,不是"最具体的胜出"。Host * !blog 里的 ! 是排除,写在 Host 行里才有效。把 Host * 放在文件最上面会把后面的具体配置全部盖掉。


6. 服务端配置

$ sudo sshd -t && sudo sshd -T | grep -Ei 'pubkey|authorizedkeys|allowusers|password|strictmodes'
指令 默认 排查点
PubkeyAuthentication yes 关了则只剩密码认证
AuthorizedKeysFile .ssh/authorized_keys 改成绝对路径后不认家目录
AllowUsers / DenyUsers 空 名单里没这个用户,直接拒
PasswordAuthentication 视发行版 关掉后密钥失效就彻底登不上
StrictModes yes 权限检查的开关,不建议关

AllowUsers 与 DenyUsers 同时存在时,DenyUsers 优先。AllowUsers 支持 user@host 形式,写错主机段会让匹配全部失效。

sshd -T 打印的是实际生效的配置,比读 /etc/ssh/sshd_config 可靠:Include 引入的 sshd_config.d/*.conf 常常覆盖你在主文件里的修改,而且顺序靠前。

$ sudo sshd -t                       # 语法检查
$ sudo systemctl reload ssh          # 只重载,不断开现有连接

改配置前先保持一个已登录的会话,再用另一个窗口测试。systemctl reload 比 restart 安全,因为 reload 不会踢掉已有会话——这一点在改坏了认证配置时能救回服务器。


7. 跳板与端口转发

$ ssh -J root@BASTION root@TARGET
$ ssh -L 3306:127.0.0.1:3306 blog     # 本地转发:访问本机 3306 即到目标
$ ssh -N -f -L 8080:127.0.0.1:8080 blog
形式 方向 典型用途
-J / ProxyJump 多跳登录 经跳板机到内网
-L 本地 → 远端 访问内网数据库、调试端口
-R 远端 → 本地 把本机服务暴露给远端
-D SOCKS 代理 浏览器走隧道

-L 的转发目标是从远端主机的视角解析的,所以 127.0.0.1:3306 指跳板/目标机自己的回环。写成对端内网 IP 时,解析发生在远端。-N 表示不执行远程命令,-f 表示后台,两者常一起用。

跳板场景的经典故障是 ProxyJump 通了但目标机拒绝:跳板机能连,不代表跳板机到目标机的密钥也在 authorized_keys 里。要分别在两段上单独测。


8. 超时与保活

参数 端 作用
ConnectTimeout 客户端 建连超时,默认走系统
ServerAliveInterval / CountMax 客户端 空闲时发探测,超次数断开
ClientAliveInterval / CountMax 服务端 服务端主动探测并清理僵死会话
TCPKeepAlive 双方 TCP 层保活,默认开

"连上后几十秒没输出就断"通常是中间 NAT/防火墙清理了空闲连接,而两端都没开应用层保活。ServerAliveInterval 30 是最省事的一行配置。


9. 排错

现象 原因 处理
Permission denied (publickey),-vvv 无 Offering 客户端没找到私钥 检查 -i、IdentityFile、文件是否存在
有 Offering 但 Server accepts key 缺席 服务端没匹配到公钥 比对指纹,确认 AuthorizedKeysFile 路径
bad ownership or modes 家目录或 .ssh 权限过宽 chmod 700/600,确认属主
一个用户能登、另一个不能 家目录权限或 AllowUsers 分别看两个用户的家目录模式
改完 sshd_config 后所有连接失败 语法错误或配置被 sshd_config.d 覆盖 先 sshd -t/sshd -T,用 reload 不用 restart
authorized_keys 里两把钥匙都失效 两条粘成一行 每行一条,末尾补换行
旧客户端报 invalid format 私钥是 OpenSSH 新格式 ssh-keygen -p -m PEM 转换
用了 agent 里的错密钥 未设 IdentitiesOnly 显式 IdentityFile + IdentitiesOnly yes

10. 边界

场景 更合适的做法 理由
多台机器的密钥分发 配置管理工具下发 authorized_keys 手工 ssh-copy-id 不可审计
短期访问授权 短期证书(SSH CA) 静态公钥无法自动过期
需要审计每条命令 ForceCommand + 会话录制 原生日志只有登录记录
高安全要求环境 硬件密钥 + 禁密码 + 限制源 IP 单因子公钥仍可被复制
大量并发连接 连接复用 ControlMaster 每次握手开销可观

11. 自测

  1. -vvv 输出里没有 Offering public key,问题在客户端还是服务端?
  2. 家目录是 775,StrictModes yes 下会发生什么?
  3. authorized_keys 里两条公钥粘在同一行,为什么两把都失效?
  4. Host * 写在配置文件最上面会有什么后果?
  5. sshd -T 与直接读 sshd_config 的差别在哪?
  6. 改认证配置时,为什么优先 reload 而不是 restart?
S15-X14 · SSH 与免密登录的排查链路
https://www.clxhxhhr.top/posts/5871/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。