首页 / 资讯中心 / 文章详情

aarch64内核4.19下将OpenSSH源码打包为RPM实现安全升级

aarch64内核4.19下将OpenSSH源码打包为RPM实现安全升级 ★ FEATURED ARTICLE
如果你手里有一台 aarch64 的服务器内核停在 4.19系统自带的 openssh 版本又老到被安全扫描工具标红你会怎么办我上周就撞上了这个事。一开始想着直接下载 openssh-8.6p1 源码./configure make make install 完事。但等到真动手发现问题不在编译在于“装上去容易撤回来难”直接覆盖了系统文件之后rpm 数据库里还是一堆旧记录后续升级、卸载、审计全乱套。所以我换了一条路把 openssh 8.6p1 源码编译成 rpm再在目标机器上用 rpm 升级。这篇文章就是把这次完整过程整理出来适合手里有一批 aarch64 机器、需要统一升级 OpenSSH 的同学参考。内容包含 rpmbuild 环境搭建、spec 文件怎么改、aarch64 4.19 内核下编译会踩哪些坑以及最后如何用生成的 rpm 包平稳升级且能够回滚。我会尽量把每个“为什么这么做”都讲清楚而不是只给你一堆命令。1. 为什么我不建议直接 make install而是非要把 OpenSSH 打成 RPM1.1 直接覆盖安装的隐患大多数人在升级 OpenSSH 时都会走“下载源码 → configure → make → make install”这条路。短平快三分钟就能把新版本跑起来。可问题恰恰出在“跑起来”之后。make install会把可执行文件、动态库、配置文件、帮助文档分散写到/usr/local/bin、/usr/local/sbin、/usr/local/etc等目录而系统包管理器并不知道这些文件的出现。更麻烦的是如果之前系统里已经安装了旧版 openssh rpm 包旧文件的路径可能和新文件路径重叠。比如/usr/bin/ssh是旧 rpm 的现在被源码编译出来的/usr/local/bin/ssh覆盖但/etc/ssh/ssh_config还是旧包里的。这样一个系统里混着两套不同版本的文件rpm -V 校验出来的全是 M 和 5 标记根本分不清是被谁改的。对于我自己维护的一台测试机文件乱一点无所谓。但如果是生产环境等下一次安全审计或者故障排查时你很难说清楚这台机器上的 ssh 到底是什么版本、配置文件是哪一次变更留下的。更致命的是回滚make install 之后你想退回到旧版本手头没有旧 rpm也没有可靠的文件备份除了导出配置重装系统基本没有干净的路可走。1.2 RPM 带来的管理价值把源码打成 rpm 包本质上就是让源码装上“包管理器的户口”。升级时用rpm -Uvh卸载时用rpm -e安装到哪台机器上都能被 rpm 数据库追踪。这样有几个直接好处依赖自动检查openssh-server会声明openssh 8.6p1如果只装了客户端系统不会要求你必定装服务端。配置文件可保护rpm的%config(noreplace)机制在升级时不会覆盖你已经改过的sshd_config如果没有这个机制一次升级可能把你的自定义配置全部重置。卸载干净rpm -e openssh-server会把包内记录的文件全部移除不会留下源码编译产生的散落文件。方便批量分发在一台 aarch64 机器上编译好所有同架构、同发行版系列的机器都可以直接用同一个 rpm 包升级不用每台都装编译器、跑一遍 configure。1.3 什么情况下值得走 rpm 打包如果你只有一台实验机且随时可以重装那 make install 确实省事。但如果你面对的是多台 aarch64 服务器需要统一升级到同一个 OpenSSH 版本或者需要在变更记录里留一句“本次升级使用某时间点编译的 rpm 包”那打包成 rpm 是唯一靠谱的方案。另外一个场景是官方仓库无法在第一时间提供新版本补丁而安全扫描又催得紧本地定制 rpm 就成了标准操作。生产环境里宁可多花半小时做 build也不要省这几十分钟给自己留隐患。2. 构建环境准备rpmbuild 目录和依赖装齐才算开始2.1 配置 ~/.rpmmacros 和目录结构rpmbuild 默认把编译目录放在/root/rpmbuild但实际上你也可以通过~/.rpmmacros里的%_topdir指到任何位置。我习惯用默认路径方便记忆。先建立标准的目录骨架mkdir -p /root/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} echo %_topdir /root/rpmbuild ~/.rpmmacros其中BUILD是解压源码后实际编译的地方BUILDROOT是安装阶段模拟的根文件系统RPMS存放最终生成的二进制 rpmSRPMS存放源码包SOURCES放原始 tar 包和补丁SPECS放 spec 文件。这个结构不是随便定的rpmbuild 会严格按照这几个目录去找输入、放输出。有一点需要注意rpmbuild必须在普通用户或者非 root 下的假根环境里跑才安全。如果直接在 root 下跑虽然能构建但一旦 spec 文件里写了错误的%install路径可能会污染系统目录。所以在生产机器上更稳妥的做法是用一个专用用户比如builduser把/home/builduser/rpmbuild作为 topdir。我这次为了快速验证直接用了 root但实际批量分发时我会在独立打包机上用专用用户。2.2 编译依赖清单OpenSSH 8.6p1 的依赖不算多但缺一个就可能在 configure 阶段直接退出。下面是我的依赖清单dnf install -y rpm-build gcc make openssl-devel pam-devel zlib-devel \ libselinux-devel audit-libs-devel krb5-devel逐一说一下为什么需要rpm-build提供rpmbuild命令这是打包的核心工具。gcc和make编译源码的基本工具链。openssl-develOpenSSH 在 Linux 下默认使用 OpenSSL 库编译时需要头文件。pam-devel如果 sshd 要支持 PAM 认证就需要 pam 头文件否则 configure 会认为你不支持 PAM直接失去账号密码登录能力。zlib-develOpenSSH 的压缩传输依赖 zlib。libselinux-devel在启用了 SELinux 的系统上sshd 需要通过 selinux 接口来设置标签这个头文件不能少。audit-libs-develsshd 的审计支持某些 spec 的 BuildRequires 里会有缺了会报依赖不足。krb5-devel如果你保留 Kerberos 支持就需要如果关了 Kerberos 可以略过但我在 spec 里保留时发现还是需要的。这里特别强调一点在 aarch64 系统上安装的依赖一定是 aarch64 版本的。可以用rpm -q --qf %{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n openssl-devel确认_arch是aarch64。如果机器的 GCC 或者 libc 是混合架构后期链接 libcrypto 时会出现“Trying to link aarch64 binary with x86 library”这类怪问题。2.3 下载源码并准备 spec 文件把openssh-8.6p1.tar.gz下载后放到SOURCES目录。记得校验一下散列值避免下载了一半的损坏文件导致编译时出现莫明其妙的“bad archive”。我习惯用sha256sum和官方发布页比对值这一步不值得省。随后解压源码把官方提供的 spec 文件复制出来cd /root/rpmbuild/SOURCES tar -xzf openssh-8.6p1.tar.gz cp openssh-8.6p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/openssh.specOpenSSH 的官方发行包中自带了contrib/redhat/openssh.spec这是给 Red Hat 系发行版用的构建模板里面已经做好了用户创建、systemd 单元安装、配置文件复制等大量工作。基于它修改比从零写 spec 要可靠得多。如果你一定要从零写请仔细阅读 fedora 打包规范里关于%pre、%post、%preun脚本的部分稍不留神就会写出“升级后 sshd 无法启动”的包。3. 修改 spec 文件的几个关键点避开默认配置里的坑3.1 版本和 Source 对应关系官方 spec 里通常写的是Version: 8.6p1这个字符串在 rpm 语法中是允许的。但你在修改Release:这一行时建议至少写成Release: 1%{?dist}这样能区分本地构建和系统源里的同名版本。Source0字段也要和SOURCES目录里的文件名严格对应。官方 spec 中经常引用的是openssh-%{version}.tar.gz那就正好对应openssh-8.6p1.tar.gz。如果你下载的文件名带了一堆奇怪的字符比如openssh-8.6p1.tar.gz.1那就要么重命名要么改Source0。rpmbuild 在构建时会把SOURCES下的文件按名字查找找不到会直接报No such file or directory。有个小细节RPM 的版本比较机制会把8.6p1和8.6区分开p1会被当作补丁级别排序时高于8.6低于8.7。所以从 8.6 升到 8.6p1 时rpm -Uvh能正确识别这是升级而不是降级。3.2 关闭 Kerberos/X11/LDNS只留下 SSH 核心功能官方 spec 里有很多开关像%global with_kerberos 1、%global no_x11 0这类。不同小版本的变量名不完全一样先把grep -n global.*kerberos\|global.*x11\|global.*ldns\|global.*pam openssh.spec看一遍再动。我说说我保留了什么、关了什么with_kerberos我选择保留 1因为内部如果有 Kerberos 环境留着能少一事。如果你完全没有 Kerberos 场景建议改成 0这样可以少装一个依赖也能减少运行时的攻击面。no_x11我改成 1。OpenSSH 的 ssh 客户端里 X11 转发本身不受影响但编译依赖里会牵扯到libX11-devel和xorg-x11-util-macros服务器上根本不需要这些东西。关闭之后 spec 不会尝试构建 ssh-askpass安装体积也会小一点。with_audit保留 1Linux 审计日志对接对排障很有用。with_pam必须保留 1除非你想让 sshd 退回 shadow 认证否则生产环境会很难用。这些开关最后会影响 configure 的参数比如--with-pam和--without-x11。如果你直接把 spec 里所有开关乱设可能会导致%configure生成的命令行和你预期不一致。所以在改完之后我习惯再看一眼%configure附近有没有把对应参数带进去。3.3 PAM、SELinux、systemd 相关的配置策略OpenSSH 8.6p1 对 PAM 的依赖很直接服务端启用密码认证时sshd 会通过 PAM 来验证用户如果sshd_config里设置了UsePAM yes但系统缺少/etc/pam.d/sshd用户登录会直接失败。官方 spec 会把pam/sshd.pam安装为/etc/pam.d/sshd但如果目标机器上这个文件已经存在你要确认 rpm 是否用了%config(noreplace)。如果在升级时被替换一些自定义的 PAM 规则会消失登录行为会被改变。SELinux 方面编译出来的/usr/sbin/sshd在升级后会从旧的sshd_exec_t变成哪种类型取决于目标机器上是否启用了 SELinux。官方 spec 里一般会在%post阶段调用restorecon或者触发selinux-policy的模块重载。如果 SELinux 处于 enforcing 模式升级完不restorecon -R /usr/sbin/sshd大概率会出现“权限被拒绝”但sshd -t正常的情况。这点我会在第 5 节验证步骤里再强调。还有 systemd 相关8.6p1 的 spec 已经包含了sshd.service、sshd.socket、sshd-keygen.service这些文件。如果你目标机器还在用 SysV init那就麻烦了。但绝大多数新部署都是 systemd所以关键是确保%files里包含了/usr/lib/systemd/system/sshd*并且%post里执行了systemctl daemon-reload。4. aarch64 4.19 内核下最容易出问题的编译项4.1 OpenSSL 开发库和 libcrypto 的匹配OpenSSH 8.6p1 官方要求 OpenSSL 版本至少是 1.1.1低于这个版本会在 configure 时报OpenSSL version too old。所以第一步就是确认系统里的openssl-devel版本和libcrypto.so是否一致。不要只盯着openssl version那只是命令行工具编译时链接的是libcrypto.so。rpm -qa | grep openssl ldconfig -p | grep libcrypto如果系统里同时存在多个 OpenSSL 版本比如/usr/lib64/libcrypto.so.1.0和/usr/lib64/libcrypto.so.1.1configure 会通过pkg-config查找。检查pkg-config --modversion openssl是否输出 1.1.1。在 aarch64 机器上这个库必须在/usr/lib64下如果看到/usr/lib下的 32 位版本说明你的开发包装错了后续链接会失败。我之前在一台纯 aarch64 机器上遇到过一种情况系统里既有openssl-devel也有openssl-libs但版本分别来自两个不同仓库最后 configure 检测通过链接时却出现一堆undefined reference to EVP_*。解决办法很简单把 openssl 全家版本统一重新安装一次openssl-devel不要手动往/usr/local/lib塞过期的 libcrypto。4.2 PAM 模块和运行目录的兼容性aarch64 和 x86_64 在 PAM 上最大的差异在于动态链接库的路径。x86_64 的 PAM 模块在/usr/lib64/securityaarch64 上同样是/usr/lib64/security所以这个不会踩坑。真正容易出问题的是你编译时用的pam-devel头文件来自 glibc 版本不一致的仓库要么没有/usr/include/security/pam_appl.h要么版本太老不支持pam_authenticate的某些返回码。另外OpenSSH 8.6 的 sshd 必须使用/run/sshd作为权限分离目录。这个目录不是由 sshd 自己递归创建的而是由 systemd-tmpfiles 创建。如果你的 rpm 包没有安装对应的 tmpfiles 配置文件或者升级时没有执行systemd-tmpfiles --create那么 sshd 启动时会报Missing privilege separation directory: /run/sshdaarch64 平台上因为文件系统布局基本一致不存在 x86 和 arm 的路径差异但如果你之前是用源码 make install 的方式跑过 8.6遗留的/run目录权限可能被改过这点也要检查。我的做法是在 spec 的%files里加上/usr/lib/tmpfiles.d/sshd.conf内容就一行d /run/sshd 0755 root root这样安装后无论什么时候启动sshd 都有可用的运行目录。4.3 seccomp 沙箱和内核选项OpenSSH 从 7.x 开始在 Linux 上会优先尝试用 seccomp 来限制预认证进程的权限减少攻击面。8.6p1 的 configure 在检测到系统内核支持 seccomp 时会编译进去。4.19 内核默认开启CONFIG_SECCOMP所以不需要额外处理。但如果你用的是裁剪过的自定义内核或者是在容器里编译并在容器里运行 rpm 包需要留意容器内uname -r是宿主内核版本如果宿主内核没有 seccompsshd 的子进程可能在 pre-auth 阶段被异常杀掉表现为连接瞬间断开日志里出现Operation not permitted。排查方式是grep CONFIG_SECCOMP /boot/config-$(uname -r)如果输出# CONFIG_SECCOMP is not set那你只能在编译时放弃 seccomp 沙箱给 configure 加--with-sandboxno或者运行时把 sshd 的UsePrivilegeSeparation设置为默认且开启其他限制。好在标题里的环境是 4.19 内核绝大多数发行版都会开启 seccomp所以这条更多是给“同内核版本但被裁剪过的系统”提个醒。4.4 不要被 aarch64 吓住它没有额外补丁需求很多人看到 aarch64 就以为要打一堆架构补丁其实 OpenSSH 的 portable 版本对 aarch64 的支持已经非常成熟没有特殊的汇编优化依赖也不像某些场景那样依赖 CPU 指令集扩展。只要系统里 GCC 是 aarch64 原生版本编译时就不会碰到架构相关的魔改。真正需要小心的是交叉编译场景在 x86 机器上用交叉工具链编译 aarch64 包有时候会因为缺少目标系统的头文件而失败。我的建议是在目标架构的机器上直接编译省去交叉工具链的配置时间也不会出现“x86 上编译出来只能在 x86 上运行”的低级错误。5. rpmbuild 打包到安装验证的完整流水线5.1 执行 rpmbuild -ba 并检查产物所有准备工作做完后进入 SPECS 目录执行cd /root/rpmbuild/SPECS rpmbuild -ba openssh.spec-ba表示同时构建二进制 rpm 和源码 rpm。如果 spec 本身有语法问题会在解析阶段直接报错如果依赖缺失会在编译前让你安装 BuildRequires。第一次跑的时候建议用rpmbuild -ba --nodeps吗不建议因为缺少依赖时直接跳过检查只会让错误出现在 configure 阶段排查更麻烦。老老实实把依赖装齐再跑。编译正常结束后二进制包会出现在/root/rpmbuild/RPMS/aarch64/下。我这次生成的包大致有这几个包名作用openssh-8.6p1-1.aarch64.rpm核心主包包含公共二进制和配置文件openssh-server-8.6p1-1.aarch64.rpmsshd 服务端、systemd 单元openssh-clients-8.6p1-1.aarch64.rpmssh、scp、sftp 客户端openssh-debuginfo-8.6p1-1.aarch64.rpm调试符号包排障时可以用openssh-debugsource源码调试符号一般不需要装如果你的 spec 中还启用了 Kerberos、LDNS 等特性可能会有额外的子包比如openssh-ldap。我这次关闭了这些所以输出就是上面四个。查看包内文件列表用rpm -qpl /root/rpmbuild/RPMS/aarch64/openssh-server-8.6p1-1.aarch64.rpm5.2 升级前备份和 rpm -Uvh这一步是生产操作的重中之重。先备份配置cp -a /etc/ssh /root/ssh-backup-$(date %Y%m%d) rpm -qa | grep openssh记录下当前 openssh 的版本和已经安装的子包。升级命令是rpm -Uvh /root/rpmbuild/RPMS/aarch64/openssh-*.rpm注意-U是升级不是安装。如果新旧包名有差异比如旧版本不分 server 和 clients新版本分了那么-U可能无法正确处理需要先安装再擦除。不过我这次新旧版本结构一致直接-Uvh一次搞定。如果你的目标机器上已存在旧版本建议不要用--force除非你已经完整备份并且清楚%post脚本不会破坏配置。强制覆盖可能把本该保留的/etc/ssh/ssh_config也替换了。升级过程中如果通过 SSH 远程连接连接大概率会被断开。这是因为 spec 的%post脚本会重启 sshd。所以升级前一定要确保自己要么在本地控制台要么有别的带外通道。我之前就在一台只有 SSH 可达的机器上直接跑升级执行完发现连接断开等了半分钟重连才发现服务已经起来了算是运气好。如果系统配置有误重连失败的风险是非常真实的。5.3 启动验证和常见登录问题升级完成后按顺序执行下面的验证ssh -V /usr/sbin/sshd -t systemctl restart sshd systemctl status sshd ss -tnlp | grep :22ssh -V能看到编译时带上的配置参数比如OpenSSH_8.6p1, OpenSSL 1.1.1k。sshd -t会检查配置文件语法并输出问题如果有Bad configuration options必须逐条解决。systemctl status能确认服务是否 active必要时用journalctl -u sshd -n 50看日志。如果服务起来了但远程登录不上下一步看/var/log/secure或者journalctl。重点排查PAM 配置是否被替换用authconfig --test或者直接看/etc/pam.d/sshd。SELinux 标签是否正确执行restorecon -vR /usr/sbin/sshd再检查审计日志ausearch -m AVC -ts recent。主机密钥权限8.6p1 生成/etc/ssh/ssh_host_rsa_key时如果权限不是 0600sshd 会拒绝使用。/run/sshd是否存在不存在就创建并加 tmpfiles 配置。我建议把sshd -T也跑一下它会把实际生效的每一项配置打出来对照PermitRootLogin、PasswordAuthentication、UsePAM等关键项确认没有因为版本升级导致默认策略变化。5.4 回滚方案别等出事了再找旧包如果不幸升级失败回滚的底线是旧版本 rpm 包。在升级前最好先找到旧版本的 RPM 文件并保存到本地目录。目标机器上如果没有可以在同系统版本的另外一台机器上执行cd /var/cache mkdir oldpkg cd oldpkg yumdownloader openssh-server openssh-clients openssh然后升级失败时使用rpm -Uvh --oldpackage oldpkg/openssh-server-旧版本-*.rpm--oldpackage允许 rpm 把当前较新的包降级到旧版本。同时你会需要把/etc/ssh从备份目录恢复出来把配置改回旧版。整个过程需要保证/run/sshd、systemd 单元等也能跟着降级所以尽量选择同发行版系列的旧 rpm 包不要拿源码 make install 的文件回滚否则又是一地鸡毛。6. 这次升级踩过的坑实际排查过程与避坑提示6.1 第一个翻车点缺少 zlib-develconfigure 直接退出我编译的那台机器精简过基础开发包不全。rpmbuild 跑到%configure阶段时报错configure: error: *** zlib.h missing - please install zlib-dev原因很简单系统里虽然有 zlib 运行库但缺少头文件。OpenSSH 的压缩功能需要 zlib 的 API没有头文件就无法编译。解决办法是安装对应架构的zlib-develdnf install -y zlib-devel装完再重新跑rpmbuild -ba。可能有人会问如果不需要压缩能不能在 configure 里加--without-zlib可以但 ssh 的Compression yes功能就废了而且官方支持矩阵里很少建议关闭。所以还是老老实实装依赖。6.2 第二个翻车点sshd 起不来/run/sshd 不存在第一次生成的 rpm 在测试机上升级后执行systemctl start sshd直接失败。查日志sshd[12345]: Missing privilege separation directory: /run/sshd sshd[12345]: fatal: Missing privilege separation directory: /run/sshdOpenSSH 从早期版本开始就一直要求这个目录存在但没有把它写在二进制里自动创建。原因是权限分离需要在初始化早期就切换角色sshd 自己没有把握在随后删除目录权限。官方打包时通常通过 tmpfiles 解决我的 spec 里漏掉了这个配置文件。修复方式是添加echo d /run/sshd 0755 root root /usr/lib/tmpfiles.d/sshd.conf systemd-tmpfiles --create /usr/lib/tmpfiles.d/sshd.conf然后在 spec 的%files里包含/usr/lib/tmpfiles.d/sshd.conf重新打包。这条经验我记了很久因为很多源码安装教程没有提纯手工编译安装的人往往是在第一次启动失败后才手动mkdir /run/sshd但这样做不持久重启后 tmpfs 清空问题还会回来。6.3 第三个翻车点旧配置里的废弃参数导致 sshd -t 报错升级后旧版sshd_config里有一行UsePrivilegeSeparation yesOpenSSH 8.6 已经弃用这个参数sshd -t直接报/etc/ssh/sshd_config line N: Deprecated option UsePrivilegeSeparation严格来说 8.6 还能接受这个参数但会有 warning。更麻烦的是某些旧参数如PermitOpen里的 IPv6 写法、AllowUsers里带网段等在新版本中语法解析更严格。排查方式是先把sshd_config里非默认的行全部列出来逐行确认在新版本中是否合法grep -Ev ^(#|$) /etc/ssh/sshd_config | sort对于不再支持的行注释掉后重新sshd -t。记住一个原则不要把旧配置文件原封不动拿给新版本用应该以新版本生成的/etc/ssh/sshd_config为基准把你的自定义项重新放进去。这样既保留需求又不会撞上废弃语法。6.4 一次看懂这些问题的排查表报错或现象根本原因处理方式configure 提示 zlib.h missing缺少 zlib-devel安装zlib-develconfigure 提示 OpenSSL version too oldopenssl-devel 版本低于 1.1.1升级/统一 openssl 版本sshd 启动报 Missing privilege separation directory缺少/run/sshd且无 tmpfiles 配置添加/usr/lib/tmpfiles.d/sshd.conf并systemd-tmpfiles --create升级后远程登录权限拒绝但 sshd 正常SELinux 标签未刷新或 PAM 配置被替换restorecon -R /usr/sbin/sshd检查/etc/pam.d/sshdssh -V显示的 OpenSSL 版本和系统不一致链接到了/usr/local/lib下的旧库清理/usr/local中的 libcrypto确保 rpm 包使用系统库sshd -t提示 Deprecated option旧配置参数已废弃以新模板为基础合并自定义配置这张表基本覆盖了我在构建和升级过程中遇到的高频问题。如果你是在纯内网环境操作可能还会遇到下载源码时需要代理的问题但那是网络策略的范畴我这里不展开。重点是把每一步的操作逻辑讲清楚免得你在报错前浪费时间。最后再分享一个实用习惯编译好的 rpm 包不要直接删放在一个统一的目录里文件名加上构建日期和 spec 的 release 号。以后不管哪台机器需要升级直接从目录里拷贝然后rpm -qpi看一眼 Build time 就能知道包是什么时候构建的。我自己现在维护的 aarch64 机器组已经把 OpenSSH 的本地 rpm 包纳入版本管理每次源码更新后重新构建先在测试机跑一遍完整的启动和登录验证再批量分发。这个流程跑顺之后升级 OpenSSH 就不再是一件“临时改动系统”的事而是一次可记录、可回滚的常规变更。
阅读完成 · 觉得有帮助?
咨询建站