拓冰建站拓冰建站
首页 / 资讯中心 / 正文

内网环境OpenSSL/OpenSSH离线源码升级实战:从漏洞修复到安全加固

1. 从漏扫报告说起内网主机为什么躲不开 OpenSSL/OpenSSH 升级1.1 一份 CVSS 9.8 的漏洞通告把内网服务器推到了风口浪尖去年年中我接到一个客户的电话语气很急促他们的一台核心业务服务器漏扫报告里赫然写着 OpenSSH 版本过低存在远程代码执行漏洞CVSS 评分 9.8。更麻烦的是这台机器部署在内网不对外提供任何互联网访问平时所有补丁更新都依赖内网镜像源可镜像源里的 OpenSSH 版本比上游落后了整整两个大版本安全修复的 rpm 包迟迟没有同步过来。这就是内网环境最尴尬的地方越是重要的核心资产越不敢随便停服越是等保和漏扫盯得紧的节点越是不能通过常规 yum/apt 命令直接升级。毕竟内网不等于绝对安全横向渗透、跳板攻击、供应链投毒哪个都能在内网里跑起来。OpenSSH 和 OpenSSL 这两样东西几乎存在于每一台 Linux 服务器里一个是远程入口一个是 TLS/加密基础设施只要其中一个版本过旧整个内网的安全基线就有缺口。我当时给客户出的方案很直接走源码编译离线部署升级 OpenSSL 之后再联动升级 OpenSSH同时把回滚预案做好。这就是这篇文章的由来。整个流程做完之后我把它整理成了一份内部文档后续又在多个项目里复用了四五次每次都能跑通所以今天把它分享出来给正在内网环境里做同样事情的朋友一个参考。1.2 内网环境的三个约束无外网、发行版混杂、依赖环环相扣在内网环境做 OpenSSL/OpenSSH 升级和在有互联网的机器上敲两行 yum update 完全是两码事。你首先要面对三个绕不开的约束。第一个约束是物理隔离。内网机器没有外网wget、curl 访问不到源码服务器软件包下载必须人肉搬运要么在能联网的测试机上提前下载好源码包和依赖通过 U 盘、跳板机或内网文件服务器传进去要么依赖内网自己的软件源但内网源往往更新滞后版本老旧达不到安全升级的目的。第二个约束是发行版混杂。一套内网环境里经常同时存在 CentOS 7、Rocky Linux 8、Ubuntu 20.04 甚至某些国产化系统每个系统的包管理器不同、默认 OpenSSL 版本不同、动态库路径不同你在 CentOS 上编译通过的流程到 Ubuntu 上可能因为缺少某个依赖头文件而直接卡住。这就要求整个升级流程不能依赖某个发行版的 RPM 或 DEB 包必须走更通用的源码编译路线。第三个约束是依赖环环相扣。OpenSSH 的编译强依赖 OpenSSL而 OpenSSL 3.x 默认安装路径如果和系统库冲突轻则导致某些模块报 version mismatch重则把 sshd 搞到起不来。另外 OpenSSH 编译还依赖 zlib、pam、selinux 等一堆系统库任何一个 devel 包缺失configure 阶段就会中断。这些依赖关系在联网环境下 apt 会自动处理离线环境下全靠你手动补齐。1.3 先看懂版本号OpenSSL 3.5.6 和 OpenSSH 9.9p2 到底改了什么升级之前先把版本号看明白否则一堆人容易把 OpenSSL 和 OpenSSH 的版本彻底搞混。OpenSSL 的版本号长这样3.5.6主版本 3次版本 5补丁版本 6。而 OpenSSH 的版本号是 9.9p2前面的 9.9 是主版本号后面的 p2 是 portable 发行版的补丁级别p是 portable 的意思代表这是可移植版本的第二个补丁。OpenSSH 安全升级的核心动机来自 2024 年披露的 regreSSHion 漏洞CVE-2024-6387这个漏洞影响 8.5p1 到 9.7p1 之间的大部分版本在特定的 glibc 环境下攻击者通过未认证的 SSH 连接就可能触发远程代码执行CVSS 评分高达 9.8。由于 OpenSSH 在主版本 9.8p1 中修复了这个漏洞后续版本对信号处理和权限隔离也做了加强直接把版本升到 9.9p2 是比较稳妥的选择。OpenSSL 这边的升级理由同样充分3.x 分支持续修复了包括证书解析、TLS 握手在内的一批高危问题。我用的是 OpenSSL 3.5.6 作为升级目标你在实际操作时以你下载到的当前稳定版本为准比如 3.0.x 长期支持分支或 3.5.x 新特性分支都行关键是版本不能低于线上已经在用的旧版本。升级 OpenSSL 必须放在 OpenSSH 之前因为 OpenSSH 编译时要链接 OpenSSL 的 libcrypto 和 libssl编译器和链接器要从新装的 OpenSSL 头文件目录里找依赖。2. 离线物料筹备下载清单、依赖检查与人肉传输的规范2.1 目标机器信息盘点版本、架构、已装依赖动手下载任何东西之前先到目标机器上做一轮信息盘点。我给客户做升级时的第一件事就是在每台待升级机器上执行下面这组命令把结果记录下来。cat /etc/os-release uname -m ssh -V openssl version rpm -qa | grep -E openssl|openssh # RHEL/CentOS/Rocky dpkg -l | grep -E openssl|openssh # Ubuntu为什么要先做这一步因为内网服务器之间可能看起来版本一样实际架构和系统补丁情况各不相同。比如同样是 CentOS 7.9有的机器装的是 OpenSSL 1.0.2k有的可能是 1.0.2k 加上一堆安全补丁同样是 x86_64 架构个别老机器可能还是 i686 的。不同架构意味着你要下载对应架构的源码包虽然 OpenSSL 和 OpenSSH 都是源码编译理论上跨架构没问题但依赖包比如 zlib-devel、pam-devel如果走二进制包路线就必须严格匹配架构。另一个要看的关键点是 glibc 版本ldd --version看一下。OpenSSH 9.9p2 对系统的 glibc 版本没有特别苛刻的要求但过于老旧的内核比如 2.6.x可能在新版 OpenSSH 上遇到编译告警或运行时异常。我建议把每台机器的 IP、系统版本、架构、原 OpenSSL 版本、原 OpenSSH 版本统一记到一个表格里后续批量执行时按表格核对不要凭记忆操作。2.2 离线安装包的三种准备方式物料准备方式取决于你有哪条网络通路可以出去。常见的三种方式我都用过根据不同的网络条件选择最顺手的一种。第一种在能联网的机器上用包管理器下载 RPM/DEB 包并传递入内网。适合补全编译依赖。红帽系用 yumdownloaderyum install -y yum-utils yumdownloader --resolve --destdir/tmp/rpms gcc make perl zlib-devel pam-devel libselinux-develDebian/Ubuntu 系用 apt downloadapt-get update cd /tmp/debs apt-get download build-essential perl zlib1g-dev libpam0g-dev libselinux1-dev第二种直接下载源码 tar 包。OpenSSL 的源码从官网下载OpenSSH 的 portable 版一般从 OpenBSD 镜像站点下载文件都是 tar.gz 格式体积不大十几 MB 级别。下载时注意核对 SHA256 校验值防止下载过程中文件不完整或被篡改这个检查必须做不能省。第三种如果内网有可以通外网的跳板机或者文件服务器直接把源码包和依赖包放到文件服务器指定目录目标机器通过 scp 或 rsync 拉取。这种方式适合节点数量较多的批量升级比逐个插 U 盘高效得多。2.3 校验、传输与目录规划下载完成后首先要做的是 SHA256 校验。我习惯在下载机器上生成一个 checksums.txt 文件把每个源码包和依赖包的校验值写进去然后一起传到内网在内网机器上再跑一次校验。这样能保证整个传输链路没有问题。sha256sum openssl-3.5.6.tar.gz openssh-9.9p2.tar.gz checksums.txt # 传到内网后执行 sha256sum -c checksums.txt传输方式方面单机操作用 scp 或 U 盘拷贝批量操作建议先建一个统一的目录规范。我的习惯是在每台目标机器上创建 /opt/upgrade 目录里面再分子目录src 放源码包rpm 放依赖包bak 放备份文件logs 放编译日志。这样如果你要写批量脚本脚本只操作这一个目录路径不会乱。2.4 编译工具链检查清单源码编译绕不开编译工具链。内网机器如果之前装过开发环境gcc 和 make 一般都在但如果是最小化安装的系统很可能连 make 都没有。编译之前先检查一遍缺什么补什么gcc --version make --version perl -v pkg-config --version另外还要检查几个关键的 devel 头文件是否存在因为 OpenSSH 编译期间会寻找这些头文件ls /usr/include/zlib.h ls /usr/include/security/pam_appl.h ls /usr/include/selinux/selinux.h这三个头文件分别对应 zlib-devel、pam-devel、libselinux-devel。CentOS 7 的最小化安装经常缺少 pam-devel 和 zlib-develUbuntu 20.04 经常缺少 libpam0g-dev这就是我前面说发行版混杂会导致依赖差异的典型场景。如果你在 configure 阶段发现缺少某个头文件编译中断后还得回头补依赖白白浪费时间。所以这一步检查一定要提前做齐全。3. OpenSSL 3.5.6 编译安装把新版藏在独立目录的完整过程3.1 select configure 参数时的取舍逻辑OpenSSL 3.x 编译安装的核心思路是不要覆盖系统自带的 OpenSSL而是把新版装到一个独立的前缀目录下让 OpenSSH 等特定组件显式去链接它。这样做的最大好处是风险隔离——系统里原本依赖 libcrypto.so.1.0.0 或 libssl.so.1.1 的软件不会因为动态库升级而突然崩溃。我采用的 configure 参数是这样的cd openssl-3.5.6 ./Configure linux-x86_64 \ --prefix/usr/local/ssl \ --openssldir/usr/local/ssl \ --libdirlib \ shared zlib \ -Wl,-rpath,/usr/local/ssl/lib逐个拆解为什么这样选--prefix/usr/local/ssl安装根目录。选 /usr/local/ssl 是 OpenSSL 编译的惯例不回写到 /usr/local/bin 和 /usr/lib64升级失败时直接删除这个目录就能整体回退。--libdirlib强制把动态库放到 /usr/local/ssl/lib而不是系统的 lib64。如果不指定不同发行版默认路径不一致有的放 lib64有的放 lib脚本里就很难统一处理。shared生成动态链接库OpenSSH 链接 libcrypto 和 libssl 需要的是 .so 文件只编译静态库无法被链接器正常使用。zlib启用对 zlib 压缩的支持OpenSSH 的压缩隧道依赖这一项。-Wl,-rpath,/usr/local/ssl/lib把库搜索路径直接写进 ELF 文件的 RUNPATH 里这样可执行文件运行时不需要依赖 ldconfig 全局配置也不会因为 LD_LIBRARY_PATH 没设置而找不到库。3.2 编译安装实操配置完成后编译和安装就只是一条命令的事make -j$(nproc) make install_sw这里用install_sw而不是make install原因是 install_sw 只安装软件部分包括头文件、动态库、二进制不安装文档、man pages 和杂项示例。在内网生产环境少安装这些内容就少一份被扫描器盯上的风险也少几个不必要的路径。安装完成后查看一下关键文件ls /usr/local/ssl/bin/openssl ls /usr/local/ssl/lib/libcrypto.so* ls /usr/local/ssl/lib/libssl.so* /usr/local/ssl/bin/openssl version -a如果版本信息能正常输出并且显示 OpenSSL 3.5.6说明 OpenSSL 本体安装成功。3.3 动态库注册ldconfig、pkg-config 和 rpathOpenSSL 安装到独立前缀目录之后系统默认的动态库搜索路径不会包含 /usr/local/ssl/lib这意味着如果某个程序直接链接 /usr/local/ssl/lib/libcrypto.so运行时可能报找不到共享库。解决办法有三层按优先级从高到低分别是第一层是编译时加 rpath。OpenSSH 编译时我在 LDFLAGS 里显式加入-Wl,-rpath,/usr/local/ssl/lib这样 sshd 和 ssh 二进制运行时不需要任何环境变量就能找到库。这是最干净的方案不需要改动系统全局配置。第二层是写 ldconfig 配置。如果你还有其他程序要使用新 OpenSSL可以把 /usr/local/ssl/lib 写进 ld.so.conf 并执行 ldconfigecho /usr/local/ssl/lib /etc/ld.so.conf.d/openssl-3.5.6.conf ldconfig ldconfig -p | grep ssl第三层是 pkg-config 路径。某些软件在 configure 阶段会通过 pkg-config 查找 OpenSSL把 PKG_CONFIG_PATH 指过去即可export PKG_CONFIG_PATH/usr/local/ssl/lib/pkgconfig pkg-config --modversion openssl三层方案我建议最少做前两层特别是 ldconfig 配置很多编译阶段报找不到 libssl.so 的问题都是因为这一步没做。3.4 典型故障openssl version mismatch 的根因与解法用源码编译 OpenSSL 之后最常见的报错就是openssl version mismatch. built against 30000020, you have 30500060。这类报错的意思是某个模块编译时链接的是旧版 OpenSSL 的头文件版本号 30000020对应 3.0.2但运行时加载的是新版 OpenSSL 的库版本号 30500060对应 3.5.6头文件和库版本不匹配。为什么会出现这种问题因为旧软件的 Makefile 里把OPENSSL_CFLAGS、LIBS这些变量写死了或者编译时用的 /usr/include/openssl/opensslv.h 是旧版的而链接器通过 rpath 或 LD_LIBRARY_PATH 加载了新版的 libcrypto.so。解决思路有两条一是把新 OpenSSL 的头文件路径通过 CPPFLAGS 放到 include 搜索路径最前面确保编译时拿到的是新版头文件二是避免在同一编译命令行里混用系统 OpenSSL 库和新版库。我在升级 OpenSSH 时最终统一用--with-ssl-dir/usr/local/ssl让编译脚本自己找头文件和库彻底绕开了这种错配。另外还有一种情况是 Python、Ruby 等运行时自带的模块报 mismatch这种一般不是升级 OpenSSL 能直接解决的需要等对应运行时版本更新。内网环境下如果遇到我建议优先排查是不是自己手改过 LD_LIBRARY_PATH把不同版本的 libcrypto 混在同一个环境变量里了。4. OpenSSH 9.9p2 接入新 OpenSSL从 configure 到 systemd 接管4.1 先绕开一个选择题OpenSSH 到底链接哪套 OpenSSL编译 OpenSSH 前要做一个非常重要的决定新 OpenSSH 链接系统自带的 OpenSSL还是链接刚装好的新 OpenSSL。我的建议很明确链接新的。原因有两个。第一OpenSSH 9.9p2 的某些新功能和对 TLS 协议组的支持依赖较新的 OpenSSL系统自带的旧 OpenSSL 可能没有对应的 API。第二安全升级的核心目的就是把加密基础设施整体拉高如果 OpenSSH 升上去了但底层还是旧的 libcrypto等于漏网之鱼还留在系统里。当然链接新 OpenSSL 会带来一个额外的副作用所有调用 ssh、scp、sftp 的进程运行时都必须能找到新 libcrypto。这个问题我在上一节已经解决了通过 rpath 和 ldconfig 双保险。4.2 编译参数与安装步骤OpenSSH 的 configure 参数比 OpenSSL 要多而且每一个参数背后都有实际意图。我用的完整命令如下export PATH/usr/local/ssl/bin:$PATH export CPPFLAGS-I/usr/local/ssl/include -I/usr/include export LDFLAGS-L/usr/local/ssl/lib -Wl,-rpath,/usr/local/ssl/lib cd openssh-9.9p2 ./configure \ --prefix/usr/local/openssh \ --sysconfdir/etc/ssh \ --with-ssl-dir/usr/local/ssl \ --with-pam \ --with-md5-passwords \ --with-privsep-path/var/empty/sshd \ --with-zlib \ --with-selinux参数说明--prefix/usr/local/openssh安装目录和 OpenSSL 一样独立前缀方便回滚。--sysconfdir/etc/ssh配置文件目录。这个参数很关键指定后新版 sshd 会继续读取 /etc/ssh/sshd_config而不是跑到 /usr/local/openssh/etc 里找能避免升级后配置路径漂移的问题。--with-ssl-dir/usr/local/ssl让 configure 脚本去 /usr/local/ssl 找新版 OpenSSL 的头文件和库这是新旧 OpenSSL 正确衔接的核心参数。--with-pam启用 PAM 认证否则密码登录可能失效。系统里必须有 /etc/pam.d/sshd 文件CentOS 7 默认有Ubuntu 也默认有。--with-md5-passwords兼容旧密码哈希某些老用户密码如果是 MD5 格式没有这个参数会认证失败。--with-privsep-path/var/empty/sshdsshd 的权限隔离目录。CentOS 7 默认有 /var/empty/sshdUbuntu 可能需要手动创建并确保属主是 root、权限为 755。--with-selinux在红帽系系统上启用 SELinux 支持否则开启 SELinux 的机器上运行自定义路径的 sshd 会被策略拦截。配置完成后编译安装make -j$(nproc) make install安装完成后使用ssh -V验证版本/usr/local/openssh/bin/ssh -V4.3 systemd override 接管 sshd 的正确姿势OpenSSH 安装到 /usr/local/openssh 后系统的 systemd 服务默认还是指向 /usr/sbin/sshd所以需要修改 sshd 服务单元让它使用新二进制。这里我强烈推荐用 systemd override 文件而不是直接修改 /usr/lib/systemd/system/sshd.service。原因很简单override 文件改动小、易回滚直接改原 unit 文件升级系统软件包时会被覆盖回滚时还要费劲恢复。创建 override 文件mkdir -p /etc/systemd/system/sshd.service.d cat /etc/systemd/system/sshd.service.d/override.conf EOF [Service] ExecStart ExecStart/usr/local/openssh/sbin/sshd -D $OPTIONS EnvironmentLD_LIBRARY_PATH/usr/local/ssl/lib EOF systemctl daemon-reload注意两点。第一ExecStart那一行必须保留它用于清空原 unit 中定义的 ExecStart如果不写systemd 会报重复定义错误。第二Environment 里加上 LD_LIBRARY_PATH 是对 rpath 的双保险虽然前面编译时已经加了 rpath但某些场景比如通过 sudo 调用 sshd环境变量会被重置rpath 依然有效LD_LIBRARY_PATH 只是确保万无一失。现在检查配置是否正确systemctl cat sshd sshd -t -f /etc/ssh/sshd_configsshd -t是配置语法检查这一步如果报错先解决不要直接重启服务。4.4 SELinux、PAM 与登录认证的联动问题内网 CentOS/Rocky 机器上升级完成后经常遇到的第一个坑就是 SELinux 拦截。即使你编译时加了--with-selinux如果 sshd 可执行文件位于 /usr/local/openssh/sbin/sshd默认的 SELinux 文件上下文并不允许它监听 SSH 端口或执行相关操作。你会看到日志里出现SELinux is preventing /usr/local/openssh/sbin/sshd from name_bind access on the tcp_socket port 22。解决办法有三个按推荐排序使用semanage fcontext修改文件上下文让新二进制继承 sshd 的上下文semanage fcontext -a -t sshd_exec_t /usr/local/openssh/sbin/sshd restorecon -Rv /usr/local/openssh如果嫌麻烦直接把新编译的 sshd 复制到 /usr/sbin/sshd 覆盖原文件这样文件上下文天然正确但这会牺牲一部分回滚便利性。临时关 SELinux不推荐生产环境千万别这么干。PAM 联动的问题则体现在密码登录失败上。新版 sshd 使用 PAM 时需要 /etc/pam.d/sshd 这个文件。如果你升级前系统 sshd 正常能密码登录这个文件肯定存在无需额外操作。但如果你的 sshd_config 里设置了UsePAM no而系统用户密码认证又依赖 PAM可能会出现 root 以外的用户无法登录的怪现象。升级后建议保持UsePAM yes并在验证阶段用普通用户试一次密码登录。另一个容易被忽视的点是 HostKey 配置。新版 OpenSSH 默认生成 ECDSA 和 ED25519 两种 host key旧的 RSA 主机会话恢复可能不正常。升级前一定要备份 /etc/ssh/ssh_host_* 文件升级后如果 sshd 启动时报找不到 host key把备份文件还原即可千万不要随便重新生成否则客户端会出现 host key 不一致告警。4.5 远程升级防自杀的会话保活技巧这是我最想强调的一个经验。远程升级 OpenSSH 时最大的风险不是编译失败而是你把 sshd 重启了结果自己当前这台机器的 SSH 会话被服务重启打断而新 sshd 又因为某个配置问题起不来你直接连不上机器而且你人在外地根本没有带外管理通道。我总结了一套远程升级的防自杀流程第一步先开一个干净的新 SSH 会话保持不动。如果你在用的终端是通过跳板机连接的不要关掉跳板机如果网络可能不稳定用 tmux 或 screen 包一个会话即使网络断开也能重连回来。第二步在重启 sshd 之前提前在另一个窗口执行一个延迟重启用脚本。脚本里先执行nohup sleep 10 systemctl restart sshd留出 10 秒缓冲期。执行重启命令后不要立刻断开观察当前会话是否还活着。如果 10 秒后 sshd 没有成功重启当前会话可能已经断掉但因为你提前留了 tmux 会话或者带外管理还能抢救。第三步确认新 sshd 启动成功后再开一个新会话测试登录。如果新会话能登录旧会话断开没关系如果新会话登录失败立刻执行回滚操作。这套方法我在多次远程升级中验证过每次都能成功把差点被锁在门外的风险降到最低。记住一个原则升级 sshd 时永远不要只有一个可用会话永远不要手动重启后死等回显。5. 升级验收与多节点批量下发验证什么才算真成功5.1 单机验收清单不只是 ssh -V升级完成后很多同事觉得跑一条ssh -V看到版本号 9.9p2 就算验收完成。这远远不够。你不能只看二进制版本还要验证功能链路。我在每个节点上会按下面的清单过一遍验证项操作预期结果OpenSSL 版本/usr/local/ssl/bin/openssl version显示 3.5.6OpenSSH 版本/usr/local/openssh/bin/ssh -V显示 9.9p2服务运行状态ps -efgrep sshd动态库链接ldd /usr/local/openssh/sbin/sshdgrep ssl密码登录用普通用户 ssh 登录登录成功密钥登录用 authorized_keys 免密登录登录成功SFTP 功能sftp -P 22 user127.0.0.1能连接并上传下载文件TCP 转发本地端口转发一条命令转发正常配置文件检查sshd -T无报错这里特别看一下 SFTP 这个项目。升级后 sftp 是否可用取决于 sshd_config 里的 Subsystem 配置。很多旧系统的配置写的是Subsystem sftp /usr/lib/openssh/sftp-server而新版 OpenSSH 的 sftp-server 安装路径是 /usr/local/openssh/libexec/sftp-server如果你不改这一行用户连接 SFTP 时会直接报subsystem request failed on channel 0。升级后必须把 Subsystem 一行改成Subsystem sftp /usr/local/openssh/libexec/sftp-server改完记得重启 sshd。这个坑特别隐蔽因为 SSH 登录看着完全正常只有 SFTP 传输时会暴露。5.2 批量下发的脚本骨架当待升级的节点数量超过 5 台时逐台手动敲命令效率太低也容易漏操作。我写过一个简单的批量脚本思路是先传包再远程执行最后收集日志。前提条件控制机到各目标机之间已经配置了 SSH 密钥免密登录或者你有统一的运维账号。#!/bin/bash NODES192.168.10.11 192.168.10.12 192.168.10.13 SRC_DIR/opt/upgrade LOG_DIR/opt/upgrade/logs for node in $NODES; do echo $node start rsync -av --delete $SRC_DIR/ root$node:$SRC_DIR/ $LOG_DIR/rsync_$node.log 21 ssh root$node bash $SRC_DIR/upgrade.sh $LOG_DIR/upgrade_$node.log 21 if [ $? -eq 0 ]; then echo $node upgrade OK $LOG_DIR/summary.log else echo $node upgrade FAILED $LOG_DIR/summary.log fi done目标机器上放一个 upgrade.sh内容就是前面两个编译安装流程的合集但要注意在脚本开头加 set -e任何一个阶段失败就立刻退出并且把当时的错误日志写下来避免半成品状态。在批量执行之前我强烈建议先挑一台配置最复杂的机器做灰度单独跑完整流程。灰度跑通了再对剩余节点批量执行。内网环境最忌讳一把梭全量操作万一哪台机器系统库版本和别的节点不一样批量脚本在那一台上卡住后续所有节点都被拖累。5.3 服务重启顺序与变更通知如果你升级的是核心业务服务器服务重启顺序必须提前规划。我的经验是先重启 sshd 服务本身验证 SSH 登录正常再通知业务团队测试依赖 SSH 的应用链路比如 SFTP 文件传输、rsync 备份、自动化采集脚本。不要一升级完就立刻所有服务一起重启那会把问题复杂化分不清故障到底是 OpenSSH 引起的还是其他系统变更引起的。另外变更操作要写入运维平台的变更单。即使内网环境没有严格的变更审批流程至少要在群里同步一下时间点和影响面。因为 SSH 服务重启虽然快但所有通过 SSH 执行的长连接、rsync 任务、自动化作业都会被打断。我曾经在一次凌晨裸奔升级时忘记通知数据同步任务结果第二天发现某张业务表的增量数据少了半天追查了很久才发现是升级时把 rsync 会话打断了教训非常深刻。6. 回滚预案与应急恢复最坏情况下的 5 分钟恢复路线6.1 升级前必须做的备份清单回滚方案的价值在于升级失败了你能在最短时间内回到可用状态。所以升级前必须把以下东西全部备份缺一不可备份项备份命令用途原 sshd 二进制cp /usr/sbin/sshd /usr/sbin/sshd.bak.$(date %Y%m%d)直接替换回滚sshd 配置文件cp -a /etc/ssh /etc/ssh.bak.$(date %Y%m%d)配置还原Host Keycp -a /etc/ssh/ssh_host_* /opt/upgrade/bak/防止 host key 丢失告警systemd overridecp -a /etc/systemd/system/sshd.service.d /opt/upgrade/bak/删除或还原PAM 配置cp -a /etc/pam.d/sshd /opt/upgrade/bak/密码认证异常时对照原 OpenSSL 库信息ldconfig -pgrep ssl 仅记录不需复制所有备份统一放到 /opt/upgrade/bak 目录下备份目录本身只读、不可被后续操作覆盖。我曾经遇到过一个问题在批量脚本里加了自动备份但因为备份命令写错了路径把所有节点的备份文件都覆盖成了同一个版本回滚时根本找不到原始二进制只得从另一台同配置机器拷过来非常被动。6.2 三类故障场景的回滚步骤升级过程中最常见的故障我归纳成三类分别给出回滚路径。第一类编译阶段失败。比如 make 报错、configure 检查不通过。这种场景系统还没被改动回滚非常简单删除 /usr/local/openssh 和 /usr/local/ssl 目录如果 OpenSSL 编译成功但 OpenSSH 失败然后确认系统原 sshd 还在运行或能正常启动。如果编译失败发生在 OpenSSL 阶段系统原 OpenSSH 还在链接系统 OpenSSL一切照旧直接清理失败产物即可。第二类sshd 起不来。执行systemctl restart sshd后服务状态为 failed或者监听端口没有起来。此时执行回滚# 还原原 sshd 二进制 cp /usr/sbin/sshd.bak.$(date %Y%m%d) /usr/sbin/sshd # 删除 override 文件恢复 systemd 默认单元 rm -rf /etc/systemd/system/sshd.service.d systemctl daemon-reload systemctl start sshd # 验证 sshd -T systemctl status sshd这里不用动 /usr/local/openssh 目录留着没问题以后排查时还能对比新旧二进制的差异。第三类sshd 能起但登录异常。可能是密码登录失败、key 认证失效、SFTP 无法使用。回滚时除了还原二进制和 override 之外还要重点检查 /etc/ssh/sshd_config 是否被新安装过程自动改动过。源码编译默认不会覆盖已有配置文件但如果你手动执行过 make install 且配置文件路径有误可能出现新二进制读取了新的默认配置。回滚后把之前备份的 /etc/ssh 目录整体还原然后重启 sshd。6.3 回滚后必须处理的遗留问题回滚不是把原二进制放回去就结束了后面还有几个遗留问题需要处理。第一个是 known_hosts 冲突。如果你在升级和回滚过程中重新生成了 host key客户端连接时会报 host key 不匹配。解决办法是恢复备份的 ssh_host_* 文件然后重启 sshd让客户端重新信任。第二个是动态库残留。如果你在回滚时删除了 /usr/local/ssl 目录以前链接到新库的程序比如自己编译的其他工具会全部报 cannot open shared object file这时候你要么保留 /usr/local/ssl 不动推荐要么重编那些程序。第三个是日志和审计。回滚动作本身要记录到变更日志同时检查 /var/log/messages 或 journalctl 里是否有相关报错保留现场日志便于后续定位根因。很多人在回滚成功后急于清理现场把最有价值的报错日志删掉了后面再做升级时又会踩同样的坑。6.4 个人经验回滚过程中最容易翻车的三个细节第一次做 OpenSSH 回滚演练时我犯过不少低级错误这里挑三个最典型的提醒大家。第一个细节备份时只备份了二进制没有备份 /usr/lib/systemd/system/sshd.service 原文件。虽然绝大多数情况下 systemd 原 unit 文件不会变但如果你曾经手动改过它回滚时就会发现 restore 出来的 unit 和你记忆中的不一样服务起不来。所以除了 override 目录原 unit 文件也建议备份。第二个细节验证回滚成功时只在自己机器上试了一次 ssh 登录就宣告恢复。实际上回滚后应该执行一轮和升级验收一样的完整验证包括密码登录、key 登录、SFTP、转发因为回滚后的环境可能同时存在新旧库混用的情况功能异常往往隐藏得很深。第三个细节没有提前准备带外管理通道。如果服务器是物理机除了 SSH 之外iDRAC/IPMI/带外管理口必须有人知道怎么连如果是虚机云控制台或虚拟机的 vCenter 控制台要能访问。否则一旦 SSH 起不来且人在异地回滚方案做得再完美你也只能在办公室干瞪眼。最后再分享一个我在多次内网升级中的个人体会OpenSSL 和 OpenSSH 这种底层组件的升级技术本身并不复杂真正决定成败的往往是一堆细节有没有备份、有没有预留第二个会话、有没有提前检查和目标机器不匹配的依赖版本、有没有在批量执行前做灰度。内网环境的特殊之处在于试错成本极高一次失败的升级可能意味着整个集群的 SSH 通道全部失效。我自己后来养成了一个习惯在正式执行升级前先找一台配置完全相同但业务价值低的测试机跑通整个流程把每一步的截屏和日志保留下来。这听起来多花时间实际上反而更省时间因为你在测试机上把坑踩完了生产机上大概率一次通过。你现在如果手里也有几台内网机器等着升级不妨照着这篇文章的流程先做物料盘点再做单机灰度最后再批量推进。稳一点别急。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门