简介面向 Rocky Linux 9.5 的 OpenSSH 10.0p1 与 OpenSSL 3.5.0 离线升级加固方案适合需要替换系统自带低版本 ssh 组件、又不想走源码编译的运维与安全加固人员使用可规避依赖冲突、升级后 sshd 无法启动等常见问题。压缩包共 6 个文件包含 5 个 x86_64 RPM 与 1 个 Shell 脚本RPM 覆盖 openssh 服务端、客户端及 chkconfig、initscripts 等配套依赖脚本负责升级流程编排与配置加固整包约 10.96MB可离线拷贝到内网主机直接执行。已有 210 人学习下载。读者可获得可直接复用的升级路径、依赖处理顺序与 sshd 服务验证方法脚本内置的加固项也便于对照梳理密钥算法、登录策略等安全基线。1. 为什么要在 Rocky Linux 9.5 上把 OpenSSH 顶到 10.0p1一次扫描告警引出的重装某次例行巡检安全侧丢过来一份 OpenSSH 加固基线报告一台 Rocky Linux 9.5 的机器上sshd 那一栏从 KEX 到 Ciphers 一路标红diffie-hellman-group14-sha1、aes128-cbc、hmac-sha1赫然在列。运维同事的第一反应是“内网机器跑了好几年没事”但基线报告不认这个说法。系统自带的 8.7p1 那一代很多算法在编译期就编进去了靠改sshd_config只能关掉一部分剩下的得从二进制层面换掉。这份资源解决的就是这件事在 Rocky Linux 9.5 x86_64 上把 OpenSSH 换成 10.0p1、OpenSSL 换成 3.5.0全部打成 RPM 包配一套可重复执行的升级加固脚本。适合手上有几台到几十台 RHEL 系机器、被基线扫描卡住、又不想半夜在生产机上make install的运维和交付工程师。下面按“先想清楚依赖、再打成包、最后上线和排查”的顺序拆开讲。2. 编译链路的先后手OpenSSL 3.5.0 与 OpenSSH 10.0p1 的依赖账2.1 为什么不能直接 rpm -Uvh 换掉系统的 openssl-libsRocky Linux 9.5 自带的 OpenSSL 已经站在 3.x 分支上soname 就是libssl.so.3。很多人看到版本号都是 3觉得rpm -Uvh上去顶多算个小版本更新装完 rpm 也确实不报错结果下一次敲 dnf直接一串undefined symbol。原因不玄学dnf 的 Python 扩展、libcurl、rpm 自身在编译期都把libssl.so.3写进了依赖你换上一个同名的新库符号表里少了几个上游没有承诺长期保留的符号进程加载阶段就崩了。提示系统的 OpenSSL 属于基础组件正确的姿势是并列安装不是原地升级。我一般的做法是把它装到独立前缀/opt/openssl-3.5.0OpenSSH 编译时用--with-ssl-dir指过去链接阶段再补一条-Wl,-rpath让 sshd 直接通过 rpath 找自己那份libcrypto.so.3。这样做有两个直接好处一是/usr/lib64/libssl.so.3原封不动dnf、curl、rpm 全部照常工作二是任何时候做系统补丁都不会把 sshd 依赖的库顺手换掉。代价是升级 OpenSSH 时要连/opt下的库一起核对版本不能只换一个二进制文件。这里有个容易被忽略的细节--with-ssl-dir只影响头文件和链接期的-L不负责运行期。运行期找库靠的是 ELF 里的DT_RUNPATH所以LDFLAGS里那句 rpath 不是可选项是必需品。少了它sshd -t在构建机上跑得好好的换到目标机上就是error while loading shared libraries。2.2 依赖清点与目录规划编译 OpenSSH 需要的开发包不多但漏一个就要重来一轮。下面这张表是我在 Rocky Linux 9.5 最小化安装上实测够用的一组缺的按需补。包名用途是否必需gcc、make编译工具链必需zlib-devel压缩传输必需pam-devel密码认证、账号策略强烈建议libselinux-develSELinux 上下文支持启用 SELinux 时必需krb5-develGSSAPI 认证有域环境时必需audit-libs-devel审计日志建议perl部分构建脚本建议rpm-build、rpmdevtools打 RPM 用必需一次性装齐# Rocky Linux 9.5 最小化环境补齐编译与打包依赖 dnf install -y gcc make zlib-devel pam-devel libselinux-devel \ krb5-devel audit-libs-devel perl rpm-build rpmdevtools tar wget目录规划上我固定三处源码放/usr/local/srcOpenSSL 前缀固定/opt/openssl-3.5.0OpenSSH 走 RPM 装到标准路径/usr/bin、/usr/sbin、/usr/libexec。为什么不把 OpenSSH 也放/opt因为 systemd 的sshd.service、Ansible 的 ssh 连接插件、监控探针全都默认标准路径装到/opt意味着你要改一圈配置文件反而增加了不可控变量。2.3 先编 OpenSSL 再编 OpenSSH 的执行序列顺序不能反。OpenSSH 的 configure 会去探测 OpenSSL 版本探测不到 1.1.1 以上就直接退出而且 10.0p1 里已经不再保留对老版本 OpenSSL 的兼容分支链接期符号对不上一样会失败。先编 OpenSSLcd /usr/local/src tar -xf openssl-3.5.0.tar.gz cd openssl-3.5.0 # 注意 3.x 的配置脚本是大写 C 的 Configure ./Configure --prefix/opt/openssl-3.5.0 \ --openssldir/opt/openssl-3.5.0/ssl \ --libdirlib64 \ shared zlib -fPIC make -j$(nproc) # install_sw 只装库和头文件跳过文档能省一半时间 make install_sw--libdirlib64是显式指定避免在部分环境下库落到lib目录导致 rpath 写错shared生成动态库静态库在 sshd 这种需要特权分离的场景里没意义-fPIC保证后续链接进动态库时不出问题。装完先验一下/opt/openssl-3.5.0/bin/openssl version -a # 输出里 OPENSSLDIR 应该是 /opt/openssl-3.5.0/ssl接着编 OpenSSHcd /usr/local/src tar -xf openssh-10.0p1.tar.gz cd openssh-10.0p1 export LDFLAGS-Wl,-rpath,/opt/openssl-3.5.0/lib64 -Wl,-z,relro -Wl,-z,now ./configure --prefix/usr \ --sysconfdir/etc/ssh \ --libexecdir/usr/libexec/openssh \ --with-ssl-dir/opt/openssl-3.5.0 \ --with-pam \ --with-privsep-path/var/empty/sshd \ --with-zlib \ --with-kerberos5 \ --with-selinux \ --with-md5-passwords make -j$(nproc)几个参数值得逐条说明。--libexecdir/usr/libexec/openssh是刻意保持和系统包一致因为sshd_config里Subsystem sftp那一行指向的就是这个路径改了后面必然要跟着改配置。--with-privsep-path指定特权分离目录这个目录必须存在且权限是 711后面打 RPM 时要在%files里显式声明。--with-md5-passwords在老口令迁移场景下有用纯新环境可以去掉。LDFLAGS里的-z relro -z now是加固项基线上通常会检查二进制是否开启了完整 RELRO。编完之后最关键的一步验证不是make install而是ldd# 必须指向 /opt 下自己那份 libssl不能是 /usr/lib64 里的 ldd ./sshd | grep -E ssl|crypto输出里如果是/opt/openssl-3.5.0/lib64/libssl.so.3说明 rpath 生效了如果显示not found或者指向/usr/lib64就别急着往下走回头检查LDFLAGS是不是在./configure之前 export 的。3. 用 rpmbuild 出包spec 里必须动刀的五段3.1 为什么非得打成 RPM 不可make install五分钟能跑完但它留下的东西系统是“看不见”的dnf 不知道有这个包rpm -Va查不出文件被改出了问题只能靠人肉 diff 两台机器的目录树。RPM 给的是三样东西——可查询、可回滚、可批量。几十台机器的时候一句dnf install ./openssh-10.0p1-1.el9.x86_64.rpm就完事出问题rpm -Uvh把旧包顶回去中间不需要任何人登录机器。还有一层考虑OpenSSH 10.0p1 的sshd已经不是单个二进制了。从 9.8 开始认证后的会话阶段被拆成独立的sshd-session10.0 又把认证前阶段拆成sshd-auth。主进程只负责监听和分发。这三个二进制必须成组安装、版本严格一致靠手工cp极易漏。RPM 的%files能强制你把它们绑在一起。3.2 spec 头部把自动依赖分析关掉先建目录树rpmdev-setuptree # 默认在 ~/rpmbuild 下生成 BUILD/BUILDROOT/RPMS/SOURCES/SPECS/SRPMSspec 开头这几行是整个包能不能装上的关键Name: openssh Version: 10.0p1 Release: 1.el9 Summary: OpenSSH 10.0p1 built against OpenSSL 3.5.0 License: BSD URL: https://www.openssh.com/ Prefix: /usr # 关键关掉自动依赖扫描 AutoReqProv: no Requires: pam, zlib, krb5-libs, audit-libs, libselinux Provides: openssh %{version}-%{release} Provides: openssh-server %{version}-%{release} Provides: openssh-clients %{version}-%{release} Obsoletes: openssh-server 8.7p1 Obsoletes: openssh-clients 8.7p1 %global ssl_dir /opt/openssl-3.5.0AutoReqProv: no不是可选项。默认情况下 rpmbuild 会扫描二进制的 ELF 头自动往Requires里塞一条libssl.so.3()(64bit)。问题在于你的库在/opt下不在 ldconfig 的搜索路径里rpm 安装时依赖检查直接失败报“缺少 libssl.so.3”。关掉自动分析改成手写Requires把 OpenSSL 当成一个随包携带的私有依赖来对待安装才能过。Obsoletes那两行是为了让新包能替掉系统自带的openssh-server和openssh-clients。不写的话rpm -Uvh会因为/usr/sbin/sshd文件冲突直接拒绝。这里要提醒一句写Obsoletes意味着系统原包被卸载之后想回退就得靠提前留好的旧包所以第 4 章的备份步骤不能省。3.3 %build 与 %install把编译参数原样搬进来%build段和手工编译的 configure 保持一致只把前缀改掉%build export LDFLAGS-Wl,-rpath,%{ssl_dir}/lib64 -Wl,-z,relro -Wl,-z,now %configure \ --sysconfdir/etc/ssh \ --libexecdir/usr/libexec/openssh \ --with-ssl-dir%{ssl_dir} \ --with-pam \ --with-privsep-path/var/empty/sshd \ --with-zlib \ --with-kerberos5 \ --with-selinux \ --with-md5-passwords make %{?_smp_mflags}%configure宏会自动补上--prefix/usr、--libdir/usr/lib64这类标准参数所以不需要重复写。%{?_smp_mflags}展开成-j$(nproc)在构建机上能压到一两分钟。%install段要额外做两件事建特权分离目录、建 host key 目录。%install rm -rf %{buildroot} make DESTDIR%{buildroot} install # 特权分离目录必须是 711权限错了 sshd 拒绝启动 install -d -m 0711 %{buildroot}/var/empty/sshd install -d -m 0755 %{buildroot}/etc/ssh install -d -m 0700 %{buildroot}/etc/ssh/sshd_config.dDESTDIR%{buildroot}是把安装重定向到打包沙箱里这一步不能漏否则make install会直接往构建机自己的/usr里写把构建环境搞脏。3.4 %files三个二进制的绑定关系%files %defattr(-,root,root,-) %attr(0711,root,root) /var/empty/sshd %dir /etc/ssh/sshd_config.d /usr/bin/ssh /usr/bin/scp /usr/bin/sftp /usr/bin/ssh-keygen /usr/bin/ssh-add /usr/bin/ssh-agent /usr/bin/ssh-keyscan /usr/sbin/sshd /usr/libexec/openssh/sshd-auth /usr/libexec/openssh/sshd-session /usr/libexec/openssh/sftp-server %config(noreplace) /etc/ssh/sshd_config %config(noreplace) /etc/ssh/ssh_configsshd-auth和sshd-session这两行是重点也是我踩过的最疼的一次坑——少列一个包能装上、sshd -t也通过就是重启起不来。%config(noreplace)保证升级时已有的sshd_config不被覆盖新版本配置以.rpmnew后缀落盘运维自己决定合不合。3.5 构建与产物校验# 打包_topdir 指向当前 rpmbuild 目录 rpmbuild -bb ~/rpmbuild/SPECS/openssh.spec \ --define _topdir $HOME/rpmbuild # 校验产物看文件清单里三个二进制是否齐 rpm -qpl ~/rpmbuild/RPMS/x86_64/openssh-10.0p1-1.el9.x86_64.rpm \ | grep -E sshd$|sshd-auth|sshd-session # 确认没有把 /usr/lib64/libssl.so.3 打进包里 rpm -qpl ~/rpmbuild/RPMS/x86_64/openssh-10.0p1-1.el9.x86_64.rpm \ | grep -c usr/lib64最后那条 grep 的期望输出是 0。如果有输出说明%files里写多了或者%install段把不该装的东西装进来了必须在装机前解决。4. 替换与加固一起做sshd_config 参数与热加载窗口4.1 升级窗口的后悔药备用端口与旧包留存改 sshd 最怕的是改完重启然后连接断了机器还在人进不去。这种情况在远程机房里发生一次就够记一辈子。所以上线前我固定做三件事。第一件备份配置和旧包# 配置文件带时间戳备份方便对比回滚 cp -a /etc/ssh /root/ssh-backup-$(date %F-%H%M) # 把系统自带的旧包留下来而不是只留个版本号 mkdir -p /root/rollback dnf download --destdir/root/rollback openssh openssh-server openssh-clients第二件在备用端口起一个独立实例验证通过再动生产端口# 用一份独立的配置文件和端口不影响正在跑的 sshd cp /etc/ssh/sshd_config /etc/ssh/sshd_config.test sed -i s/^#\?Port .*/Port 2222/ /etc/ssh/sshd_config.test # -D 前台运行便于观察-f 指定配置-p 覆盖端口 /usr/sbin/sshd -D -f /etc/ssh/sshd_config.test -p 2222 -E /tmp/sshd-test.log 第三件SELinux 放行备用端口。这是那 2222 连不上的最常见原因日志里只留一句Permission denied看着像认证问题其实是内核层面拦了。# 临时放行仅用于升级窗口 semanage port -a -t ssh_port_t -p tcp 2222 # 升级完成后记得删掉 semanage port -d -t ssh_port_t -p tcp 2222防火墙那边同理firewall-cmd --add-port2222/tcp加的是临时规则窗口结束要清掉别留成常开的洞。4.2 安装、版本确认与 systemd 处理正式替换# 注意先装包、后 reload不要反过来 rpm -Uvh --nodeps ~/rpmbuild/RPMS/x86_64/openssh-10.0p1-1.el9.x86_64.rpm # 语法自检没有输出才算过 /usr/sbin/sshd -t # 输出实际生效的完整配置比翻文件靠谱 /usr/sbin/sshd -T | head -20 # 客户端版本确认 ssh -V # 期望输出OpenSSH_10.0p1, OpenSSL 3.5.0 systemctl daemon-reload systemctl restart sshd systemctl status sshd --no-pager--nodeps是有意为之。因为AutoReqProv: no之后新包的Requires是手写的但系统里可能还残留着别的包对旧版 openssh 的版本约束--nodeps跳过检查。代价是装完必须自己确认一遍sshd -T能跑通不能装完就走。sshd -t只校验语法不校验算法是否真的可用。比如你把KexAlgorithms写成sntrup761x25519-sha512openssh.com但这个算法在你编译的 OpenSSL 版本里没有对应实现sshd -t照样一声不吭地过重启后客户端才发现协商不上。要确认算法真的生效得看sshd -T输出里的kexalgorithms、ciphers、macs三行。4.3 加固脚本用 Include 片段而不是 sed 改全文加固参数往哪儿写直接决定了这套脚本能不能在几十台机器上复用。用 sed 在sshd_config正文里做替换的老办法一旦遇到格式稍有差异的机器就会改错行而且没法回滚。我习惯用sshd_config.d加Include的方式。关键在于sshd_config的解析规则同一个参数出现多次时第一次出现的值生效后面的一律忽略。所以Include必须插在文件最顶端否则片段里的任何设置都会被正文里的老配置吃掉。#!/usr/bin/env bash # OpenSSH 加固片段写入幂等执行 set -euo pipefail CONF_DIR/etc/ssh/sshd_config.d MAIN/etc/ssh/sshd_config FRAG${CONF_DIR}/99-hardening.conf install -d -m 0700 $CONF_DIR cp -a $MAIN ${MAIN}.bak.$(date %s) # Include 必须在第一行否则片段被正文覆盖 if ! grep -q ^Include ${CONF_DIR}/\*.conf $MAIN; then sed -i 1i Include ${CONF_DIR}/*.conf $MAIN fi cat $FRAG EOF # 算法族只留 AEAD 和 SHA2 及以上的组合 KexAlgorithms sntrup761x25519-sha512openssh.com,curve25519-sha256,diffie-hellman-group16-sha512,diffie-hellman-group14-sha256 Ciphers chacha20-poly1305openssh.com,aes256-gcmopenssh.com,aes128-gcmopenssh.com,aes256-ctr MACs hmac-sha2-512-etmopenssh.com,hmac-sha2-256-etmopenssh.com HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256 # 认证面 PermitRootLogin prohibit-password PasswordAuthentication no PubkeyAuthentication yes PermitEmptyPasswords no MaxAuthTries 4 LoginGraceTime 30 # 会话与转发 ClientAliveInterval 300 ClientAliveCountMax 2 AllowAgentForwarding no X11Forwarding no PermitTunnel no MaxStartups 10:30:60 LogLevel VERBOSE EOF chmod 0600 $FRAG # 自检不通过就不 reload避免把连接搞断 if /usr/sbin/sshd -t; then systemctl reload sshd echo hardening applied else echo config invalid, rolled back cp -a ${MAIN}.bak.* $MAIN fi脚本里set -euo pipefail保证任何一步失败就停不会写出半截配置。sshd -t放在 reload 之前是因为 reload 本身不会校验配置配错了照样把新配置读进去下一次连接直接失败。备份文件名用时间戳而不是固定后缀连续跑两次不会互相覆盖。PermitRootLogin prohibit-password是留了口子的写法——允许 root 用密钥登录禁止密码。有些基线要求写no那就得确保机器上有一个能 sudo 的普通账号否则锁死自己。这个取舍不要一个人拍板升级前和业务方确认清楚。5. 五类翻车现场从现象倒推到根因的排查记录5.1 坑一装完重启sshd 直接躺平现象systemctl restart sshd返回失败journalctl -u sshd里一行exec /usr/libexec/openssh/sshd-auth: No such file or directory或者更早期的版本报sshd-session找不到。诡异的地方在于sshd -t在这之前跑得好好的一点提示都没有。原因10.0p1 的 sshd 主进程只负责监听认证前阶段 fork 出sshd-auth认证后交给sshd-session。这两个二进制是 configure 阶段根据--libexecdir生成的%files里只要漏掉任意一个包就能装、服务就起不来。sshd -t只解析配置语法不检查辅助二进制是否存在所以它不报警。解决打完包先跑rpm -qpl对着make install的完整产物清单逐个比。我现在的习惯是find /usr/libexec/openssh -type f和%files段做一次人工对照三个二进制必须都在。已经装上的机器从构建机的%{buildroot}里把缺的二进制 scp 过去放对位置、改回root:root 0755服务立刻就能起来。5.2 坑二特权分离目录缺了或者权限不对现象sshd 启动失败日志里Missing privilege separation directory: /var/empty/sshd或者Privilege separation user sshd does not exist。前一句往往是权限问题后一句是系统里没有 sshd 这个用户。原因--with-privsep-path指定的目录rpm 不会自动创建make install也不一定建。就算建了权限是 755 也会被拒绝——sshd 要求这个目录所有者和组都是 root权限必须是 711既不能被普通用户列目录也不能被写。解决spec 的%files里用%attr(0711,root,root) /var/empty/sshd显式声明同时在%post里补一句install -d -m 0711 -o root -g root /var/empty/sshd兜底。已经上线的机器直接手工修install -d -m 0711 -o root -g root /var/empty/sshd ls -ld /var/empty/sshd # 期望drwx--x--x. 2 root root另外那个Privilege separation user sshd does not exist是系统自带的sshd用户被误删了useradd -r -s /sbin/nologin -d /var/empty/sshd sshd补回来就行。5.3 坑三SSH 能连SFTP 全军覆没现象终端 ssh 登录完全正常一到 WinSCP 或者sftp命令就报subsystem request failed或者连接直接被关。客户端那边看着像认证问题服务端日志里只有一句subsystem request for sftp failed。原因sshd_config里Subsystem sftp /usr/libexec/openssh/sftp-server的路径和自编译产物实际安装的位置对不上。如果 configure 时--libexecdir写的是默认值sftp-server会落到/usr/local/libexec而配置文件里还指着系统老路径。解决两条路一是编译时就把 --lib本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?