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

ARM服务器内网升级OpenSSH:源码编译RPM打包全流程实践

ARM服务器内网升级OpenSSH:源码编译RPM打包全流程实践 ★ FEATURED ARTICLE
这两年国产化替代和信创推进得很快手头负责的几台ARM服务器aarch64架构内核4.19也陆续从CentOS迁移到了其他发行版。系统自带的OpenSSH版本往往跟不上趟要么CVE公告满天飞要么老旧版本不支持新密钥算法。我这边遇到的实际场景是线上环境要求OpenSSH必须升级到8.6p1及以上但内网环境无法直接访问外网yum源也没有现成的ARM架构RPM包可用。没办法只能自己动手把源码编译打包成rpm走内网分发。整个过程踩了不少坑这里把完整流程和心得整理出来给同样在ARM环境折腾OpenSSH的朋友做个参考。这篇文章涉及的关键点是aarch6464位ARM下的编译兼容性、4.19内核环境下的构建限制、rpmbuild打包规范以及最终的升级回滚策略。既适合运维朋友按步骤操作复现也适合想搞清楚为什么要改spec文件、为什么ELF校验会报错这类原理问题的读者。1. 项目需求与整体方案选型解析1.1 为什么一定要源码编译成RPM直接拿源码configure、make、make install也不是不能用但在生产环境里这么做会埋不少雷。最直接的问题有三点后续没法用rpm -qf、rpm -ql这类命令追踪文件归属卸载的时候容易留垃圾文件。升级时没法利用RPM的依赖检查和文件冲突检测万一覆盖了系统关键库或配置文件排查很痛苦。内网环境多台机器都要升级一台台make install效率太低没法统一版本、统一校验。所以最稳妥的做法是把OpenSSH源码打成RPM包走本地yum源或scp分发。这样既保留rpm的追踪能力也方便比对校验。1.2 方案选型用什么发行版做构建基础我这里的实际环境是基于4.19内核的ARM64系统起初打算直接在目标机器上rpmbuild但编译OpenSSH需要完整的gcc、make、rpm-build工具链目标机器作为生产节点我不想装一堆编译依赖。实际采用的是独立构建机 目标机器同架构同发行版的策略构建机一台aarch64虚拟机操作系统版本和目标机器一致同一个base镜像这样打出来的包二进制的glibc依赖版本是匹配的。目标机器4.19内核的ARM服务器只负责安装RPM包。这里有个关键点需要说明如果构建机和目标机器的发行版版本不一致比如构建机是较新版本、目标机器较老动态链接库版本不对装上去很容易直接报error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file。所以宁可先花点时间把构建机系统版本对齐也别图省事用别的机器代打。关于4.19内核这个信息的作用主要是确认系统的glibc版本范围以及是否支持某些新的内核特性。OpenSSH 8.6p1对内核的要求并不高4.19完全满足其运行需求真正的瓶颈反而在编译工具链和openssl-devel版本上。这个后面细说。1.3 核心构建工具的版本搭配与取舍OpenSSH 8.6p1官方要求OpenSSL版本不低于1.0.1但我建议直接用1.1.1分支的openssl-devel打底。原因有两方面8.6p1对OpenSSL 3.x还没有完全适配在编译时可能出现无法解析符号的问题。系统自带的openssl-devel 1.1.1系列足够满足编译需求打出来的包在运行时的依赖是libcrypto.so.1.1内网环境基本都有。另外还需要安装以下基础工具这就是rpmbuild的全家桶yum install -y gcc gcc-c make rpm-build rpmdevtools openssl-devel zlib-devel krb5-devel pam-devel libselinux-devel注意rpmdevtools这个包很关键它提供rpmdev-setuptree命令来创建标准的构建目录树。我用的是提前建好的所以没执行这个命令但如果你是第一次接触rpm打包建议先跑一遍初始化。提示如果构建机上没有/root/rpmbuild目录执行rpmdev-setuptree会自动创建/root/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}这六个目录。这算是一个隐含的新手须知。2. 源码准备与spec文件核心改造2.1 源码下载与完整性校验OpenSSH官方发布包我一般从两个维度校验一是PGP签名官方发布版都有对应asc文件二是生产环境的实际使用口碑。8.6p1这个版本在企业环境里用得比较多功能稳定所以直接选它。源码下载后统一放到/root/rpmbuild/SOURCES/目录cd /root/rpmbuild/SOURCES tar -xzf openssh-8.6p1.tar.gz这里有个细节rpmbuild在构建过程中会重新解包源码所以SOURCES目录下只需要保留tar包不需要手动解压后的源码目录。如果你把解压后的目录也留在SOURCES里某个环节反而可能因为文件干扰而出问题。执行tar -xzf解压的目的其实是为了拿到源码里的openssh.spec文件。这个spec文件在tar包根目录下是后续打包的核心。把解压出来的openssh.spec拷贝到/root/rpmbuild/SPECS/目录cp /root/rpmbuild/SOURCES/openssh-8.6p1/openssh.spec /root/rpmbuild/SPECS/然后就开始改spec。2.2 spec文件里必须动的几个关键位置直接使用官方openssh.spec在CentOS系发行版上打包通常会遇到三处痛点我逐个说明改法和原因第一处是%define _prefix和系统默认路径的关系。由于系统自带的OpenSSH安装在/usr/bin、/usr/sbinRPM打包时如果直接按默认路径打包文件会和系统原有ssh命令冲突。我的做法是保留默认/usr前缀但在%install阶段用--with-default-path和--with-superuser-path显式指定PATH确保打包后的二进制解释器依赖和库路径都正常。这一步不直接改路径宏而是要保证rpmbuild完成后自动将文件归入正确目录。第二处是BuildRequires依赖少了一个关键项。官方spec在部分系统上缺libselinux-devel的构建依赖导致rpmbuild执行%configure阶段时提示找不到selinux头文件。处理方法在BuildRequires一行后面追加libselinux-devel。第三处是%preun脚本和配置文件.rpmnew机制的取舍。OpenSSH升级时最忌讳覆盖/etc/ssh/sshd_config一旦配置被重置远程连接会直接断掉。我采取的策略是在spec文件的%files段里对/etc/ssh/sshd_config使用%config(noreplace)标记这样RPM升级时如果配置文件被本地修改过会保留原文件并将新配置写成sshd_config.rpmnew不会覆盖原有内容。不过更稳妥的做法是干脆不让RPM包直接管理/etc/ssh/sshd_config而是在%pre脚本里做备份在%post脚本里根据旧配置生成新配置。这个思路适合对自动化要求比较高的场景稍后我会在完整构建流程里给出示例脚本。2.3 构建OpenSSH需要的几个configure参数configure参数直接决定了二进制特性和运行行为我的rpmbuild命令中使用的关键参数如下--with-md5-passwords --with-pam --with-selinux --with-kerberos5 --with-privsep-path/var/empty/sshd --with-privsep-usersshd --sysconfdir/etc/ssh --with-default-path/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin --with-superuser-path/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin这些参数的含义要理解--with-privsep-path/var/empty/sshdOpenSSH的权限分离功能需要一个空目录存放隔离后的进程信息需要确保这个目录存在且属主是sshd。--with-pam启用PAM认证支持对应系统的login认证链路。--with-kerberos5如果内网有Kerberos认证中心这个参数会编译进GSSAPI支持没有也没关系不影响SSH本身使用。--sysconfdir/etc/ssh确保运行时读取/etc/ssh/sshd_config而不是默认的/usr/local/etc。2.4 spec文件的修改清单速查我根据实战修改总结了一份速查表供直接参考修改位置原始内容修改/新增内容原因BuildRequiresopenssl-devel追加libselinux-devel否则configure检测不到selinux头文件%files/etc/ssh/sshd_config加%config(noreplace)修饰防止升级覆盖本地配置%preun默认删除增加重启前备份配置逻辑回滚安全URL/版本号8.6p1按实际填写无%configure默认参数补充--with-kerberos5等支持GSSAPI3. 完整构建流程与参数调试实录3.1 构建目录初始化与环境变量我这边的构建机是全新的aarch64虚拟机先初始化目录rpmdev-setuptree mkdir -p /root/rpmbuild/SOURCES /root/rpmbuild/SPECS /root/rpmbuild/RPMS/aarch64注意RPMS目录下有个aarch64子目录因为aarch64架构的RPM包会按架构名分目录存放构建时如果该目录不存在rpmbuild会自动创建但手动创建可以提前确认文件输出位置。3.2 源码放置与spec文件生效验证把源码tar包放进SOURCES目录后先在SPECS目录下执行一次spec语法解析验证spec文件有没有明显错误rpmbuild -bp /root/rpmbuild/SPECS/openssh.spec-bp表示只执行到%prep阶段解压源码、打补丁不真正编译。这一步能把大部分路径问题暴露出来比如源码包文件名对不上spec里写的%{name}-%{version}.tar.gz、补丁文件缺失等。我第一次执行时报了这样一个错error: File /root/rpmbuild/SOURCES/openssh-8.6p1.tar.gz doesnt exist原因很简单我下载的文件名是openssh-8.6p1.tar.gz没错但当时把它放到了/tmp下忘了移到SOURCES目录。这类低级错误很常见建议每次构建前先ls一下确认文件名精确匹配spec里的Source0。3.3 应用spec文件补丁官方spec针对不同发行版会有一些patch片段比如openssh-8.6p1-fix这类补丁用途通常是调整sshd_config的默认权限或修复特定环境下的编译问题。我的spec文件里有一段patch需要手动确认检查%prep阶段的%patch命令路径是否对应SOURCES目录下实际存在的patch文件。某些patch在aarch64上会冲突特别是涉及汇编优化的如果执行%prep时报patch failed我会单独排查。不过在我的这个案例里官方spec自带的patch在aarch64编译OpenSSH时没有造成冲突这个酸爽程度令人欣慰省掉了不少麻烦。3.4 执行完整rpmbuild构建spec文件确认无误后执行完整构建cd /root/rpmbuild/SPECS rpmbuild -ba openssh.spec-ba表示构建二进制RPM和源码RPMSRPM。执行过程中会依次经历%prep解压源码、应用补丁。%build执行configure和make编译实际二进制。%install执行make install到临时目录。%check执行测试用例可选。%files将临时目录里的文件按列表组装成RPM包。我这次构建完成约耗时5分钟左右其中真正耗时大头是%build阶段一个8核ARM虚拟机上编译OpenSSH还比较快。日志中会看到大量的gcccommand调用看到gcc的版本是gcc version 8.3.1发行版自带的GCC就知道编译器环境没毛病。构建结束后在/root/rpmbuild/RPMS/aarch64/下生成了如下几个RPM包openssh-8.6p1-1.el8.aarch64.rpm openssh-clients-8.6p1-1.el8.aarch64.rpm openssh-server-8.6p1-1.el8.aarch64.rpm3.5 构建过程中的实际编译日志解读在%build阶段出现过一条比较有代表性的日志checking whether OpenSSL is usable... yes这条日志说明openssl-devel的版本是兼容的。如果这里显示no还伴随一堆undefined reference to EVP_...之类的错误大概率是openssl版本太高或太低导致API不匹配。我在另一篇构建笔记里碰到过OpenSSL 3.x环境下编译OpenSSH 8.6失败的场景所以这里特别强调版本搭配。4. 安装验证与回滚方案设计4.1 安装前准备与环境备份RPM包构建完成后不建议立刻在生产机器上升级。我的顺序是先在一台测试机上验证再推到生产。测试机和生产机的系统版本、内核4.19、架构aarch64必须一致这样验证结果才有代表性。安装前先备份目标机器关键目录cp -a /etc/ssh /etc/ssh.bak.$(date %Y%m%d%H%M) cp -a /etc/pam.d/sshd /etc/pam.d/sshd.bak.$(date %Y%m%d%H%M)同时重启sshd服务前先在防火墙层面把22端口临时放开一个备用通道持续监测确保万一sshd起不来还能通过控制台或带外管理系统介入处理。4.2 安装RPM包的命令与顺序安装时不要直接rpm -Uvh *.rpm一次装全部建议分开rpm -Uvh openssh-8.6p1-1.el8.aarch64.rpm rpm -Uvh openssh-clients-8.6p1-1.el8.aarch64.rpm rpm -Uvh openssh-server-8.6p1-1.el8.aarch64.rpm原因openssh-server依赖openssh包中的某些文件分开安装可以更精确地看到依赖缺失发生在哪一步。如果使用yum或dnf安装依赖关系会自动处理但内网环境不一定配了本地yum源所以直接用rpm命令反而更可控。装完后检查版本ssh -V sshd -Vssh -V通常能直接输出OpenSSH_8.6p1的信息sshd -V实际是等价于sshd不带参数会出错但错误信息里会带版本号注意不要误判为启动失败。4.3 配置检查与sshd重启升级后/etc/ssh/sshd_config如果被%config(noreplace)保护了原文件不会被覆盖。我还是习惯性检查一下关键配置是否保留grep -E ^(Port|PermitRootLogin|PubkeyAuthentication|PasswordAuthentication) /etc/ssh/sshd_config然后重启sshdsystemctl restart sshd systemctl status sshd如果systemctl status显示active (running)再用另一个会话测试登录是否正常。这里有一个经验升级后连接测试一定要等到确认服务正常再拔掉旧会话并且尽量用新开终端测试别在自己当前会话里直接重启sshd再断开防止万一网络中断连不上。4.4 回滚方案回滚是升级方案里必须预留的后手。我把回滚分成了两种模式模式一安装后完全不可用需恢复旧版本。模式二新版本启动异常但旧版本RPM包还在系统里。对于模式二直接用旧版本RPM包重装覆盖rpm -Uvh --oldpackage openssh-server-旧版本.aarch64.rpm rpm -Uvh --oldpackage openssh-旧版本.aarch64.rpm--oldpackage参数允许降级安装。恢复后立即把/etc/ssh.bak.*里的原配置还原。对于模式一如果系统因为sshd起不来而无法远程连入只能通过带外手段登录。所以我在执行升级脚本时会额外留一个crontab任务每分钟检测一次sshd进程如果连续检测失败会自动执行回滚脚本避免失联风险。这个脚本我在实际环境里部署过非常有必要因为人总有手滑的时候。4.5 RPM包内容验证使用下面几个命令验证RPM包内文件完整性和权限rpm -V openssh rpm -ql openssh-server | grep sshd rpm -qpi openssh-8.6p1-1.el8.aarch64.rpmrpm -V输出结果的每一行第一个字段是文件校验状态如果全是点表示校验通过有S、M、U等字母则表示大小、权限或属主被改变需要进一步排查。实际场景中构建机上打出的包和安装机器上的文件校验通常一致除非安装后被手动改过。5. 特殊问题处理与依赖库避坑5.1 链接错误关于openssl版本不匹配在4.19内核加aarch64环境下最常见的编译报错是openssl头文件不匹配表现形式是/usr/include/openssl/evp.h:XX: undefined reference to EVP_CIPHER_CTX_new这类undefined reference错误本质上是openssl库版本和头文件版本不一致。我构建时反复确认了openssl-devel的版本必须和系统的openssl主包一致yum list installed | grep openssl确保得到的是同一个版本号比如openssl-1.1.1k与openssl-devel-1.1.1k这样编译器使用的头文件与链接器使用的库来自同一套API。5.2 PAM认证配置在aarch64上的差异OpenSSH 8.6p1在aarch64上启用PAM认证运行时依赖/etc/pam.d/sshd存在。很多从源码直接make install的教程会因为缺少这个文件导致密码认证失败。RPM打包时spec文件里通常会自带PAM配置文件安装时把它放到/etc/pam.d/sshd。我用spec打包后正常情况下这个文件被正确安装但在某些定制系统中/etc/pam.d目录本身的权限或SELinux上下文有问题导致sshd读取PAM配置失败。遇到这种情况检查SELinux上下文ls -Z /etc/pam.d/sshd如果上下文不是system_u:object_r:pam_etc_t:s0用restorecon -v /etc/pam.d/sshd修复。5.3 sshd目录权限引发的问题--with-privsep-path/var/empty/sshd指定的目录必须存在且属主必须是root权限不能大于755。否则启动时日志会报prctl(PR_SET_KEEPCAPS) failed: Operation not permitted这个报错往往让人误以为是SELinux问题但更多时候是/var/empty/sshd目录权限被改动了。我打RPM包时会在spec的%post脚本里自动创建该目录并设置权限但如果是手动升级、没执行%post脚本就需要手动补上。6. 常见问题排查与避坑经验汇总6.1 构建阶段的典型报错报错信息可能原因解决方案File not found: /root/rpmbuild/BUILDROOT/...源码tar包没有按照spec里的Source0命名在SOURCES目录下检查文件名统一为openssh-%{version}.tar.gz格式configure: error: *** Cant find recent OpenSSL libcryptoopenssl-devel版本不匹配或未安装确认openssl与openssl-devel版本一致建议1.1.1patch failedspec中Patch文件与源码不匹配注释掉冲突的Patch或改用官方自带patchrpmbuild: command not found未安装rpm-build工具链安装rpm-build6.2 安装阶段的典型报错报错信息可能原因解决方案error: Failed dependencies: libcrypto.so.1.1()(64bit) is needed目标机器openssl版本低于构建机先升级系统openssl到1.1.1或构建时改低版本依赖user sshd does not exist目标机器没有sshd用户安装openssh-server前用useradd -r -d /var/empty/sshd sshd创建sshd: error while loading shared libraries动态链接库找不到ldd /usr/sbin/sshd排查缺失库路径6.3 编译机与目标机版本一致性教训我在早期踩过一个印象深刻的坑构建机是某较新版本里面自带glibc 2.28目标机器是旧版glibc 2.17。打完包拿到目标机器上一装sshd根本起不来ldd一查libc.so.6版本要求高于目标机器。这个教训让我彻底养成习惯构建机的发行版版本、glibc版本必须与目标机器严格一致最低要求是glibc版本一致。如果实在找不到完全一致的构建环境我可以提供一个折中方案用静态编译的方式打开一部分库的静态链接但这会改变二进制运行行为生产环境不建议这么干。优先还是保持一致。6.4 经验升级前把sshd_config差异做到最小升级时%config(noreplace)虽然能保住本地配置但新版本会带来新的配置项比如Include /etc/ssh/sshd_config.d/*.conf这样的新默认配置目录旧配置未必能覆盖新功能的开启需求。我的经验是升级前先用sshd -T导出现有运行配置升级后把它和目标机器的默认配置做一次diff把新增项合并进现有配置。这样的好处是能把配置变更记录在案方便以后追溯。sshd -T /tmp/sshd_config_old.txt cp /etc/ssh/sshd_config /tmp/sshd_config_after_upgrade.txt diff /tmp/sshd_config_old.txt /tmp/sshd_config_after_upgrade.txt6.5 构建时间与性能优化建议aarch64环境下编译OpenSSH本身不算重但如果构建机磁盘I/O比较慢rpmbuild过程中大量小文件拷贝会拖慢整体时间。我的做法是使用rpmbuild --define _smp_build_ncpus 4控制并行编译的CPU核数避免在低配构建机上因为并行编译把资源打满导致系统无响应。不过OpenSSH源码编译的并行度本来就不高这个参数的意义更多是防止内存不足。如果你希望构建过程中保留中间状态以便排查可以加上--keep-temp参数这样%build阶段的临时文件不会在结束后清空。7. 构建脚本化与自动化分发7.1 把构建流程固化成一个脚本手动执行rpmbuild方便调试但内网环境如果后续要升级到8.7、8.8之类的新版本每次都手动改spec很麻烦。我把整个流程固化成了一个构建脚本逻辑是从本地目录读取源码tar包。自动校验包名是否匹配spec里的Source0。执行rpmbuild -ba。构建完成后自动把aarch64目录下的RPM包归档到一个统一目录。脚本核心片段如下#!/bin/bash # openssh-rpm-build.sh set -e SPEC_FILE/root/rpmbuild/SPECS/openssh.spec SOURCE_TAR/root/rpmbuild/SOURCES/openssh-8.6p1.tar.gz if [ ! -f $SOURCE_TAR ]; then echo 源码包不存在: $SOURCE_TAR exit 1 fi cd /root/rpmbuild/SPECS rpmbuild -ba $SPEC_FILE --define _smp_build_ncpus 4 echo 构建完成RPM包目录 ls -lh /root/rpmbuild/RPMS/aarch64/加set -e的目的是任何一个步骤失败立即退出避免拿到不完整的产物。实际执行时如果某个patch失败日志会明确显示在哪一步方便回溯。7.2 内网分发策略RPM包构建完成后内网分发有几种方式我这边用的是最朴素的方式把RPM包拷到内网软件仓库目录配合createrepo生成repodata目标机器用yum安装。对于没有搭建内网仓库的系统可以直接用scp分发加本地rpm -Uvh。如果机器数量超过几十台我建议无论如何都要搭一个轻量级内网yum源避免每台机器都存一遍rpm文件、手工执行命令出错概率会高很多。7.3 升级后功能自检脚本升级完不能只看sshd状态。我写了一个自检命令集用来确认关键功能正常# 检查SSH协议版本 ssh -Q kex ssh -Q cipher # 检查密钥类型支持 ssh-keygen -Q # 实际建立一次本地环回连接测试 ssh -o StrictHostKeyCheckingno -o BatchModeyes localhost true echo SSH loopback test OK其中ssh -Q kex输出支持的密钥交换算法列表如果里面包含curve25519-sha256和新版ecdh-sha2-nistp256等条目就说明新版本特性生效了。对于内网有密钥登录场景的系统BatchModeyes模式下如果能免密登录成功说明公钥认证链路正常。7.4 一个很隐蔽的坑host key权限问题升级后重启sshd日志里如果出现Host key /etc/ssh/ssh_host_rsa_key has invalid permissions原因通常是RPM升级过程中/etc/ssh目录下host key文件的权限被重置了系统要求必须是owner可读0600组和其他用户不能有任何权限。解决方案很简单chmod 0600 /etc/ssh/ssh_host_*_key chmod 0644 /etc/ssh/ssh_host_*_key.pub这个坑在手工make install时很少遇到但RPM包在%files里如果没有对host key文件显式声明权限会被打成默认权限升级安装时就会触发。我打RPM包时专门在spec的%files阶段加了权限声明算是一个进阶细节。8. 后续维护视角spec文件版本升级技巧8.1 从8.6p1迁移到更新版本OpenSSH 8.6p1当前已经算较老版本如果后续安全通告要求升级到新版我的做法是下载新版本tar包比如openssh-9.x.x.tar.gz。解压后查看新版本的openssh.spec通常官方会同步更新BuildRequires和配置参数。对比旧spec把新版spec中的Source0文件名、版本号、Release号同步到旧spec。重新执行rpmbuild。大部分情况下改动点有限核心的--with-pam、--with-kerberos5等参数在新版本里依然兼容所以基础流程无需大改。8.2 利用rpmbuild宏定义管理多版本如果你的构建机需要同时维护8.6p1和9.x两个版本的RPM包我建议在构建命令里用--define传参区分版本而不是维护两份spec文件rpmbuild -ba /root/rpmbuild/SPECS/openssh.spec --define version 9.6p1对应的spec文件里要预先写成%define version %{?version:9.6p1}的形式。这种情况下目录下的SOURCES里要放好对应的tar包。这个方式在自动化集成中非常有用能减少重复维护spec的成本。8.3 订阅安全通告与内核版本的关系OpenSSH升级和内核版本的关系通常不会很紧密4.19内核完全能支撑OpenSSH 8.6p1的socket通信、进程权限分离等特性。但某些安全加固类内核模块比如seccomp filter增强、SELinux策略更新可能影响sshd系统调用升级前还是需要先跑一遍sshd -T进行语法与权限检查。我的经验是只要目标是同发行版同内核系列4.19安全通告跟进就可以放心执行。写在最后的实操体会把openssh编译打包成rpm这件事说难不算难说容易也容易在调试阶段翻车。我自己走了几趟弯路之后最大的体会是版本一致性是命门不只是源码版本还包括构建机、目标机的openssl、glibc、发行版版本任何一个层级对不上产出的冒烟包都会在目标机器上报出各种疑难杂症。另外一个小建议正式给生产环境批量升级前一定要做一次构建机打包 → 测试机安装 → 回滚演练的完整环路把坑在自己手里趟一遍。内网环境没有外部的包管理器可以依赖所有流程自洽性都得靠自己验证。经过这轮折腾至少以后再生级新版本我手里有了一套可直接复用的构建脚本和回滚预案不再每次裸操全靠胆子。
阅读完成 · 觉得有帮助?
咨询建站