简介:在Rocky Linux 9.5(Blue Onyx)x86_64正式环境下,这份OpenSSH/OpenSSL升级加固资源包面向系统运维与安全管理人员,用于解决默认SSH组件版本偏低、被扫描出安全漏洞以及手动编译升级依赖复杂等痛点,目标是将服务快速升级到OpenSSH 10.0p1和OpenSSL 3.5.0,并对sshd服务完成基础加固。资源压缩包内共包含6个文件,由5个RPM安装包和1个Shell脚本组成,整体压缩包体积仅约10.96MB;RPM包覆盖openssh服务端、客户端以及chkconfig、initscripts等关键依赖,脚本则按照正确顺序自动安装RPM、更新启动方式并接入systemd管理,省去了源码编译时匹配编译器和库版本的大量工作。目前已有205人学习下载,特别适合Rocky Linux 9.5服务器批量升级加固场景。使用者仅需将资源上传至目标机器后执行脚本,即可获得稳定运行的SSH服务并保持开机自启,后续可通过systemctl status sshd确认运行状态;对于等保整改、漏洞修复后需快速收敛风险的运维团队而言,这是一条低成本、低难度的升级路径。
1. 升级 OpenSSH 不只是换版本:等保复查把我逼成了 RPM 流水线
某次等保复测,扫描器把系统自带的 OpenSSH 8.7p1 翻了个底朝天,光高危 CVE 就列了好几页,评审意见很直接:升级到已修复版本。可生产机不是测试机,直接在源码目录 make install 会让 rpm 数据库再也管不住 ssh,下回安全加固又要重新折腾一遍。最后我把 OpenSSH 10.0p1 配着 OpenSSL 3.5.0 一起打成 rpm,做成一套 openssh 加固升级脚本,让版本、依赖、加固项全部变成可重复执行的产物。这篇文章就是整套流程的拆解笔记,适合正在做等保整改、又不想被源码编译坑第二次的运维和交付工程师。
2. 升级前环境摸底与依赖排雷:三个命令看清现状再动手
2.1 版本摸底:看系统、看 OpenSSL、看 SELinux
动手之前,先把家底盘清楚。我一般会在目标机器上依次跑下面这组命令,输出结果存到临时文件里作为升级前的快照。
cat /etc/redhat-release ssh -V 2>&1 openssl version getenforce systemctl is-active sshd ss -lntp | grep :22ssh -V和openssl version分别确认当前 SSH 与服务端依赖的 OpenSSL 版本;getenforce看 SELinux 是 Enforcing 还是 Disabled,这个状态决定了后面要不要做上下文恢复;systemctl is-active sshd确认服务当前是正常的,避免升级过程中带着一个半死不活的进程操作;最后ss检查 22 号端口实际监听情况。
这套命令的价值在于给升级画一条基线。如果原环境里 sshd 的配置已经被前人改得面目全非,或者防火墙规则本身就没放行,升级后再排查就没法判断是升级引入的问题还是历史遗留问题。我见过有人跳过这一步直接上生产,结果升级完才发现是旧版本身就起不来,白忙一晚上。
2.2 安装编译依赖:缺 dev 包会让你在 90% 时报错,尤其是 pam-devel
OpenSSH 的 RPM 打包依赖一堆开发库,缺哪个都在编译中段才报错,非常消耗耐心。我在 Rocky Linux 9.5 上通常一次性装齐。
dnf groupinstall -y "Development Tools" dnf install -y rpm-build rpmdevtools \ pam-devel zlib-devel libselinux-devel \ krb5-devel ncurses-devel gcc make perl这里重点说说几个包的用途。pam-devel是给--with-pam用的,少了它就算 configure 能过,make 也会在 pam 相关文件上报错,而且报错信息很容易让人误以为是代码问题。libselinux-devel对应--with-selinux,在 Enforcing 模式下编译时如果缺少这个包,生成的二进制会对 SELinux 策略支持不完整。rpm-build与rpmdevtools提供 rpmbuild 和目录初始化命令,krb5-devel用于 GSSAPI 认证支持,如果公司内部有基于 Kerberos 的认证体系,这个包必须装。
装依赖有个习惯性问题:不要图快把* -devel全部装上。全量安装虽然能少报错,但会引入大量无关库,RPM 打包时%files列表会变得很难控制,最后打出来的 rpm 可能带着一堆本不需要的.so依赖。
2.3 备份与回滚预案:主机密钥和原版 RPM 就是你的后悔药
升级 OpenSSH 最怕的不是编译失败,而是升级后连不上机器。所以在编译之前,我必须先把三样东西备份好:原版 RPM 包、配置文件、主机密钥。
mkdir -p /root/backup-ssh-$(date +%Y%m%d) cd /root/backup-ssh-$(date +%Y%m%d) rpm -qa | grep -E "^(openssh|openssl)" > rpm-list.txt cp -a /etc/ssh /root/backup-ssh-$(date +%Y%m%d)/etc-ssh cp -a /etc/pam.d/sshd /root/backup-ssh-$(date +%Y%m%d)/ tar czf ssh-host-keys.tar.gz /etc/ssh/ssh_host_*rpm -qa导出的清单专门用于回滚时对照原版本号。有些厂内镜像源保留了旧版本 rpm,回滚时直接dnf install旧版本号即可;如果源里没有,就要靠rpm -Uvh强制装回备份的 rpm 文件。/etc/ssh整个目录复制保存,因为里面除了配置文件还有ssh_host_*密钥和moduli文件。主机密钥尤其重要,不备份的话升级后重启 sshd 会重新生成新密钥,客户端会出现 host key 变更告警,逼着用户去核对指纹。
备份完我会顺手tar tzf ssh-host-keys.tar.gz看一眼包内文件是否完整,这一步花十秒钟,却能避免回滚时才发现在备份环节就翻车的尴尬。
3. 编译 OpenSSL 3.5.0 并打入 RPM:这个前置步骤不做好,后面全白搭
3.1 为什么不能直接让 OpenSSH 10.0p1 链系统自带的 OpenSSL
Rocky Linux 9.5 自带的 OpenSSL 版本通常是 3.0.x,理论上 OpenSSH 10.0p1 可以链接它编译通过。但实际生产中我们要考虑两件事:一是新版 OpenSSH 对某些密码学算法有更强的偏好,依赖新版本库的特性;二是直接升级系统 OpenSSL 会牵连 Nginx、Apache、PostgreSQL 等大量服务,风险不可控。
我采用的方案是给 OpenSSL 3.5.0 指定独立安装路径/opt/openssl,让 OpenSSH 单独链接这套新库。这样系统自带的 OpenSSL 保持不动,其他服务继续走旧库,互不影响。代价是编译 OpenSSH 时要明确指定--with-ssl-dir=/opt/openssl,并且处理运行时动态库搜索路径。
很多人会在这一步图省事,直接 ./configure 然后 make install 把 OpenSSL 装到/usr/local就完事。这个做法不是不行,但后续维护时/usr/local/lib64会被各种手动编译的库塞满,而且 rpm 数据库里完全没有记录,想彻底卸载只能手动清文件。所以我坚持把 OpenSSL 也打成 rpm,至少卸载和升级是有据可查的。
3.2 Configure 参数拆解与 RPM 化打包
先看编译 OpenSSL 3.5.0 的 Configure 命令。这里我选了linux-x86_64平台配置,这是 OpenSSL 官方提供的最贴近 Rocky Linux 9.5 的 x86_64 配置。
cd /root/rpmbuild/BUILD/openssl-3.5.0 ./Configure linux-x86_64 \ --prefix=/opt/openssl \ --openssldir=/opt/openssl \ enable-fips \ no-ssl2 no-ssl3 no-sctp \ shared make -j$(nproc) make install_sw install_ssldirs DESTDIR=/root/rpmbuild/BUILDROOT/openssl-3.5.0--prefix=/opt/openssl指定了安装根目录,所有头文件、库和二进制都落在这个目录下。enable-fips是很多等保单位和金融机构的硬性要求,开启后系统里会有openssl fipsinstall可用来做 FIPS 模式校验。no-ssl2 no-ssl3是安全基线,直接禁用这两个已经淘汰的协议版本,避免后续扫描又报 CVE。
注意最后一行我用了DESTDIR指定了打包用的 buildroot 路径。RPM 打包时不会直接往系统写入文件,而是先装进这个临时目录,再由 rpmbuild 从这里取文件打入包内。这是规范做法,少了这一步,rpmbuild 只能去系统路径里找文件,就会出现"编译成功但打包空壳"的怪象。
打包对应的 spec 文件我一般新建一个openssl-3.5.0.spec,%files段直接引用/opt/openssl下的实际文件即可。Release 号我习惯设为自定义值,比如1.rocky9,方便和官方源里的包区分。
3.3 ldconfig 与 PATH:装错位置比不装更吓人
OpenSSL 装完后,第一件事是把新库的路径写进动态链接器配置。
echo "/opt/openssl/lib64" > /etc/ld.so.conf.d/openssl-3.5.0.conf ldconfig ldconfig -p | grep libcryptoldconfig会刷新系统的动态链接器缓存,让运行在默认环境下的程序也能根据libcrypto.so.3这样的名字找到/opt/openssl/lib64下的库文件。最后一行 grep 是为了确认新库已被加载,如果输出里同时出现多个路径下的libcrypto.so.3,就要留意链接顺序问题。
有一个很容易踩的坑:不要为了验证就把/opt/openssl/bin加进/etc/profile的 PATH,或者直接替换/usr/bin/openssl。系统里很多脚本和程序依赖/usr/bin/openssl的特定输出格式,你切到 3.5.0 后某些命令的输出格式变了,可能导致上层应用判断异常。要验证新版本,直接调用全路径/opt/openssl/bin/openssl version即可,这才是最安全的方式。
/opt/openssl/bin/openssl version OpenSSL 3.5.0 1 Jan 2025 (Library: OpenSSL 3.5.0)如果输出里的版本号和编译时一致,说明安装链路是通的。另外建议顺手跑一遍make test,OpenSSL 的测试套件覆盖很全,虽然耗时大概十几分钟,但能提前暴露 FIPS 模块没装好、某些算法在 x86_64 上行为异常这类问题。编译 OpenSSH 前先让 OpenSSL 通过测试,后面少熬夜。
4. 打包 OpenSSH 10.0p1 的 RPM:从 spec 修改到生产上线
4.1 准备 rpmbuild 目录结构:官方 spec 需要大改
OpenSSH 源码包里自带一份openssh.spec,但它默认针对的是 OpenSSH 的通用环境,很多参数没适配 Rocky Linux 9.5。我一般这样初始化目录并放置源码。
rpmdev-setuptree cp openssh-10.0p1.tar.gz /root/rpmbuild/SOURCES/ cp openssh.spec /root/rpmbuild/SPECS/rpmdev-setuptree会自动创建BUILD、BUILDROOT、RPMS、SOURCES、SPECS五个目录。源码包放进SOURCES,spec 放进SPECS。注意源码包文件名必须和 spec 里的Source0一致,否则 rpmbuild 直接报错找不到文件。
官方 spec 里可能还会引用openssh-10.0p1.tar.gz之外的补丁文件,例如 PAM 相关的补丁。如果源码包里没有单独提供这些补丁,我建议直接把 spec 里的Patch0、Patch1等对应的行注释掉,然后基于系统自带的 openssh 配置逻辑重新补 PAM 设置。不要试图把一个给其他发行版用的补丁强行打在 Rocky Linux 上,会打出一堆语义冲突。
4.2 修改 spec 文件:configure 段是整份文件的心脏
spec 文件里最关键的是%configure段。官方默认可能是直接./configure,我需要显式指定新 OpenSSL 路径、PAM、SELinux 支持。
%build ./configure \ --prefix=/usr \ --sysconfdir=/etc/ssh \ --with-ssl-dir=/opt/openssl \ --with-pam \ --with-selinux \ --with-md5-passwords \ --with-privsep-path=/var/empty/sshd \ --with-systemd make -j$(nproc)--prefix=/usr让 ssh 命令、sshd 服务都装在标准位置,这样systemctl能找到服务文件,/usr/bin/ssh也能被现有调用脚本找到。--sysconfdir=/etc/ssh保证配置文件路径和旧版一致,不会出现配置漂移。--with-ssl-dir=/opt/openssl是这次升级的核心参数,直接指定头部和库的搜索路径。--with-pam保留系统 PAM 认证流程,--with-selinux配合 Enforcing 模式。--with-privsep-path=/var/empty/sshd指定特权分离目录。
/var/empty/sshd这个目录是 OpenSSH 运行时的降权目录,它的属主必须是 root,权限必须是 755。打包安装脚本里我会顺手加上目录创建和权限修正:
mkdir -p /var/empty/sshd chmod 755 /var/empty/sshd chown root:root /var/empty/sshd如果这个目录的属主或权限不对,sshd 启动时会提示权限太开放或目录不存在,属于很隐蔽的坑,日志里只会出现一句含糊的privilege separation directory报错。
4.3 rpmbuild 到上线:永远是 rpm -Uvh,不是 rpm -e
spec 改好后开始编译,这一步会调用编译器把 OpenSSH 源码打成二进制 rpm。
rpmbuild -ba /root/rpmbuild/SPECS/openssh.spec ls /root/rpmbuild/RPMS/x86_64/-ba参数表示同时构建二进制包和源码包。如果编译过程中报错,优先查看BUILD目录下的 config.log,里面是 configure 阶段输出的详细日志,比终端里的报错信息完整得多。
编译产物一般会包含openssh、openssh-server、openssh-clients、openssh-askpass等几个 rpm。安装时我要强调一个反直觉的点:不要写rpm -e openssh-server再rpm -ivh新包。删除再装会让/etc/ssh/sshd_config从系统里消失,新包安装时如果检测不到旧配置,会选择使用默认配置,你的历史加固项全部失效。正确做法是直接升级:
rpm -Uvh /root/rpmbuild/RPMS/x86_64/openssh-*.rpm systemctl daemon-reload systemctl restart sshd ssh -Vrpm -Uvh是升级模式,会保留原配置文件,并在必要时生成.rpmnew文件存放新版的配置差异。systemctl daemon-reload强制 systemd 重新加载服务文件,避免新安装的服务单元没被识别。重启后立刻ssh -V确认版本,这是最直接的验收。
5. 避坑清单:编译之后、重启之前最容易翻车的五个位置
5.1 起不来:libcrypto.so.1.1 加载失败
现象:rpm 安装完成后执行systemctl restart sshd,终端报错:/usr/sbin/sshd: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file。
原因:sshd 二进制在编译时链接到了系统原有的 OpenSSL 3.0 库,运行时却找不到对应的libcrypto.so.1.1版本号。这通常是因为 spec 里的--with-ssl-dir没生效,或者 rpath 没写入二进制。
解决:先用ldd /usr/sbin/sshd | grep -E "crypto|ssl"看实际链接路径。如果指向/usr/lib64而不是/opt/openssl/lib64,说明编译时没找对新 OpenSSL。一个临时补救办法是用 chrpath 修改二进制的 rpath:
chrpath -r /opt/openssl/lib64 /usr/sbin/sshd systemctl restart sshd但这不是长久之计,rpath 会被 RPM 升级覆盖。彻底方案是回到 spec 里确认 configure 确实带上了--with-ssl-dir=/opt/openssl,并检查编译日志里有没有显式提示找到了新版本头文件,然后重新 rpmbuild。
5.2 SELinux 上下文:端口在听,防火墙放行,你依旧连不上
现象:sshd 进程正常监听 22 端口,firewalld 也放行了 ssh 服务,但客户端连接始终卡在认证阶段,日志里没有明显报错,只有 SELinux 的avc: denied记录。
原因:OpenSSH 用 RPM 升级后,/etc/ssh下部分文件或/usr/sbin/sshd本身的安全上下文是usr_t或etc_t,而不是 SSHD 需要的sshd_exec_t、sshd_etc_t。SELinux 在 Enforcing 模式下直接拦截。
解决:对关键路径做一次递归恢复上下文。
restorecon -Rv /etc/ssh /usr/sbin/sshd /var/empty/sshd ls -Z /usr/sbin/sshdrestorecon会从 SELinux 策略数据库里读取正确标签并应用。ls -Z验证结果,正常时 sshd 的标签应该是system_u:object_r:sshd_exec_t:s0。这一步应该在 rpm 安装完、重启服务前就做,否则就会出现端口通了却连不进去的尴尬局面。
5.3 PAM 认证锁人:密码正确也被拒
现象:升级完成后,用户输入正确的密码也登录不进去,journalctl -u sshd显示类似pam_unix(sshd:auth): authentication failure。
原因:编译时没加--with-pam,导致 sshd 编译的 PAM 支持不完整;或者/etc/pam.d/sshd文件里引用了新版本不存在的模块。Rocky Linux 9.5 默认的 PAM 配置里包含pam_unix.so和pam_systemd.so,如果编译时没有把 systemd 相关支持链进去,就会出现认证通过但会话建立失败的情况。
解决:先确认/etc/pam.d/sshd存在,权限 644,属主 root。然后查看编译时是否带了--with-pam和--with-systemd。如果没带,重新配置再打包。临时应急可以先把系统原有 PAM 配置恢复备份再重启服务:
cp /etc/pam.d/sshd.rpmnew /etc/pam.d/sshd 2>/dev/null systemctl restart sshd把.rpmnew覆盖回主配置是快速让 PAM 恢复可用的手段,但覆盖前建议 diff 对比一下新旧配置的差异,别把新版本特意加固的 PAM 逻辑又覆盖掉了。
5.4 把自己锁门外:回滚窗口这样开
现象:远程操作时执行完rpm -Uvh和systemctl restart sshd,连接突然断开,之后再连就报错或超时,整个人被锁在机房外面。
原因:新配置里PasswordAuthentication no直接生效,而你的公钥没写进 authorized_keys;或者主机密钥变了导致客户端拒绝连接。
解决:升级前先开一个不依赖 ssh 的带外会话,比如机房控制台或云厂商的 VNC。同时在远程会话里设置一个延迟回滚的定时任务,给自己留 120 秒后悔药。
(sleep 120 && rpm -Uvh /root/backup-ssh-$(date +%Y%m%d)/openssh-*.rpm && systemctl restart sshd) &这条命令会在 120 秒后自动降级回原版本并重启服务。如果这条命令执行前你就发现新环境有问题,在它触发前手动连接另一个会话,敲kill %1取消回滚即可。注意,这行命令应该在升级操作前就挂起来,不是升级失败后才写。
5.5 firewalld 没放行:服务起来了却出不了门
现象:升级后ss -lntp显示 sshd 在 22 端口正常监听,但客户端连接直接超时,防火墙运维检查也没看到明显拦截。
原因:原环境 firewalld 一直开着,但之前 sshd 是通过旧版本 RPM 安装时自动注册的 service 规则放行。升级后新 RPM 的服务文件路径变了,firewalld 里虽然写着 ssh 服务,实际匹配的端口服务上下文没有刷新。
解决:不要依赖 firewalld 的自动集成,手动放行并验证。
firewall-cmd --permanent --add-service=ssh firewall-cmd --reload firewall-cmd --list-all--add-service=ssh会添加基于服务名的放行规则,--reload使规则生效。最后--list-all检查输出里是否同时存在ssh服务。如果公司安全策略要求限定来源 IP,可以改成firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept',比全量放行更稳妥。
6. 最后的落地:把验证做成脚本,把回滚变成习惯
6.1 十分钟一键巡检脚本
升级加固完成不等于结束,等保复查时对方要的是可验证的审计结果。我习惯把版本、端口、加固项、SELinux、防火墙这些检查点写成一个脚本,每次维护后跑一遍,输出贴在变更单里即可。
#!/bin/bash # sshd 升级加固巡检脚本 echo "== SSH 版本 ==" ssh -V 2>&1 echo "== 监听端口 ==" ss -lntp | grep sshd echo "== 关键加固项 ==" grep -E "^(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|MaxAuthTries|ClientAliveInterval|X11Forwarding)" /etc/ssh/sshd_config echo "== SELinux 上下文 ==" ls -Z /usr/sbin/sshd echo "== 防火墙放行 ==" firewall-cmd --list-all | grep services脚本里的grep -E只匹配形如PermitRootLogin no这类没有被注释掉的配置项。加固项预期值应该是:PermitRootLogin no、PasswordAuthentication no、PubkeyAuthentication yes、MaxAuthTries 4、ClientAliveInterval 300、X11Forwarding no。如果输出里某一项没出现,说明它被注释了,当前生效的是编译默认值,需要手动打开。
在脚本开头先跑版本检查,是因为 ssh 版本偶发回退并不显而易见——比如某个后续 yum 操作把旧版拉回来。把这组检查脚本放进 cron 每周跑一次,输出追加到/var/log/ssh-audit.log,比临时抱佛脚靠谱得多。
6.2 回滚的肌肉记忆
我第一次做 OpenSSH 升级时也翻过车,在没开带外会话的情况下直接远程重启了 sshd,结果断连后只能在机房门口吹冷风。从那以后,我每次做 ssh 升级都强制自己先走一遍固定流程:备份三件套、开带外通道、挂延迟回滚、验证巡检脚本,全部就绪才碰rpm -Uvh。这套流程现在基本成了肌肉记忆,哪怕只有一台测试机也照走一遍。
另外有个细节值得一提:升级后第一次测试登录时,尽量用一个新的本地会话窗口,不要复用升级前那个连接。旧连接经过 sshd 重启后状态本身就不可靠,复用它会让你误判新服务是否正常。用新窗口重连,能连上就说明整条链路通了。这个习惯救过我好几回,希望帮到你。
本文还有配套的精品资源,点击获取