ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SSH密钥过期?解密认证链路排查与配置实践

SSH密钥过期?解密认证链路排查与配置实践 你的SSH密钥可能已经「过期」了搪长假回来第一件事就是连服务器结果Permission denied (publickey)直接糊脸。多数人的第一反应是“密钥过期了”然后把~/.ssh翻了个底朝天重新生成密钥、重新上传公钥折腾半小时问题照旧。我甚至见过有人在群里问“macOS 升级之后 SSH 密钥是不是会失效”底下还有人说“会重装系统必须重新生成”。这里先泼一盆冷水裸的 SSH 私钥文件本身几乎不存在“有效期”的概念。用ed25519或rsa生成出来的私钥只要文件没坏、权限没错、密码短语没忘它十年之后还是同一把钥匙。真正会“过期”的是整条公钥认证链路里某个环节失效了——服务端authorized_keys被清理、客户端身份文件没有被正确选用、known_hosts变化、ssh-agent缓存失效、甚至远程主机换了新算法。绝大多数“密钥过期”的报错背后都是这类问题。这篇文章不打算给你堆一堆概念而是把这些年被“密钥过期”坑过的场景拆开揉碎从排查思路到实操配置再到 Git 平台、VSCode Remote、批量运维这类常见环境下的版本一次性梳理清楚。不管你是刚上手 Linux 的小白还是每天要跨几十台机器的运维这里面的坑和对应的解法大概率你都遇得到。1. “密钥过期”这个说法藏着几个容易被混淆的场景1.1 私钥文件本身几乎不会过期真正会失效的是“认证链路”先理清楚 SSH 公钥认证到底是怎么工作的。你在客户端持有的~/.ssh/id_ed25519是私钥它不会主动过期。真正决定你能不能登录的是服务端~/.ssh/authorized_keys里是否还存着你对应的公钥。整个认证链路可以简化成四段客户端本地持有私钥连接时客户端向服务端声明“我这里有私钥请验证”服务端在authorized_keys里查找对应的公钥找到后服务端用一个随机挑战值让客户端用私钥签名验证通过即放行。这四段里任何一环出问题最终报错都是同一个味道Permission denied (publickey)。但服务端在拒绝时并不会告诉你“你的公钥记录被删了”还是“你没提供任何可用的密钥”。所以排查时必须自己一步步确认。最常见的“假过期”场景是服务端改版或运维统一重置了authorized_keys某个用户的公钥被清理了或者你换了电脑、重装了系统生成的是一对新密钥而服务端还存着旧公钥。两边信息不对称就产生了“密钥过期了”的错觉。1.2 有“有效期”概念的几种特殊情况要说“确实会过期”的场景也不是完全没有只是它们通常不是靠手动生成的那对裸密钥。第一种是SSH 证书。如果你用ssh-keygen -s给用户签发过证书那么证书本身可以携带有效期。签发时可以指定-V -1d:30d之类的有效期范围到期后即使authorized_keys里配置了对应的 CA 公钥客户端持有的证书也会被拒绝。这在大型集群里很常见管理员用 CA 集中签名到期自动轮换。比如ssh-keygen -s ca_key -I user_identity -n username -V -1d:30d user.pub这种情况下报错的字眼里往往带着Certificate invalid: expired一眼就能看出是证书过期和普通密钥失效不是一回事。第二种是FIDO/U2F 硬件密钥生成的 discoverable credential。用ssh-keygen -t ed25519-sk生成的密钥如果依赖硬件里的 PIN 或生物识别某些场景下硬件固件升级或凭据被重置客户端也会报无法使用密钥。这算硬件层面的“状态变动”。第三种是ssh-agent 里的会话时效。你把带密码短语的私钥ssh-add进去了之后一直可以免密登录。但如果重启了电脑、重启了 ssh-agent 服务或者会话超时被清理你会觉得“没动任何配置怎么就登录不了了”。这是 agent 的缓存没了不是密钥失效。很多人在这个点上栽过跟头。1.3 各种报错文案对应的真实故障类型我自己排查过不少“密钥过期”类工单先看报错文案基本能判断方向。整理一张表给你省得走弯路。报错文案片段更可能的真实原因排查方向Permission denied (publickey)服务端没有匹配到公钥或客户端没提供可用身份检查authorized_keys、IdentityFile选择Load key ... incorrect permissions私钥文件权限过宽chmod 600Host key verification failedknown_hosts里记录的主机指纹变了核对指纹或移除旧记录no matching key exchange method服务端算法太老客户端默认禁用显式指定KexAlgorithmsConnection reset by ... port 22网络中间设备干预、IP 被复用或 sshd 异常抓包或看服务端日志Certificate invalid: expiredSSH 证书过期重新签发This host key is already known且明显不匹配有人篡改或换了密钥联系管理员确认指纹这张表不是标准答案但能帮你把“感觉是密钥问题”和“实际是什么问题”之间拉开一点距离后面排查就有方向了。2. 实际案例Ubuntu 连不上服务器我按这个顺序排查到底2.1 第一步还原客户端的完整日志别一上来就盲改配置先看客户端日志。连接时加上-vvv参数ssh -vvv useryour-server输出里重点看几段identity file、Offering public key、Authentications that can continue。举一个典型的失败片段debug1: identity file /home/user/.ssh/id_ed25519 type 0 debug1: Next authentication method: publickey debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:xxx debug1: Authentications that can continue: publickey debug1: Trying private key: /home/user/.ssh/id_rsa如果日志显示“Offering public key”之后立刻回到“Authentications that can continue”说明服务端明确返回了“这个公钥我不认”。此时基本可以判定问题在服务端的authorized_keys而不是客户端。反过来说如果日志里压根没有“Offering public key”这一行而是跳到“Trying private key”说明客户端认为自己没有可用的身份文件或者被IdentitiesOnly/IdentityFile配置限制了。先在本地把这两件事分开。2.2 第二步看服务端 auth.log 里对应的拒绝原因客户端日志只能告诉你“被拒了”具体为什么拒要看服务端。Ubuntu 上打开sudo tail -n 50 /var/log/auth.log收到权限拒绝时记录大概长这样sshd[12345]: Failed publickey for user from 192.168.1.10 port 54321 ssh2: ED25519 SHA256:xxx sshd[12345]: Connection closed by authenticating user 192.168.1.10 port 54321 [preauth]如果连Failed publickey这行都没有只是纯粹的连接被重置那就更像网络层因素。如果出现了Failed publickey下一步就要确认authorized_keys文件里的内容到底对不对。2.3 第三步验证 authorized_keys 的格式和权限这是能卡住绝大多数人的一步。检查三件事文件权限、内容格式、路径是否正确。# 在服务端执行 ls -la ~/.ssh/ chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys常见的坑有.ssh目录权限是755authorized_keys是644sshd 会直接拒绝读取还有的同事把公钥粘贴时合并成了一行或者中间塞了换行符导致整条记录无效。再确认服务端到底读的是哪个文件sudo sshd -T | grep authorizedkeysfile默认是.ssh/authorized_keys但有的人改过AuthorizedKeysFile或者是用sshd_config里的AuthorizedKeysCommand拉取动态公钥这种情况下静态文件怎么改都没用。2.4 一个典型的误判客户端换了新钥匙服务端还是旧记录一次真实的排查某同事换了办公电脑把旧电脑的私钥拷了过来公钥也跟着拷了过来但两边authorized_keys对不上。折腾半天最后发现旧电脑的私钥其实还在只是他新拷的私钥是另一台机器的。更离谱的情况是他把公钥贴到了自己的authorized_keys里但服务端配置了AuthorizedKeysCommand从某个 CMDB 拉取导致他自己本地的修改根本不生效。所以排查优先顺序建议是先确认客户端选用的是哪把钥匙-vvv再到服务端确认读的是哪个文件最后才怀疑“真的过期了”。3. VSCode Remote SSH 反复掉线问题多半不在密钥本身上3.1 Remote-SSH 的“身份传递”机制很多人用 VSCode Remote-SSH 连远程开发机某天突然提示“此扩展在此工作区中被禁用因为其被定义为在远程扩展主机中运行”第一反应是“我的密钥坏了”。其实这个提示说的是 VSCode 的扩展机制你装的某个扩展只支持在远程主机上跑不支持在当前工作区加载和 SSH 密钥没有直接关系。它只是在你连接出问题时被一起带出来的“次级症状”。Remote-SSH 的工作方式是先建立一条 SSH 连接然后在远端下载并启动一个 VSCode Server。远程 Server 若需要访问你的内网 Git、内网平台往往需要把本地的 SSH 身份转发到远端。如果你在 VSCode 里配置了ForwardAgent yes或者通过ssh -A连接远程就可以利用本机的 agent 里的密钥。一旦 agent 没生效远程 Server 去拉私有仓库就会报权限错误表现很像“密钥失效”。3.2 让身份转发稳定生效的配置方式在~/.ssh/config里给远程主机加上Host myserver HostName 192.168.1.100 User yourname ForwardAgent yes ServerAliveInterval 60 ServerAliveCountMax 3ForwardAgent yes开启之后VSCode 里的集成终端如果还要访问内网 Git也能直接使用本机的 agent非常方便。需要强调的是ForwardAgent是有安全边界的远端一旦被攻破攻击者可以用你的 agent 去访问你当前所有已加载的密钥对应的服务。如果只是跑普通开发环境问题不大但生产跳板机建议按需关闭。如果远端没有你的“本机 agent”你还可以把私钥放到远端但这一步会引入私钥拷贝的风险我一般不建议除非是专门的部署账号。3.3 反复掉线的高发原因保活、IP 复用和 HostKey 变化Remote-SSH 更常见的“密钥过期”场景其实是这三种。第一种是长连接被中间设备掐断。办公网、云平台安全组都可能空闲端口超时回收表现就是挂着挂着就断。配置ServerAliveInterval可以定期发心跳避免被当成死连接。第二种是IP 被复用导致 HostKey 冲突。公司 DHCP 池如果紧张原来机器的 IP 分给了新机器连过去会报 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! 这是known_hosts里的旧指纹和新机器对不上不是密钥失效。确认新机器没问题后用ssh-keygen -R 目标IP删掉旧记录再连即可。第三种是定时任务覆盖了 authorized_keys。有些公司的安全系统会定期把各服务器的authorized_keys刷新成统一状态你手动加进去的临时公钥会被清掉。这属于“管理策略导致密钥被移除”本质上也是授权链路断掉。遇到这种情况别硬刚了走正式的密钥申请流程才是正路。3.4 如果连的是交换机或路由器这类网络设备再补一个很容易被误会成“密钥过期”的场景连接华为、中兴或某些老旧的交换机、路由器时SSH 报错经常会带no matching key exchange method或no matching cipher。这不是认证失败而是加密算法协商失败——新版本 OpenSSH 默认关闭了旧式算法而设备固件还停留在老协议。临时连接可以用ssh -oKexAlgorithmsdiffie-hellman-group14-sha1 userswitch但要注意这类老算法存在安全风险只能用于临时排查有条件还是升级设备固件或者改用带证书的现代登录方案。4. Git 平台和批量登录场景下的密钥轮换策略4.1 GitHub / GitLab / Gitea 上的密钥为什么会“突然失效”不少人遇到过昨天git push还好好的今天直接Permission denied (publickey)。去平台后台一看密钥列表里少了一把。平台通常不会无故删除你的公钥但触发条件很实在平台风控检测到某把私钥在多台机器上高频使用判定为疑似已泄露自动禁用你把同一把私钥复制到多台设备某台设备的私钥文件权限不对被监控策略扫描到公司 GitLab 的管理员定期清理长期未使用或超期未轮换的部署密钥Deploy Key。更隐蔽的一种情况是你在仓库里配置了 Deploy Key后来仓库迁移新的仓库没有关联原来的 Deploy Key自动构建脚本里还带着旧密钥于是git clone直接 403。这类问题不会在密钥本身报“过期”但只要构建一跑就暴露。4.2 尽早给部署用户使用专用密钥并分离权限Git 平台上的登录密钥和部署密钥一定要分开。个人终端上用个人密钥CI/CD 构建机上用单独的 Deploy Key而且施加最小权限ssh-keygen -t ed25519 -C ci-deployproject-a -f ~/.ssh/ci_project_aDeploy Key 在 GitLab/GitHub 上通常只给予某个仓库或某个项目组的只读或读写权限。只读就够了的话绝不勾写权限。这样即使 CI 机器被侵入攻击者能动的范围也被锁死在一个仓库里不会拖走你整个账号下的所有仓库。4.3 批量登录几百台机器时如何管理密钥如果只管几台机器手动把公钥塞到每台服务器的authorized_keys里还能忍。一旦机器到了几十上百台这个做法就是给自己埋雷。批量场景下我的实践优先级是跳板机集中管理所有机器只允许通过跳板机登录跳板机上统一管理用户公钥SSH CA 签名管理员用 CA 给用户公钥签名服务器统一信任 CA 公钥用户换电脑只换公钥签名服务器端不需要任何改动定期批量替换如果暂时做不了 CA那就写 Ansible 或脚本批量分发authorized_keys但必须保留审计记录。某个项目组曾经把公钥直接写死在机器初始化镜像里结果有人离职后公钥还在所有服务器上被安全扫描扫出来才紧急处理。这种事只要遇到一次你就明白为什么分散管理是灾难。4.4 网络设备场景下的注意点再补一个偏冷门的如果你管理的网络设备华为、中兴、H3C 等开启 SSH 后提示密钥不匹配或者直接拒绝连接除了前面说的算法协商问题还可能是设备侧只支持 RSA-SHA1而新版 OpenSSH 默认发送的是 RSA-SHA2。在客户端加参数临时验证ssh -vvv -oHostKeyAlgorithmsssh-rsa -oPubkeyAcceptedAlgorithmsssh-rsa adminswitch如果这样能通就要考虑设备固件升级或者在客户端配置里给这个设备单独开PubkeyAcceptedAlgorithms ssh-rsa。这类问题本质是“密钥算法兼容性”不是“密钥到期”但症状非常迷惑人。5. 让“密钥过期”不再发生的日常配置与检查习惯5.1 客户端的 ssh config 长什么样更不容易踩坑我有一套维护了很久的客户端~/.ssh/config模板能避免大量“假过期”问题Host * ServerAliveInterval 60 ServerAliveCountMax 3 AddKeysToAgent yes IdentityFile ~/.ssh/id_ed25519 IdentitiesOnly yes Host bastion HostName bastion.example.com User ops DynamicForward 1080 Host dev-* HostName %h.dev.internal User dev ForwardAgent yes几个关键点解释一下IdentitiesOnly yes非常重要。默认情况下 OpenSSH 会把~/.ssh下所有私钥依次尝试一遍如果服务端只认其中一把但第一把尝试的钥匙返回了拒绝有的服务端配置会直接中断整个认证流程。用IdentitiesOnly yes限制只发送你指定的身份文件能大幅减少这种“明明钥匙没错但登录不了”的情况。AddKeysToAgent yes配合 ssh-agent让你第一次输入密码短语后后续连接不再反复问。如果你有多个密钥可以再搭配Host不同主机用不同IdentityFile。5.2 服务端怎么避免大规模密钥失联在服务端维护authorized_keys时注意几点每一行只放一个公钥别把公钥和注释塞在同一行还会断行定期备份.ssh/authorized_keyscp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.bak.$(date %F)用sshd -T确认实际生效的配置sudo sshd -T | grep -E pubkeyauthentication|authorizedkeysfile如果要临时给某个用户添加一把期限受限的钥匙可以考虑在后台配置 SSH CA而不是手动追加公钥再手动删除。手动操作容易忘遗忘的安全隐患比过期本身更麻烦。5.3 一个简单有用的检测定时任务真正让“密钥过期”成为可控风险的是提前发现而不是事后排查。下面这个脚本可以放在管理机上定时跑批量探测目标主机的公钥登录是否正常#!/bin/bash # 检测批量主机 SSH 公钥登录是否正常的简单脚本 HOSTS_FILE~/hosts_list.txt KEY_FILE~/.ssh/id_ed25519 while read host; do [ -z $host ] continue if ssh -i $KEY_FILE -o BatchModeyes -o ConnectTimeout5 -o StrictHostKeyCheckingno $host echo ok /dev/null 21; then echo [OK] $host else echo [FAIL] $host fi done $HOSTS_FILEBatchModeyes很重要它禁止交互式输入密码确保测试的是公钥认证而不是密码认证。.ssh目录里的公钥变化也值得监控。你可以在管理机上写一个定时任务每天检查所有authorized_keys文件的哈希值出现变化马上告警能第一时间发现“被莫名清理”或“被篡改”的情况。5.4 关于密码短语和 ssh-agent 的搭配最后聊一个特别容易造成“密钥过期”错觉的细节私钥文件本身设置了密码短语每次登录都要输入一次。很多人早上开机后第一次连服务器输入了密码短语之后一直可以免密登录某天突然又要求输入第一反应是“密钥失效了”。其实只是 ssh-agent 在重启后还没有加载这把钥匙。把这段加到~/.ssh/config里Host * AddKeysToAgent yes IdentityFile ~/.ssh/id_ed25519再配合系统启动时执行一次ssh-add ~/.ssh/id_ed25519这样重启后只要输入一次密码短语后续整个会话周期内都免密。如果你有多个不同安全级别的密钥可以分开Host段避免所有钥匙一股脑进 agent。我在实际维护中还有一个习惯每把用于生产环境的密钥都会在注释里写明用途、负责人、创建时间。比如用ssh-keygen -C ops-bastion-2025-zhangsan生成。这样即使几年后被人翻出来也至少能知道这把钥匙是谁的、是干什么的而不是面对一堆yournameyourhost的默认注释发愁。很多“过期”问题最后不是技术解决不了是信息对不上。把元信息写清楚能省掉一大半排查时间。
返回列表