简介:面向 Rocky Linux 9.5(Blue 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 旁路装到/opt,OpenSSH 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/lib64 | libcrypto.so.3 / libssl.so.3 | openssl35-libs |
/opt/openssl-3.5.0/bin | openssl 命令行 | openssl35 |
/opt/openssh-10.0p1/sbin | sshd、ssh | openssh10 |
/opt/openssh-10.0p1/libexec | sftp-server、ssh-keysign | openssh10 |
/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_PATH,systemd 拉起的服务不继承登录 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 \ --libdir=lib64 \ 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--libdir=lib64是刻意的,跟系统库目录同名,后面 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.pm,make 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-service=sshd \ --with-privsep-path=/var/empty/sshd \ --with-privsep-user=sshd \ --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指到/opt,configure 会去那儿找libcrypto和头文件,不会误用系统的 3.2。
LDFLAGS里的-rpath是整套方案的关键一环,--enable-new-dtags让链接器生成RUNPATH而不是老的RPATH,两者在传递性上有区别,用新的更符合预期。--with-selinux在 Rocky 9 的 enforcing 模式下必须开,否则 PAM 会话阶段会出各种莫名其妙的拒绝。
用make编完不要make install,直接进 spec,因为我们要的是 rpm:
Name: 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-user=sshd \ --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_t,sshd 以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 覆盖了,其实是没有restart,ss
本文还有配套的精品资源,点击获取