Rocky Linux 9.5 旁路升级 OpenSSH 10.0p1 加固实践 简介面向 Rocky Linux 9.5Blue Onyx系统运维与安全加固人员的 OpenSSH 升级套件用于把系统自带的旧版 SSH 服务平滑替换为 OpenSSH 10.0p1 与 OpenSSL 3.5.0 组合应对老版本协议与加密算法薄弱、安全合规检查不通过等常见问题适合具备一定 Linux 基础、需要在内网批量处置 sshd 的工程师使用。压缩包共 6 个文件、约 10.96MB其中 5 个为 x86_64 架构 RPM 包覆盖 openssh 服务端与客户端以及 chkconfig、initscripts 等配套依赖另有 1 个 shell 脚本 upgrade_ssl_ssh.sh 负责串联升级流程。全部离线打包便于在无外网的生产环境直接部署。资源已有 210 人学习下载。借助现成的二进制包与脚本读者可省去自行编译 OpenSSL、手工打包 RPM 的繁琐过程较快完成版本替换并保留回滚余地同时参考脚本中的执行顺序与参数设置梳理出契合自身环境的 sshd 加固与验证思路。1. 从 8.7p1 到 10.0p1这台机器为什么不能直接换血一次内网基线核查扫描器把 Rocky Linux 9.5 上一台跳板机的 OpenSSH 8.7p1 标成高风险要求限期升到 10.x。麻烦在于这台机器上跑着业务系统/usr/lib64/libssl.so.3被 dnf、curl、python 一堆东西共用直接编译覆盖基本等于自找停机。最后的做法是OpenSSL 3.5.0 旁路装到/optOpenSSH 10.0p1 编成独立 rpm包名带 10 后缀与系统 openssh 共存sshd 用 systemd drop-in 切过去最后用一份sshd_config.d落地 openssh加固。标题里那份 rockylinux9.5-ssh10.0p1-ssl3.5.0-rpm-x86-64 升级加固脚本干的就是这条链上的六件事编译、打包、装包、切服务、回滚、加固。适合手里压着 RHEL 9 系机器、被基线报告卡住、又不敢整版本换血的同学。2. 从 configure 到 rpm 包布局OpenSSL 3.5.0 为什么只能旁路装先确定一条原则这次升级只做加法不做替换。系统的 openssl 3.2 库保持不动我们的 3.5.0 装到独立前缀下由 sshd 用 RUNPATH 指过去。这样即使整套东西崩了dnf、curl、subscription-manager这些还活着至少能远程救回来。2.1 三条硬约束推出包布局第一条系统libssl.so.3不能碰。两个版本的主版本号相同、ABI 名字相同一旦ldconfig缓存被改机器上所有链接 libssl 的程序都可能加载到 3.5.0 的符号行为差异会在你最不想要的时候冒出来这种情况很难靠猜定位只能靠别让它发生。第二条dnf update不能把我们的成果冲掉。所以 rpm 包名不能叫openssh或openssh-server只能另起名字。第三条回滚必须是一条rpm -e能完成的动作意味着除配置文件外所有文件都得收在/opt下。三条约束推出来的布局是这样路径内容归属包/opt/openssl-3.5.0/lib64libcrypto.so.3 / libssl.so.3openssl35-libs/opt/openssl-3.5.0/binopenssl 命令行openssl35/opt/openssh-10.0p1/sbinsshd、sshopenssh10/opt/openssh-10.0p1/libexecsftp-server、ssh-keysignopenssh10/etc/systemd/system/sshd.service.d/override.conf指定新 sshd 启动脚本写入/etc/ssh/sshd_config.d/00-hardening.conf加固项openssh10-hardening有同学会问为什么不干脆把/opt/openssl-3.5.0/lib64塞进/etc/ld.so.conf.d/因为那是全局污染任何程序都可能被换库。也不能靠LD_LIBRARY_PATHsystemd 拉起的服务不继承登录 shell 的环境变量配了也白配。正解是链接期写 RUNPATH。2.2 OpenSSL 3.5.0 的编译参数逐条拆# 编译期依赖装完可以留着不影响运行 dnf install -y gcc gcc-c make perl perl-IPC-Cmd perl-FindBin zlib-devel tar -xzf openssl-3.5.0.tar.gz cd openssl-3.5.0 ./Configure \ --prefix/opt/openssl-3.5.0 \ --openssldir/opt/openssl-3.5.0/ssl \ --libdirlib64 \ shared \ zlib-dynamic \ enable-ec_nistp_64_gcc_128 \ no-ssl3 no-ssl3-method no-comp no-brotli \ no-weak-ssl-ciphers make -j$(nproc) make install_sw # 注意不是 install--libdirlib64是刻意的跟系统库目录同名后面 spec 里写路径时不用做分支判断shared生成带 SONAME 的动态库方便只替换库不动二进制no-ssl3 no-weak-ssl-ciphers是加固前置把协议层面的脏东西直接编译掉比事后在配置文件里一条条禁更彻底no-brotli避免因为系统 libbrotli 版本差异导致 configure 探测失败。最后用install_sw而不是install前者只装软件本身不装 man 手册和 HTML 文档包体积能小一半也避免覆盖/opt下别的东西。装完先自检别急着编 SSH# 看动态库路径是否已写入 RUNPATH readelf -d /opt/openssl-3.5.0/bin/openssl | grep -i -E rpath|runpath # 看版本与编译参数 /opt/openssl-3.5.0/bin/openssl version -aOpenSSL 3.x 的构建默认会给bin/openssl加库路径readelf一般能看到RUNPATH。如果输出为空说明这套构建没带后面编 OpenSSH 时就得自己在LDFLAGS里补别指望运行时再设环境变量。提示openssl version -a里的OPENSSLDIR应该指向/opt/openssl-3.5.0/ssl。如果它还打印/etc/pki/tls说明--openssldir没生效多半是解包目录里残留了上一次的configdata.pmmake distclean后重来。2.3 OpenSSH 10.0p1 的 configure 与 spec 关键段tar -xzf openssh-10.0p1.tar.gz cd openssh-10.0p1 ./configure \ --prefix/opt/openssh-10.0p1 \ --sysconfdir/etc/ssh \ --with-ssl-dir/opt/openssl-3.5.0 \ --with-pam --with-pam-servicesshd \ --with-privsep-path/var/empty/sshd \ --with-privsep-usersshd \ --with-selinux \ --with-zlib --with-kerberos5 --with-libedit \ --without-openssl-header-check \ LDFLAGS-Wl,-rpath,/opt/openssl-3.5.0/lib64 -Wl,--enable-new-dtags--sysconfdir/etc/ssh是让新旧两个 sshd 共用同一份配置目录好处是加固只写一遍坏处是万一配置里有 8.7p1 不认识的指令老 sshd 反而起不来这个坑在第 5 章细说。--with-ssl-dir指到/optconfigure 会去那儿找libcrypto和头文件不会误用系统的 3.2。LDFLAGS里的-rpath是整套方案的关键一环--enable-new-dtags让链接器生成RUNPATH而不是老的RPATH两者在传递性上有区别用新的更符合预期。--with-selinux在 Rocky 9 的 enforcing 模式下必须开否则 PAM 会话阶段会出各种莫名其妙的拒绝。用make编完不要make install直接进 spec因为我们要的是 rpmName: openssh10 Version: 10.0p1 Release: 1%{?dist} Summary: OpenSSH 10.0p1 built against OpenSSL 3.5.0, side-by-side License: BSD BuildRequires: systemd-rpm-macros AutoReqProv: no %global _prefix /opt/openssh-10.0p1 %global debug_package %{nil} %global __os_install_post %{nil} %description Side-by-side OpenSSH build, does not touch system openssh packages. %prep %setup -q -n openssh-10.0p1 %build ./configure --prefix%{_prefix} --sysconfdir/etc/ssh \ --with-ssl-dir/opt/openssl-3.5.0 --with-pam \ --with-privsep-path/var/empty/sshd --with-privsep-usersshd \ --with-selinux --with-zlib \ LDFLAGS-Wl,-rpath,/opt/openssl-3.5.0/lib64 -Wl,--enable-new-dtags make %{?_smp_mflags} %install make install DESTDIR%{buildroot} install -D -m0644 contrib/sshd.service %{buildroot}%{_unitdir}/sshd10.service %files %{_prefix} %{_unitdir}/sshd10.serviceAutoReqProv: no是整份 spec 里最容易被忽略、又最容易翻车的一行。不关掉自动依赖扫描rpmbuild 会把/opt/openssl-3.5.0/lib64/libcrypto.so.3识别成一个libcrypto.so.3()(64bit)依赖而系统里没有任何包 provides 它装包当场报依赖缺失。__os_install_post %{nil}则是跳过 strip 和 brp 检查避免 rpmbuild 对/opt前缀挑刺。构建命令mkdir -p rpmbuild/{SOURCES,SPECS,BUILD,RPMS,SRPMS} cp openssh-10.0p1.tar.gz rpmbuild/SOURCES/ cp openssh10.spec rpmbuild/SPECS/ rpmbuild -bb --define _topdir $PWD/rpmbuild rpmbuild/SPECS/openssh10.spec产物在rpmbuild/RPMS/x86_64/下rpm -qpl看一眼文件列表确认没有文件跑到/usr或/etc下尤其别出现/etc/ssh/sshd_config否则会和系统包抢文件装的时候直接冲突。3. 装机执行序列备份、切 unit、回滚的三层防护包编出来了真到机器上还有一段路。升级 ssh 最怕的不是装不上是装完连不进去而且手里没有第二条路。所以执行序列里备份和回滚的优先级高于装包本身。3.1 备份清单五样东西缺一不可对象命令用途已装包快照rpm -qa --qf %{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n /var/backup/rpm-list.txt回滚时比对配置目录tar czf /var/backup/etc-ssh-$(date %F).tgz /etc/ssh恢复配置systemd 单元systemctl cat sshd /var/backup/sshd-unit.txt恢复启动参数SELinux 上下文semanage fcontext -l /var/backup/fcontext.txt复核标签当前会话ss -tnp state established ( sport :22 )避免踢掉自己会话那一项不是备份是提醒。执行前先开第二个 SSH 窗口挂着并且确认这个窗口用的是密钥而不是密码因为加固之后密码登录就关了。血泪经验是只开一个窗口改 sshd改完一重启自己也进不去只能找人去机房按电源。3.2 脚本的四段结构#!/usr/bin/env bash set -euo pipefail BACKUP_DIR/var/backup/ssh-upgrade-$(date %Y%m%d%H%M) PKG_DIR/root/pkg NEW_SSHD/opt/openssh-10.0p1/sbin/sshd preflight() { [[ $EUID -eq 0 ]] || { echo must be root; exit 1; } rpm -q openssh10 /dev/null { echo openssh10 already installed; exit 1; } [[ -x $NEW_SSHD ]] || { echo target binary missing; exit 1; } $NEW_SSHD -t -f /etc/ssh/sshd_config || { echo config check failed; exit 1; } } backup() { install -d -m0700 $BACKUP_DIR cp -a /etc/ssh $BACKUP_DIR/etc-ssh systemctl cat sshd $BACKUP_DIR/sshd-unit.txt semanage fcontext -l $BACKUP_DIR/fcontext.txt ss -tnp state established ( sport :22 ) $BACKUP_DIR/sessions.txt } install_pkg() { dnf install -y --nogpgcheck $PKG_DIR/openssl35-*.rpm dnf install -y --nogpgcheck $PKG_DIR/openssh10-*.rpm } switch_unit() { install -d -m0755 /etc/systemd/system/sshd.service.d install -m0644 $PKG_DIR/sshd-override.conf \ /etc/systemd/system/sshd.service.d/override.conf semanage fcontext -a -t sshd_exec_t /opt/openssh-10.0p1/sbin/sshd 2/dev/null || true restorecon -v /opt/openssh-10.0p1/sbin/sshd /opt/openssh-10.0p1/libexec/* systemctl daemon-reload systemctl restart sshd sleep 2 ss -ltnp ( sport :22 ) }四个函数分别对应预检、备份、装包、切服务。预检里那句$NEW_SSHD -t -f /etc/ssh/sshd_config很关键它用的是新二进制去校验现有配置能在停服务之前把不兼容的指令挑出来比重启失败再翻日志快得多。装包顺序不能颠倒先 openssl35后 openssh10。虽然AutoReqProv关了之后 rpm 层面不检查但 sshd 一启动就要dlopen那个库库不在就直接退出。switch_unit里的 SELinux 两行别省。/opt下的文件默认标签是usr_tsshd 以sshd_t身份执行它会被拒绝表现是systemctl start失败、journalctl -u sshd里一行Permission denied光看日志其实看不出是 SELinux 干的得靠ausearch -m avc -ts recent才能看见。override 文件内容[Service] ExecStart ExecStart/opt/openssh-10.0p1/sbin/sshd -D $OPTIONS第一行空的ExecStart是必须的。systemd 对ExecStart的处理是追加语义不清空的话会出现两个启动命令sshd 会报 Address already in use 或者干脆只跑第一个。这个空赋值是 drop-in 覆盖命令行类指令的标准写法别漏。$OPTIONS保留原变量如果你的/etc/sysconfig/sshd里设过东西它会照常生效。3.3 回滚路径与三层防护回滚按逆序做四步systemctl stop sshd rm -f /etc/systemd/system/sshd.service.d/override.conf systemctl daemon-reload systemctl start sshd # 此时回到系统 8.7p1 rpm -e openssh10 rpm -e openssl35-libs openssl35顺序不能反。先摘 drop-in 再删包是因为 systemd 的 unit 里还指着/opt/openssh-10.0p1/sbin/sshd包一删systemctl start就找不到可执行文件机器彻底没 ssh。整个方案的抗风险能力来自三层隔离rpm 包名不同dnf不会认为冲突systemd 用 drop-in 而不是改原 unit系统包升级时我们的覆盖还在/etc/ssh用sshd_config.d而不是改主配置配置管理工具回写也不会冲掉。三层里任何一层失效另外两层还能让你连回去。注意一点dnf update之后 ssh 不会自动变回 8.7p1。有人看到升级完版本没变以为是 dnf 覆盖了其实是没有restartss本文还有配套的精品资源点击获取