所有看似简单的运维操作,背后都有一个等着你跳的坑。 ------ 一个过来人的忠告,但通常你听过就忘,直到自己踩进去。
一个普通的周二下午
那天下午其实挺正常的。我刚巡检完一套系统,正在工位上跟同事扯淡,安全员小李走过来,表情微妙地递过来一份报告。
"张哥,等保测评的报告出来了。有几个高危漏洞需要修复。"
我心里毫无波澜------不就高危么,每年都一样,小 CASE!
我接过来翻了两页。得分还行,比去年有进步。正想说"不错不错",翻到漏洞清单那一页------
SSH 高危漏洞,OpenSSH 版本过低(CVE-2023-xxxx)。整改时限:15 个工作日。
我当时的表情大概经历了三个阶段:
第一阶段:哦,新系统嘛,正常都会有这个漏洞。 第二阶段:不就是升级个 SSH 嘛。 第三阶段:小事,10 分钟搞定一台。
Flag 已立好。后面的故事不出预料地,Flag 倒了!
自信的准备工作
用 ssh -V 看了一眼当前版本:
yaml
OpenSSH_6.6.1p1, OpenSSL 1.0.1e-fips 11 Feb 2013
CentOS 7 自带的经典老款。都过了多少年了,也该升了。
上网查了一下最新稳定版本------OpenSSH_9.x。好家伙,从 6 到 9,跳了三个大版本。不过没关系,信心满满。
习惯性地先查了一圈资料,搜了几篇教程,大概流程都差不多:
- 装依赖
- 编译
- 替换
- 重启服务
- 完事
看起来毫无波澜。
我甚至还跟新同事说了一嘴:"SSH 升级,简单的,一会儿给你演示。"
------ 这句话后来成了我在这件事上说的最后悔的一句话,没有之一。
先试试 Yum 这条路
我一开始想走最简单的路:先试试 yum update openssh 能不能搞定。
结果系统告诉我没有可更新的包。CentOS 7 官方源里的 OpenSSH 早就停更了,7.4p1 就是尽头。
好吧,换阿里云的源试试。
先备份当前 yum 配置文件:
bash
mkdir -p /etc/yum.repos.d/backup
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/
下载阿里云的配置文件:
bash
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
chmod 644 /etc/yum.repos.d/CentOS-Base.repo
清理缓存并更新源:
bash
yum clean all
yum makecache
啊哈!换源成功,这回可以试试升级了:
bash
yum update openssh
结果只有 openssh 7.x 的包。不行,那试试 epel-release?
bash
yum install epel-release
yum --enablerepo=epel update openssh
哦豁!不出所料,依然不是最新版本,而且新版 OpenSSH 依赖的 OpenSSL 版本跟系统自带的冲突了。
没办法了,只能上源码编译了。这条路终究躲不过去。
升级 OpenSSL:第一个连环坑
首先确保编译环境就位:
bash
yum install -y "Development Tools"
下载 openssl-1.1.1w 源码,解压,./config,看起来一切正常。
然后 make -j$(nproc) && make install。
编译完成!测试一下:
bash
openssl version -a
显示版本为 OpenSSL 1.1.1w。OK!OpenSSL 升级完成!
但是------忘了检查其他应用会不会受影响。升级完关键库,其他依赖旧版 SSL 的服务会不会崩?
赶紧检查了一下。还好,其他应用没有异常,虚惊一场。但以后再升关键库,不能再这么鲁莽了。
确认新库的加载路径:
bash
# 检查 ldconfig 能否找到新装的库
ldconfig -p | grep ssl
# 如果 /usr/local/lib64 不在搜索路径中,手动加一下
echo "/usr/local/lib64" > /etc/ld.so.conf.d/openssl-1.1.1w.conf
ldconfig
验证新版本:
bash
/usr/local/bin/openssl version
输出正常:OpenSSL 1.1.1w。
好,前置条件总算满足了。
正菜来了:编译 OpenSSH
下载 openssh-9.9p2 源码包,解压。
./configure 这一步是有讲究的。我查到的教程推荐这样:
bash
./configure --prefix=/usr/local --sysconfdir=/etc/ssh
--prefix=/usr/local:装到独立目录,不覆盖系统原生 SSH--sysconfdir=/etc/ssh:配置文件沿用原路径,不影响现有配置
然后 make && make install。
编译通过了,没有报错,一切顺利。
等等,顺利得让我有点不安。
果然。我试着用另一个窗口 SSH 连接这台机器------
ssh: connect to host 192.168.x.x port 22: Connection refused
我盯着这个报错看了大概 3 秒钟,脑子里飞速闪过各种可能。
还好当前会话还连着。检查 sshd 状态:
bash
systemctl status sshd
显示 active (running)。
诶?那为什么连不上?
再仔细看看......
bash
ps aux | grep sshd
发现了两个 sshd 进程:一个是 /usr/sbin/sshd(旧版),一个是 /usr/local/sbin/sshd(新版)。
两个 sshd 在打架。
旧版的 sshd 被 systemd 管着,开机就启动了,占着 22 端口。新版的 sshd 虽然装好了,但根本没跑起来------端口被占了,它起不来。
而且更坑的是,新旧两版的配置文件虽然都指向 /etc/ssh/sshd_config,但 OpenSSH 9.x 的配置项跟前几代有差异------有些参数废弃了,有些新参数旧版不认。如果旧版的 sshd_config 里有新版不兼容的配置,新版 sshd 直接拒绝启动,而且日志里给的错误信息特别隐晦。
关键问题:你没有告诉 systemd 要启动新版本的 sshd。
新版的 systemd 服务文件 /usr/lib/systemd/system/sshd.service 里写死的二进制路径还是 /usr/sbin/sshd(旧版)。我把新的二进制复制过去行不行?行,但下次 yum update 一跑,又被覆盖了。不是长久之计。
手动抢救
先把旧版 sshd 停掉:
bash
systemctl stop sshd
确认端口释放了:
bash
ss -tlnp | grep 22
没输出,端口已经释放。
手动启动新版 sshd:
bash
/usr/local/sbin/sshd
没报错。检查进程确认:
bash
ps aux | grep sshd | grep -v grep
新版 sshd 进程在跑了。
赶紧试另一个窗口------
连上了!
bash
ssh -V
输出确认:
yaml
OpenSSH_9.9p2, OpenSSL 1.1.1w 11 Sep 2023
成了。
长舒一口气。看看表,从开始到解决,用了将近 3 个小时。
说好的 10 分钟呢?
但等等
现在新版 sshd 只是被我手动跑起来的。如果机器重启了,systemd 还是会拉起旧版的 sshd。得把服务配置也改过来。
修改 /usr/lib/systemd/system/sshd.service,把 ExecStart 路径指向 /usr/local/sbin/sshd,然后重新加载并重启:
bash
systemctl daemon-reload
systemctl restart sshd
确认服务正常:
bash
systemctl status sshd
搞定。这才算真正升级完了。
本篇的教训(不吐不快)
你以为这就完了?天真。
就在我沉浸在"搞定了"的喜悦中时,手机震了。安全员小李发来一条消息:
"张哥,这次 SSH 漏洞涉及的主机我整理好了,你看看,一共 15 台。"
我盯着手机屏幕,嘴角的笑容慢慢凝固。
屏幕上那 15 行主机名,每一行都像在嘲笑我。
15 台。每台 3 小时。那就是 45 个小时。
我开始认真思考一个哲学问题:这个班是非上不可吗?
------ 下一篇,给你讲讲我是怎么从"一台 3 小时"进化到"十五台 1 小时"的。
📌 技术要点(给不想看故事的人):
这篇涉及的知识点,先列出来,后面会有专门的标准方案篇:
- 升级前先确认带外管理可用(iDRAC / iLO / IPMI),这是你翻车后的最后退路。云服务器的话,确认控制台能正常登录。
- 生产环境不建议用 yum update 跳大版本升级 SSH。 CentOS 7 官方源不会提供 OpenSSH 9.x,走源码编译几乎不可避免。
- 编译前先搞清新旧两者的安装路径差异。 默认安装(
/usr)和独立安装(/usr/local)各有优劣。选了独立安装就要同时处理 systemd 服务文件的路径。 - 升级 OpenSSH 前可能要先升 OpenSSL。 这是最常见的连环依赖问题。注意 OpenSSL 新版本默认装到
/usr/local/,编译 OpenSSH 时要确认 ldconfig 能找到它。 - systemd 服务文件不会自动更新。 升级完记得改
sshd.service里的二进制路径,然后daemon-reload+restart。 - 换源后检查文件权限。 curl 下载的 repo 文件可能没有读取权限,记得
chmod 644。
下一篇:从一台到十五台------脚本化作战,批量升级的真实姿势