
简介Rocky Linux 9.5 系统默认自带的 OpenSSH 版本较旧在安全扫描中往往被标记为高风险项存在合规隐患。针对这一场景这套面向系统管理员与运维人员的升级加固资源将 OpenSSH 升级至 10.0p1 版本同时配套 OpenSSL 3.5.0并完成服务启动方式等相关适配适用于需要满足等保合规、修复已知漏洞的 x86_64 架构环境。压缩包共六个文件其中五个为 RPM 安装包一个为自动升级脚本整体大小约十点九六 MB。RPM 包分别覆盖服务端、客户端与必要依赖脚本则负责规划安装顺序、处理 SysV 服务启动方式并在升级后校验 sshd 运行状态从而降低手工替换带来的依赖缺失与中断风险。资源中附有升级后的版本输出和 sshd 状态验证示例便于读者快速确认执行结果。目前已有 199 人学习下载适合生产环境批量部署或离线内网升级场景按脚本执行即可完成整体加固节省重复排错时间。1. RockyLinux 9.5 升级加固脚本为什么值得在你的机器上跑一遍很多运维第一次看到“rockylinux9.5-ssh10.0p1-ssl3.5.0-rpm-x86-64升级加固脚本”这个标题第一反应是“又要折腾 SSH 了”。但实际情况是等保测评、漏扫报告、红队演练里OpenSSH 系统自带的 8.7p1 已经挂了不止一个高危 CVE而软件仓库不会自动给你推送大版本升级。把 OpenSSH 升到 10.0p1、把 OpenSSL 升到 3.5.0再以 RPM 方式封装成脚本批量下发到 x86-64 的服务器上是内网大批量整改的一种务实做法。这篇笔记直接写给要动手的运维和 SRE从编译依赖讲到 spec 文件从配置加固讲到回滚后悔药按步骤复现即可别上来就改生产环境的 sshd_config。2. 先立住编译链路OpenSSL 3.5.0、OpenSSH 10.0p1 和 RPM 的三角关系2.1 为什么新 SSH 必须配新 SSL算法与 ABI 的边界OpenSSH 10.0p1 对底层加密库的要求比 8.7p1 高了一截默认编译时它需要 OpenSSL 1.1.1 以上而 RockyLinux 9.5 自带的是 OpenSSL 3.0.7。3.0.7 不是不能用但新版本的 OpenSSH 会启用更多基于 OpenSSL 3.x 的算法实现例如 ed25519 的优化路径、NIST 曲线的常数时间实现以及未来要铺开的混合后量子密钥交换。如果你只把 OpenSSH 二进制换了底下的 libssl、libcrypto 还停留在 3.0.7某些新特性会静默回退到旧实现漏扫工具依然能识别出版本特征。所以这个脚本的核心不是“装一个新 OpenSSH 完事”而是把 OpenSSL 3.5.0 和 OpenSSH 10.0p1 当作一个整体链路来升级。编译 OpenSSH 时用--with-ssl-dir指向新 OpenSSL 的安装前缀让它在运行时链接到 3.5.0 的共享库。这里有一个天然陷阱OpenSSL 3.5.0 的 soname 还是 libssl.so.3 和 libcrypto.so.3跟系统自带的 3.0.7 一致。如果直接覆盖/usr/lib64下的同名库几乎所有依赖 OpenSSL 的 rpm 包都会遭殃轻则ssh起不来重则dnf、curl、nginx一起崩。常见的做法是给 OpenSSL 单独一个前缀比如/opt/openssl-3.5.0编译 OpenSSH 时用它再通过 rpm 的Requires或部署脚本里的ld.so.conf.d配置来绑定运行时路径。在写构建脚本前先用openssl version -a看清楚系统当前状态再决定你是要覆盖式升级还是独立前缀安装。覆盖式升级只在完全清楚风险、且能接受批量回滚的测试环境里做生产内网我一般选独立前缀这是血泪经验。2.2 选择独立前缀还是不独立不要把系统 OpenSSL 连根拔起在实际构建里OpenSSL 3.5.0 的安装路径建议定为/opt/openssl35。理由很直接RockyLinux 9.5 的openssl-libs是系统级基础库被rpm、dnf、gnutls、python3这些关键组件依赖。你要是把它替换成 3.5.0RPM 数据库里的依赖关系会立刻乱掉rpm -V校验一大堆失败dnf可能直接拒绝工作。独立前缀安装则让 OpenSSL 3.5.0 成为 OpenSSH 10.0p1 的私有依赖系统其余部分照旧用 3.0.7互不干扰。下面是我常用的 RPM 构建环境初始化命令直接在 RockyLinux 9.5 上执行# 安装编译工具和构建 rpm 所需的基础包 dnf install -y gcc make rpm-build rpmdevtools \ openssl-devel zlib-devel pam-devel \ libedit-devel systemd-devel krb5-devel \ selinux-policy-devel audit-libs-devel # 初始化 rpmbuild 目录树 rpmdev-setuptree # 确认目录结构 ls -l ~/rpmbuild/代码逻辑先装编译工具链特别注意pam-devel和systemd-devel因为 OpenSSH 要接入 PAM 认证和 systemd socket 激活缺了它们编译出的 sshd 无法正常集成登录流程。rpmdev-setuptree会创建BUILD、RPMS、SOURCES、SPECS、SRPMS这五个标准目录后续 OpenSSL 和 OpenSSH 的源码包、spec 文件、构建产物都要往这里放。参数说明rpmdev-setuptree默认建在/root/rpmbuild或当前用户家目录下。如果你们公司规范要求统一构建路径可以在~/.rpmmacros里写%_topdir /data/rpmbuild然后rpmdev-setuptree就会按新路径建目录。后续所有 spec 里的%{_topdir}变量都会跟随这个配置别忽视了这一步否则脚本一换机器就找不到 RPM 输出目录。2.3 下载源码与校验OpenSSL、OpenSSH 的版本对齐下载源码时我建议用固定版本号的 tar 包不要用 git clone 最新主干。OpenSSL 3.5.0 和 OpenSSH 10.0p1 这种组合版本号一旦漂移configure 的参数兼容性就会改变发布的 RPM 可能在这台机器上编过、那台机器上就报一堆未定义符号。下载后必须做两件事校验 sha256以及用tar -tzf检查包完整性和顶层目录名——这个细节经常被忽略。# 下载并放置源码包openssl 和 openssh 都放进 SOURCES 目录 cd ~/rpmbuild/SOURCES # 校验 openssl 源码包假设你已从官方渠道下载 sha256sum openssl-3.5.0.tar.gz # 校验 openssh 源码包 sha256sum openssh-10.0p1.tar.gz # 解压后确认版本特征 tar -tzf openssl-3.5.0.tar.gz | head -5 tar -tzf openssh-10.0p1.tar.gz | head -5这里解释一下sha256sum返回的哈希值要与官方发布页面对上这一步能防止下载到被篡改的源码包也避免因为部分压缩包损坏导致 configure 失败时浪费一小时排查。tar -tzf只列出包内容不实际解压head -5看顶层目录名是否规范例如openssl-3.5.0/和openssh-10.0p1/。如果顶层目录名跟版本号不一致后面 spec 文件里的%setup -n参数就要手动对齐否则 rpmbuild 找不到源码目录。3. 把 OpenSSL 3.5.0 打成 RPMspec、configure 与批量部署的改写3.1 spec 文件里最关键的三段配置OpenSSL 的 RPM 打包方式有好几种最常见的是沿用openssl.spec改版本号。但为了满足“升级加固脚本”这个目标我不会完整贴一个几百行的 spec而是把三个关键段落拉出来讲透。第一段是%define把版本和安装前缀写死方便批量修改%define _prefix /opt/openssl35 %define openssl_version 3.5.0 %define openssl_release 1第二段是%install关键在最后用ldconfig刷新缓存%install rm -rf %{buildroot} make DESTDIR%{buildroot} install_sw # 确保共享库进入系统缓存方便 OpenSSH 编译时找到 mkdir -p %{buildroot}/etc/ld.so.conf.d echo %{_prefix}/lib64 %{buildroot}/etc/ld.so.conf.d/openssl35.conf第三段是%files只打包lib、lib64、include、bin下的核心产物不要把整个/opt/openssl35下的文档、示例一起打包减小 RPM 体积也避免文档覆盖冲突。这里我一般只收lib64/libssl.so*、lib64/libcrypto.so*、include/openssl/*.h和bin/openssl。写这段 spec 时要理解install_sw的作用它只装软件库和头文件不装文档和示例减少打包干扰。ld.so.conf.d的配置文件会被ldconfig在 RPM 安装后自动读取让 OpenSSH 在运行时能找到/opt/openssl35/lib64下的libssl.so.3。注意%{_prefix}/lib64这个路径取决于你的架构x86-64 下 OpenSSL 默认装到lib64如果你的构建机是 32 位或使用其他架构要改成lib。3.2 configure 参数与共享库布局OpenSSL 3.5.0 在 x86-64 上的 configure 参数我一般按最小化原则来设置cd ~/rpmbuild/BUILD/openssl-3.5.0 ./Configure \ linux-x86_64 \ --prefix/opt/openssl35 \ --openssldir/opt/openssl35/ssl \ shared \ no-comp \ no-idea \ no-camellia \ no-seed \ no-md2 \ no-rc5 \ no-weak-ssl-ciphers \ enable-ec_nistp_64_gcc_128说明linux-x86_64是 OpenSSL 官方针对 x86-64 架构的优化 target它比默认的linux-generic64多启用一些汇编优化。shared生成动态库OpenSSH 在运行时动态链接libssl.so.3。no-comp关闭压缩算法SSL 压缩在业界已被证实有安全风险属于加固标配。enable-ec_nistp_64_gcc_128允许使用 64 位优化过的 NIST 椭圆曲线实现对 TLS 握手性能会有小幅提升。编译过程就是经典的make -j$(nproc) make install_sw然后把apps/openssl复制到/opt/openssl35/bin。这里有个小坑OpenSSL 3.5.0 的默认配置文件路径是/opt/openssl35/ssl/openssl.cnf如果某些工具链需要读取系统默认 cnf你需要在部署脚本里设置OPENSSL_CONF环境变量否则部分依赖 OpenSSL 的软件会有奇怪行为比如证书加载失败。3.3 构建、安装与 ld.so.conf 配置用 rpmbuild 构建 OpenSSL RPM 时建议直接指定 spec 文件路径避免复杂的rpmbuild -ba参数组合cd ~/rpmbuild rpmbuild -ba SPECS/openssl35.spec构建完成后RPMS/x86_64/下会有类似openssl35-3.5.0-1.el9.x86_64.rpm的产物。先不要急着批量推到生产环境先在一台同样装了 RockyLinux 9.5 的测试机上验证安装# 在测试机上安装 openssl35 rpm rpm -ivh openssl35-3.5.0-1.el9.x86_64.rpm # 确认动态库已经在系统缓存里 ldconfig -p | grep openssl35在这里你需要观察两个值ldconfig -p输出里应当出现/opt/openssl35/lib64/libssl.so.3和libcrypto.so.3且系统原有的/usr/lib64/libssl.so.3还在。这时候用openssl version -a未必能直接指向 3.5.0因为/usr/bin/openssl链接的还是系统旧库我们真正关心的是后续 OpenSSH 编译时能否找到它。在构建 OpenSSH 前用export PATH/opt/openssl35/bin:$PATH把新openssl命令放在前面再执行openssl version看到OpenSSL 3.5.0就说明这条链路通了。4. 编译 OpenSSH 10.0p1 并加固spec、密钥算法与 sshd_config4.1 configure 参数组合OpenSSH 10.0p1 的 configure 参数比 OpenSSL 更讲究因为你要用它覆盖系统默认的/usr/bin/ssh和/usr/sbin/sshd同时还要兼容 PAM、SELinux、kerberos 这些既有环境。我常驻的一套参数如下cd ~/rpmbuild/BUILD/openssh-10.0p1 ./configure \ --prefix/usr \ --sysconfdir/etc/ssh \ --with-ssl-dir/opt/openssl35 \ --with-pam \ --with-selinux \ --with-kerberos5 \ --with-md5-passwords \ --with-privsep-path/var/empty/sshd \ --with-privsep-usersshd \ --with-zlib \ --with-4in6解释一下--prefix/usr表示把二进制装到/usr/bin覆盖系统自带 SSH 工具。如果你用默认的/usr/local那/usr/bin/ssh还是旧版命令是新的、服务是旧的排查问题时会搞混。--sysconfdir/etc/ssh保证配置路径不变避免复制/etc/ssh/ssh_config到别处。--with-ssl-dir/opt/openssl35指定 OpenSSL 3.5.0 前缀configure 会在该目录下找include和lib64。--with-pam打开了 PAM 认证--with-selinux支持 SELinux 上下文。如果测试环境没有启用 SELinux这个参数也不会报错只是多编译一些支持代码。--with-privsep-path/var/empty/sshd和--with-privsep-usersshd是特权分离机制的必要配置OpenSSH 默认用这个机制把 sshd 分成主进程和特权子进程避免完整 root 权限暴露在网络侧。构建完成后记得手动创建这个目录并设置权限很多升级脚本会在这里翻车后面避坑章会细说。4.2 加固配置Cipher、MAC、KexAlgorithms 与认证策略编完不是结束真正的“加固”体现在/etc/ssh/sshd_config的裁剪上。我给生产环境用的配置片段如下# 只保留安全算法剔除所有已知弱算法 Ciphers chacha20-poly1305openssh.com,aes256-gcmopenssh.com,aes128-gcmopenssh.com,aes256-ctr,aes128-ctr MACs hmac-sha2-512-etmopenssh.com,hmac-sha2-256-etmopenssh.com KexAlgorithms sntrup761x25519-sha512openssh.com,curve25519-sha256,curve25519-sha256libssh.org,diffie-hellman-group16-sha512 # 禁止不安全的登录方式 PermitRootLogin prohibit-password PermitEmptyPasswords no PasswordAuthentication no KbdInteractiveAuthentication no ChallengeResponseAuthentication no # 限制尝试次数延长非法探测成本 MaxAuthTries 3 MaxSessions 10 LoginGraceTime 30 # 关闭不需要的转发和图形功能 X11Forwarding no AllowAgentForwarding no AllowTcpForwarding no这里要注意PasswordAuthentication no意味着所有用户必须走密钥登录在批量部署前必须先推送公钥否则会把所有管理员锁在门外。实操上我会分两步第一步先把公钥推到所有目标机第二步才改sshd_config并重启服务。PermitRootLogin prohibit-password允许 root 用密钥登录但不允许密码登录满足不少运维要 root 直连的需求同时避开弱口令风险。KbdInteractiveAuthentication no是很多加固文档遗漏的因为部分版本默认允许 keyboard-interactive攻击者可以利用 PAM 响应做密码尝试这里一并关掉才彻底。这些配置项在你执行sshd -T时都能看到实际生效值改完之后别只信配置文件要看sshd -T | grep passwordauthentication的输出。4.3 生成升级加固脚本并落到部署流程RPM 构建完成后通常还需要一个部署脚本承担三件事安装 RPM、备份旧配置、重启 sshd。下面这个脚本是我每次升级都会套用的模板#!/bin/bash # upgrade_harden_ssh.sh set -euo pipefail BACKUP_DIR/root/ssh_upgrade_backup_$(date %Y%m%d%H%M%S) mkdir -p $BACKUP_DIR # 备份旧配置和旧二进制回滚时用 cp -a /etc/ssh/sshd_config $BACKUP_DIR/ 2/dev/null || true cp -a /usr/sbin/sshd $BACKUP_DIR/sshd.old 2/dev/null || true # 安装新构建的 openssl 和 openssh rpm rpm -ivh /path/to/openssl35-3.5.0-1.el9.x86_64.rpm rpm -ivh /path/to/openssh-10.0p1-1.el9.x86_64.rpm # 备份后以加固配置覆盖 install -m 600 sshd_config.hardened /etc/ssh/sshd_config # 创建特权分离目录 mkdir -p /var/empty/sshd chmod 755 /var/empty/sshd chown root:root /var/empty/sshd # 关键重新加载 SELinux 上下文 restorecon -Rv /usr/sbin/sshd /etc/ssh 2/dev/null || true # 重启 sshd 服务 systemctl restart sshd # 输出当前版本便于检查 sshd -V 21 | head -1set -euo pipefail让脚本在任何一条命令失败时立即退出避免安装到一半就继续往下走造成“半升级”状态。cp -a保留文件属性和 SELinux 上下文备份这些不是为了恢复配置而是为了在回滚时能精确还原旧状态。install -m 600设定了sshd_config权限OpenSSH 对配置文件的权限非常敏感如果组和其他用户可读sshd 会拒绝启动。restorecon -Rv在启用了 SELinux 的机器上是必须的因为新装到/usr/sbin/sshd的二进制可能继承了打包机的上下文不恢复的话SELinux 会拦截 sshd 对配置文件和特权目录的访问。这套脚本在 x86-64 的 RockyLinux 9.5 上跑通后可以再套一层循环实现 ssh 批量登录部署把 IP 列表读入for循环逐个scpRPM 包和脚本再远程执行。不过要注意如果是批量推送到几百台机器建议用批量运维工具Ansible、PSSH分发不要在脚本里硬编码密码。5. 升级加固避坑手册5 个典型翻车现场5.1 现象sshd 重启后报Missing privilege separation directory升级完执行systemctl restart sshd服务启动失败日志里出现Missing privilege separation directory: /var/empty/sshd。原因是新编译的 OpenSSH 编译时指定了--with-privsep-path/var/empty/sshd但这个目录在全新系统上并不存在或者权限被改坏了。解决办法很简单mkdir -p /var/empty/sshd chmod 755 /var/empty/sshd chown root:root /var/empty/sshd。这步要在部署脚本里显式执行别指望 RPM 的%post脚本帮你建好因为有些环境对/var/empty有特殊挂载或清理策略目录刚建好就被 tmpwatch 删了。5.2 现象远程连接全部拒绝端口处于 listening 但握手失败升级脚本执行完systemctl status sshd看着是 active但用 ssh 远程工具连上去立刻Connection closed by remote host甚至sshd进程来回重启。这种情况八成是 SELinux 上下文没恢复或者配置里有不可识别的新关键字。先看/var/log/secure和/var/log/audit/audit.log确认是不是SELinux is preventing /usr/sbin/sshd from ...。解决方式是restorecon -Rv /usr/sbin/sshd /etc/ssh如果还不行用ausearch -m avc -ts recent看具体拒绝类型再用semanage调整策略。千万别在排查之初就setenforce 0那是把安全问题延后处理。5.3 现象编译时明明指定了--with-ssl-dir/opt/openssl35链接却还是系统 OpenSSL 3.0.7这种情况通常不是 configure 参数没生效而是 configure 脚本检测到了系统路径下的 OpenSSL 头文件和库之后优先选了/usr/include/openssl。要确认实际链接路径编译完成后执行ldd /usr/sbin/sshd | grep ssl如果输出libssl.so.3 /lib64/libssl.so.3说明 OpenSSH 编出来的二进制跑的时候还是找系统库。解决方式在部署机上写入/etc/ld.so.conf.d/openssl35.conf且让/opt/openssl35/lib64排在系统/lib64前面再执行ldconfig。也可以在编译时加LDFLAGS-Wl,-rpath,/opt/openssl35/lib64强制二进制记录 rpath。我用rpath方案多一点因为不依赖部署机的ld.so.conf顺序。5.4 现象普通用户登录正常root 登录始终Permission denied这个翻车现场几乎全是PermitRootLogin策略和公钥权限的组合问题。你在sshd_config里写的是prohibit-password但/root/.ssh/authorized_keys的属主或权限不对sshd 会隐式拒绝。常见错误是chmod 600 /root/.ssh/authorized_keys没做或者.ssh目录的属主不是 root。解决办法就是按 OpenAI 官方运维惯例操作路径权限严格对齐.ssh目录 700authorized_keys600属主为登录用户。另外prohibit-password只禁止密码认证 root 登录密钥认证不受影响如果连密钥也被拒多半是公钥格式与 OpenSSH 10.0p1 默认接受的格式不一致ssh-keygen -l -f authorized_keys可以验证。5.5 现象升级后scp、sftp不存在或版本混乱rpm -ivh安装新包后发现scp命令还是旧版或者干脆command not found。这是因为 OpenSSH 的 RPM 包在%files中漏掉了scp、sftp这两个二进制或者把它们装到了/usr/local/bin与系统旧的/usr/bin/scp形成两个版本并存。解决方式是检查 RPM 包内容rpm -ql openssh | grep -E /scp|/sftp确认它们在/usr/bin下。如果路径不对要么改 spec 文件的%files段要么在部署脚本里用alternatives --set指定版本防止 ssh 批量登录脚本调用到旧版而行为不一致。6. 验证与回滚给升级留一道后悔药升级这种事没有后悔药就不要动生产环境。我每次执行完升级脚本后不急着走人先跑一组验证命令确认新版本和加固配置都生效了。下面这组命令适合作为升级脚本的最后一段# 查看版本信息 ssh -V 21 /usr/sbin/sshd -V 21 | head -1 # 导出实际生效的配置 /usr/sbin/sshd -T | grep -E passwordauthentication|permitrootlogin|maxauthtries /usr/sbin/sshd -T | grep -E ciphers|macs|kexalgorithms # 检查监听状态和端口 ss -tlnp | grep :22 # 用本地密钥做一次完整登录验证 ssh -o BatchModeyes -o StrictHostKeyCheckingyes root127.0.0.1 uptimessh -V和sshd -V输出会显示 OpenSSH_10.0p1 字样这说明二进制层面已升级。sshd -T会把当前加载的配置逐项倒出来grep -E就是检查关键加固项是否真的生效。这里有个容易忽略的点sshd -T读取的是sshd_config的最终合并结果包括/etc/ssh/sshd_config.d/*.conf里的覆盖项所以如果你之前的系统里有额外的配置片段没清理这里会原形毕露。最后一条ssh -o BatchModeyes是自动化验证的关键BatchMode防止脚本卡在密码输入提示上能直接测出密钥认证链路通不通。回滚方案要在升级前写清楚我的做法是升级前用dnf list installed openssh\* openssl\*记录当前 RPM 版本升级后把旧文件备份在/root/ssh_upgrade_backup_*/。回滚不是简单yum install openssh-server-8.7p1回去因为系统的openssl-libs版本、PAM 配置、SELinux 上下文都可能被升级脚本改过。正确步骤是先用备份文件恢复sshd_config和/var/empty/sshd权限再用rpm -Uvh --oldpackage把旧版 OpenSSH 和旧版 OpenSSL RPM 装回去最后执行restorecon -Rv /usr/sbin/sshd并重启服务。如果只恢复配置不恢复二进制新旧版本混用的情况下sshd -T很可能因为配置里有新版才支持的关键字而启动失败。对于sshd_config里我提供的加固算法清单如果公司现有监控系统或跳板机只认旧的ssh-rsa签名算法升级前一定要先把跳板机和新版 OpenSSH 的兼容性测好。OpenSSH 10.0p1 已经默认禁用了 sha1 签名的 RSA 证书那些老旧的堡垒机、网络设备内置的 SSH 客户端可能全部连不上新服务器。建议在灰度阶段保留一台旧版配置的服务器或者临时在配置里把PubkeyAcceptedAlgorithms打开兼容旧签发证书等跳板机侧升级后再收紧。我在生产环境里吃过一次教训当时想着既然要做加固就把X11Forwarding no、AllowTcpForwarding no一起加上结果开发团队的一堆自动化脚本依赖 TCP 隧道直连测试环境第二天早上告警响成一片。后来我把转发限制都做成了条件式比如在Match Group developers段落里放开AllowTcpForwarding yes其他用户默认关闭两边都能工作。这种细节你是没法在漏扫报告里看到的但生产环境里它就是救命的。升级和加固本来就是为了让业务更稳不是为了跟业务做对。如果让运维同事和管理层都舒服这次升级就是成功的。希望这些经验能让你少走两个来回。本文还有配套的精品资源点击获取