
上周三下午我正在处理另一台机器的日志切割任务群里的消息突然就炸了。“SSH 连不上了所有服务器都试了一遍就这台不行。”发的是一台国产化 Linux 服务器银河麒麟 ARM 架构之前我花了不少时间给它配好了 SSH 密钥免密登录连 root 带普通管理员账号一共五个全部走公钥认证。结果现在开发同事说连不上而且不只是密钥不行连密码登录都被拒22 端口始终没响应。我当时第一反应是“密钥权限被改了”第二反应是“sshd_config 被谁动过”结果排查到最后发现真正的问题出在 RPM 包底层文件上连 rpm 命令都直接报“没找到”。整个排障过程持续了将近三个小时从 SSH 服务一路挖到系统的包管理文件最后用 rpm2cpio 和 cpio 手工把底层二进制文件从 RPM 包里抠出来重新塞回去才救回来。这篇文章我把完整的排查链路、认证原理、修复步骤和踩过的坑都整理出来希望对搞运维、搞国产化系统、或者遇到过不明原因 SSH 挂掉的朋友有帮助。1. 故障现场密钥免密登录配置“翻车”了1.1 出事之前这台机器上我都做了什么先交代一下背景。这批机器是几个月前上线的系统是银河麒麟基于 RPM 包管理内核和桌面环境都做了定制底层命令和 CentOS/RHEL 高度兼容。上线的第二周我做了 SSH 密钥免密登录的配置目的很简单让自动化运维脚本和开发同学不用每天输密码也减少密码被爆破的风险。当时做的事情其实就三件第一步生成密钥对。我在本地跳板机上执行了ssh-keygen -t ed25519 -C deployinternal得到一对密钥私钥留在本地公钥分发到服务器上。选择 ed25519 而不是 RSA主要是因为它密钥短、生成快、安全性不输 RSA 4096而且新版 OpenSSH 对它的支持非常好。第二步部署公钥。把公钥追加到服务器的~/.ssh/authorized_keys文件里。需要注意的是~是哪个用户的家目录我当时给 root 和 deploy 用户都配了所以分别在/root/.ssh/authorized_keys和/home/deploy/.ssh/authorized_keys各追加了一次。权限也顺手设了.ssh目录是 700authorized_keys是 600属主归属对应用户。第三步修改/etc/ssh/sshd_config开启公钥认证。主要参数是PubkeyAuthentication yes然后测试无密码登录成功确认没问题之后才关闭了密钥登录的后备选项当然密码登录我还是留着的PasswordAuthentication yes因为总有人会忘带私钥。配置完之后免密登录一直用得挺顺。无论是ssh rootserver还是scp传文件都是秒连从来没有出过问题。1.2 故障症状密钥被拒、密码被拒、端口没反应开发同学反馈的问题描述是这样的密钥登录本地执行ssh deployserver后终端卡在Authenticated to server之前迟迟不出来最后报Permission denied (publickey,password)密码登录ssh rootserver后输入正确密码同样被拒绝端口状态在本地telnet server 22显示Connection refused这个组合很反常。如果只是密钥没配对密码认证应该还能兜底如果只是密码策略变了密钥认证应该能通。现在两者同时挂了最直接的解释就是 sshd 服务根本没在监听或者监听进程是异常状态。我从带外管理口那台机器有 BMC 远程控制台登录进系统先确认网络层面ping网关正常说明服务器本身还活着。接着看了 sshd 服务状态systemctl status sshd输出显示Active: failed也就是 sshd 服务已经崩溃退出。再尝试手动启动看看报错systemctl start sshd结果瞬间失败什么守护进程都没起来。这时候我就明白这已经不只是配置层面的问题了大概率是 sshd 依赖的底层文件出了问题。2. 顺着登录链路深挖密钥认证的本质与定位转折2.1 密钥免密登录到底是怎么工作的在讲后面的修复过程之前我先把密钥免密登录的认证机制完整梳理一遍因为这次排障里一个关键教训就是很多人对密钥认证的理解停留在“把公钥放上去就行”这会导致故障时根本不知道该往哪个方向查。密钥免密登录的本质是服务端验证“你确实持有与已登记公钥配对的私钥”。完整个流程是这样第一步客户端发起连接告诉服务器自己想用哪个用户登录并列出自己支持的认证方法。服务器端如果开启了PubkeyAuthentication就会回复允许公钥认证。第二步服务器在用户的authorized_keys文件里找到客户端声称持有的公钥生成一个随机挑战值用这个公钥加密后发给客户端。第三步客户端用本地私钥解密挑战值把结果返回给服务器。服务器验算通过就确认“持有私钥的人”是合法用户。这个过程绕过了密码传输私钥全程不出本地机器安全性和便捷性都比密码登录高不少。但这里面有个容易忽略的细节sshd服务在读取authorized_keys文件之前会先检查文件的权限和属主。如果.ssh目录权限是 777或者authorized_keys文件其他用户也能写服务端会直接拒绝加载公钥。这就是StrictModes参数在做的事默认是开启的。我举一个生活化的例子公钥就像你所在写字楼前台的访客名单写着“持有工牌 9527 的人可以进入 12 层”。私钥就是那张工牌每次进门时你刷一下工牌前台把编号和名单一比对符合就放行。但如果你把访客名单放在人来人往的公共桌上前台会认为名单不可信直接拒绝所有人进去。所以如果你发现自己明明配好了密钥却依然要输密码别急着怀疑密钥对不对先去看权限。这是我在过往项目中踩过最多的坑没有之一。但这一次连authorized_keys都还没来得及看服务就起不来了肯定还有更底层的问题。2.2 关键转折sshd 崩溃背后的依赖分裂从带外控制台登录系统后我先执行了最常见的三连排查sshd -t journalctl -u sshd --no-pager -n 50 ls -l /etc/ssh/sshd_configsshd -t只校验配置语法很快就通过了说明sshd_config本身没问题。journalctl里只看到服务启动失败的记录没有更详细的报错。第三次检查配置文件也没有异常。这时候我意识到一个问题既然配置没问题那 sshd 为什么连启动都启动不了我决定直接尝试手动执行 sshd 二进制文件看真实输出/usr/sbin/sshd -D终端立刻输出了一行关键信息/usr/sbin/sshd: error while loading shared libraries: libcrypto.so.10: cannot open shared object file: No such file or directory这句话才是真正的问题sshd启动时依赖的libcrypto.so.10动态库文件丢失了。libcrypto.so.10是 OpenSSL 的核心动态库SSH 在做加密握手、密钥交换、证书认证时都离不开它。如果这个库缺失sshd 整个进程连初始化都做不到。我赶紧用ldd命令检查/usr/sbin/sshd的完整依赖关系ldd /usr/sbin/sshd输出的后半段有一堆not found不只是libcrypto.so.10还有libssl.so.10以及好几个 PAM 相关的库文件。这说明并非偶发性的单个文件损坏而是多个 RPM 包的文件同时缺失很像是有人对系统文件做过整体误删或者某个不安分的安装脚本把库文件给覆盖没了。紧接着我准备用 RPM 查询哪个包应该拥有这些文件rpm -qf /usr/lib64/libcrypto.so.10结果系统直接回了一句bash: rpm: command not found在这个时刻我的排障思路彻底转折了——这台机器不仅 SSH 挂了连 RPM 包管理工具本身也丢了。一个连包管理机制都瘫痪的系统用常规手段根本无法修复。2.3 为什么 rpm 命令会突然消失常见原因盘点后来故障恢复之后我做了一次彻底复盘。结合现场查到的线索这台机器的rpm命令丢失以及底层库文件缺失最可能是下面几个原因之一第一被误卸载。如果有人执行了rpm -e或者第三方安装脚本里写了卸载操作恰好把包含rpm命令的rpm包或 OpenSSL 相关的openssl-libs包给卸掉了就会同时引发命令丢失和库文件缺失。第二文件被恶意清空。这类情况在国产化系统上也不是没发生过某些安全软件或可疑脚本会因为“误判”而清空一些核心动态库文件。毕竟libcrypto和libssl这两个文件的名字在某些检测规则里很容易被当成“可疑加密工具”处理但实际上它们是系统运行必不可少的底层组件。第三rpm 数据库损坏。命令还在但执行时读取/var/lib/rpm下的数据库报错表现上也像是“没有这个命令”。这种情况稍微好办一点因为rpm --rebuilddb通常能解决。第四磁盘写满导致文件截断。文件明明存在但内容写到一半因磁盘满被截断系统启动后加载动态库失败。我当时检查了磁盘空间没有满所以基本排除。无论如何当务之急是找到正确的 RPM 包从中提取底层文件手工恢复。这就要用到我们今天标题里的另一个关键词——RPM 包底层文件修复。3. 修复实操不依赖 rpm 命令也能手工恢复底层文件3.1 第一阶段准备确认系统版本、架构和拿到正确 RPM 包在动手之前我反复告诉自己一个原则绝不要在一台系统已经处于异常状态的机器上随手找一个版本相似的文件就覆盖过去。系统版本、包版本、CPU 架构必须完全对上否则可能引起连锁的兼容性问题到时候连系统都起不来。首先确认当前系统的基础信息。我用cat /etc/os-release查看系统发行版确认是银河麒麟 V10 SP1 版本内核基于 4.19。再执行uname -m确认 CPU 架构是aarch64也就是 ARM 64 位。这一步很重要因为 ARM 架构和 x86_64 的 RPM 包完全不通用硬装会导致二进制格式不兼容。接着我需要找到这台机器对应的原始安装源。幸运的是机房里有这台机器当时的 ISO 安装镜像我把Kylin-Desktop-V10-SP1-Release-aarch64.iso挂载到另一台同架构的正常机器上进入Packages目录后找到了几个必要的 RPM 包openssh-server-*.aarch64.rpmopenssh-clients-*.aarch64.rpmopenssh-*.aarch64.rpmopenssl-libs-*.aarch64.rpmpam-*.aarch64.rpm这里面openssh-server提供/usr/sbin/sshd主进程openssh提供公共配置文件openssl-libs就是丢失的libcrypto.so.10和libssl.so.10的所属包pam包里则有许多 SSH 登录依赖的 PAM 模块。如果你没有原始 ISO也不要慌只要网络能通且系统配置了 Yum/DNF 源可以用yumdownloader直接下载对应包或者在有图形界面的机器上从官方镜像站手动下载相同版本的 RPM 包。但注意一点下载时一定要匹配系统版本和 CPU 架构千万别下载成x86_64的包塞到aarch64系统里。3.2 核心操作利用 rpm2cpio 和 cpio 从 RPM 包里提取文件RPM 包这东西本质是一个带元数据的 cpio 归档。平时我们安装时RPM 工具会负责校验依赖、执行脚本、把文件放到正确位置。但现在 rpm 命令本身已经“失联”了没法走正常的安装通道所以我决定绕开 rpm直接用底层的rpm2cpio和cpio组合把文件解出来。把 RPM 包传到目标机器通过带外控制台挂载虚拟光驱或者用 scp 从别的机器传然后在任意临时目录下执行mkdir /tmp/openssh-fix cd /tmp/openssh-fix rpm2cpio openssh-server-8.2p1-4.ky3.aarch64.rpm | cpio -idmv这条命令中rpm2cpio负责把 RPM 包转换成 cpio 格式流竖线管道符再把这个流直接交给cpio-i表示解包-d表示自动创建所需目录-m表示保留文件时间戳-v是显示解出的文件明细。执行完毕后临时目录里会出现一个usr/目录里面的结构就是 RPM 包装载到系统后的文件布局比如usr/sbin/sshd、usr/etc/ssh/sshd_config具体路径取决于包内定义。这一步相当于我们用手把压缩包里的东西“倒”了出来不需要发行版的包管理器参与。有了这个基础之后修复就变成了一个“文件搬运”的活儿。但搬运之前还有一步必做的工作先看包里有哪些文件、对应的路径是什么。这一步尤其重要因为很多 RPM 包里的文件路径和系统实际路径不完全一致例如某些配置文件在包内叫usr/etc/xxx但装到系统后会放到/etc/xxx。要对照cpio -t的输出或者直接看解出来的目录结构判断。find /tmp/openssh-fix -type f | sort确认文件清单后再用ldd检查一下sshd解包后所有依赖的库文件是否都已经解出来了。如果还缺哪个库继续从对应 RPM 包里解包提取。这一步是确保我们“搬”过去的文件能够真正运行起来的关键。3.3 文件覆盖与权限恢复先备份、再覆盖、后对齐文件提取到临时目录后接下来的操作就是覆盖但覆盖这一步绝对不能用蛮力必须遵守三条规则备份旧文件、按需要保留原配置、严格重置权限。我先在临时目录里找到了usr/sbin/sshd确认它是可执行文件查看版本和依赖ldd /tmp/openssh-fix/usr/sbin/sshd这时发现里面大部分依赖已经能搜到输出只有libcrypto.so.10和libssl.so.10仍然显示not found因为这两个库文件还没有恢复。于是我用相同方式解包openssl-libsmkdir /tmp/ossl-fix cd /tmp/ossl-fix rpm2cpio openssl-libs-1.1.1f-3.ky3.aarch64.rpm | cpio -idmv解出来后找到了usr/lib64/libcrypto.so.10、usr/lib64/libssl.so.10以及一系列符号链接文件。接下来进入正式恢复环节。我先把原系统上还在的旧文件全部做个备份。虽然很多文件已经缺失但能备份的还是备份一下放到/root/backup-ssh-$(date %F)/目录下。备份命令类似这样mkdir -p /root/backup-ssh-2025-01-15 cp /usr/sbin/sshd /root/backup-ssh-2025-01-15/sshd.bak 2/dev/null || true cp /usr/lib64/libcrypto.so.10 /root/backup-ssh-2025-01-15/libcrypto.so.10.bak 2/dev/null || true然后把解压出来的文件复制回系统标准路径cp /tmp/openssh-fix/usr/sbin/sshd /usr/sbin/sshd cp /tmp/ossl-fix/usr/lib64/libcrypto.so.10 /usr/lib64/libcrypto.so.10 cp /tmp/ossl-fix/usr/lib64/libssl.so.10 /usr/lib64/libssl.so.10覆盖完成后立刻重置文件权限和属主。这步千万不能省否则文件虽然存在但可执行位不对或者属主不对依然无法正常调用。chmod 755 /usr/sbin/sshd chown root:root /usr/sbin/sshd chmod 755 /usr/lib64/libcrypto.so.10 /usr/lib64/libssl.so.10 chown root:root /usr/lib64/libcrypto.so.10 /usr/lib64/libssl.so.10需要注意一个细节libcrypto.so.10和libssl.so.10通常还带有一串符号链接比如libcrypto.so.1.1或.so结尾的软链。这些软链有时候会因为误删操作一起丢失。我专门检查了一遍/usr/lib64/下这两个库的软链情况发现缺失后通过ln -s按原样补回。如果你不确定软链的目标可以用ls -l从另一台同环境正常机器上对照获取。我用一个通俗类比来解释这一步这些库文件就像家里水管系统的弯头和接头。弯头缺失水龙头拧开也只能哗哗漏水接头型号不对硬接上去可能把整段水管都崩裂。只有弯头、接头、水管口径完全匹配水才能顺畅地从主管道流到各个水龙头。我们在修复底层库时也是同理不仅要文件在还得确保软链、权限、属主全部对齐。3.4 验证闭环服务恢复 密钥免密登录重新测试文件覆盖完成后我重新执行了依赖检查ldd /usr/sbin/sshd | grep not found输出的结果是空的说明所有依赖库都已经找到了。这时候心里稍微有底了一些。接下来启动服务systemctl daemon-reload systemctl start sshd systemctl status sshd这一次sshd状态从failed变成了active (running)22 端口也开始监听。但到这里只能算“SSH 服务能起来了”还不能算“故障已经解决”。为什么因为现在这台机器的 RPM 数据库和关键包状态还很混乱我必须确认密钥免密登录真正恢复了才敢把问题单关闭。先在服务器本地测一次完整的密钥认证流程。我从另一台正常客户端机器上执行ssh -v deployserver-v参数会输出详细认证过程。日志里看到debug1: Server accepts key: /home/deploy/.ssh/authorized_keys debug1: Authentication succeeded (publickey).这一行就说明公钥认证已经成功免密登录恢复了。但我还是不甘心又追加验证了一个密码登录ssh rootserver输入密码后成功登录。这说明 PAM 的登录链路也恢复了密码认证没有受到影响。验证过程中还有一个重要动作查看服务端/var/log/secure日志确认没有出现权限相关告警或Authentication refused的错误。日志干净之后整个故障闭环才算真正打通。4. 故障复盘这类问题怎么防、怎么自愈4.1 为什么“免密登录”本身没有背锅这次故障里最误导人的地方就在于表面症状是“SSH 连不上”而且我们刚刚大改过密钥免密登录配置所以所有人第一反应都是“免密登录配置出问题了”。但事实上密钥免密登录配置一直是对的真正问题出在 OpenSSL 动态库和 RPM 工具链文件的缺失上。这个经验非常重要当 SSH 出现异常时先把排查范围从“配置文件”扩大到“服务运行状态”和“底层依赖”才能真正定位问题。我做了一个简单的心得总结如果sshd服务还活着但认证失败优先排查密钥权限、authorized_keys内容、StrictModes参数、PAM 配置如果sshd服务起不来优先看journalctl -u sshd和手动执行/usr/sbin/sshd -D的报错如果手动执行报缺失动态库用ldd检查依赖链并逐步向上追溯是哪个 RPM 包拥有这个文件如果rpm命令本身消失那就准备走本文的rpm2cpio cpio手工修复路线把这个排查顺序写进故障手册里下次遇到类似问题能少走至少半小时弯路。4.2 故障自愈与应急工具建议这次经历让我对“自愈”有了更深的体会。所谓自愈不只是出了问题能快速恢复更是在问题发生之前就给自己留好退路。我在事后给这批服务器做了几项加固措施现在分享出来第一建立本地 RPM 包迷你仓库。从官方 ISO 里把openssh、openssl-libs、pam、coreutils等关键软件包全部拷贝到一个独立目录做一次基于版本号的索引。以后哪怕没外网也能快速找到匹配的包。第二开启 RPM 数据库定时校验。用rpm -V可以比对包内文件的属性与原始安装状态及时发现文件缺失、内容改动或权限变化。我写了一个简单的巡检脚本每周跑一次输出差异报告到监控平台。这个思路成本很低但能在文件刚开始被破坏时就发出警告。第三配置带外管理通道。这台服务器原本就有 BMC 带外控制台这次我之所以能在断网情况下完成修复全靠它。绝不要在生产环境放弃带外通道那是你在紧急情况下的最后一根救命稻草。第四建立应急自愈脚本。我写了一个非常朴素的检查脚本每隔五分钟检测一次sshd服务状态如果发现服务为failed就自动执行一次ldd /usr/sbin/sshd检查一旦确认是依赖缺失就把本地镜像仓库里准备好的 RPM 包解包并覆盖恢复。这个脚本没有多复杂但它真正实现了标题里“故障自愈”四个字——当然覆盖文件前必须备份这属于基础素养。第五把救援手册打印出来放机柜。手册要写清楚“在什么情况下用哪些命令”包含从带外登录、备份、解包、覆盖、验收到回滚的每一步。我们平时总觉得这些命令烂熟于心但真实故障现场人一紧张连ls都可能打错。手册不是给高手看的是给紧绷状态下的自己兜底的。4.3 经验之外的一点小提醒最后说一个很多人容易忽视的细节清理临时文件。我在修复结束后把/tmp/openssh-fix和/tmp/ossl-fix目录里的 RPM 解包内容全部清理掉了因为这些文件如果不清理会占用临时目录空间还可能被后续巡检脚本误认为“异常文件”。清理命令很简单rm -rf /tmp/openssh-fix /tmp/ossl-fix此外这次修复也让我更加确信在国产化 Linux 环境中尽量不要用“下载二进制然后直接覆盖”的思路做 Fix因为国产系统对文件权限、SELinux 上下文、PAM 配置的校验往往比标准发行版更严格偶尔还会加上自己的安全机制。凡是从 RPM 包原文解出的文件安全性要远高于网上随便下载的二进制文件。这个案例最让我感慨的地方在于一次看似普通的 SSH 免密登录故障最终却牵出了整个系统的 RPM 包管理链路问题。如果没有 RPM 包底层的手工修复能力这台机器很可能会走向“重装系统”的命运。而看清底层机制、掌握手工提取和恢复的能力才是运维人在关键时刻真正救命的本事。