
做运维最烦的事就是内网机器装软件。那种物理隔离的服务器yum源连不上pip源连不上外网根本不通但业务又催着你两天内把环境搭好。最近我手头就遇到这么一单一台内网服务器要部署新的中间件结果检查依赖时发现openssl版本太老中间件直接拒绝启动。连带zlib、pam也得一起处理因为这三个库在Linux底层里是绑在一根绳上的。先说结论这一套三件套的离线安装本质上解决的是“内网环境下的基础运行库升级”问题。zlib负责压缩解压openssl负责加密通信pam负责用户认证任何一个版本太旧都会牵一发而动全身。我这次用的是源码编译方案整个过程从下载、校验、传输、编译到安装验证花了差不多半天时间中间踩了几个坑尤其是pam编译时找不到openssl头文件的问题耽误了不少功夫。这篇文章就把完整流程和避坑策略写出来给同样被困在内网环境的运维朋友一个可参考的路径。1. 整体设计三件套的依赖关系与安装方案选型1.1 三个软件的依赖关系与编译顺序这三样东西从依赖关系上看是层层往上的zlib是最底层openssl依赖zlibopenssl的压缩功能要用到libzpam又依赖opensslpam_timestamp模块需要openssl的HMAC能力来计算时间戳。所以编译顺序必须固定成 zlib → openssl → pam不能倒过来否则你编pam的时候会发现找不到openssl的头文件和库文件。换个思路理解zlib是地基openssl是承重墙pam是门锁。地基不稳承重墙就歪门锁自然装不上去。很多新手喜欢直接跳过zlib先编openssl结果openssl编译倒是能过因为openssl对zlib是可选的找不到libz会自动禁用压缩支持但后期用的时候才发现性能下降、功能缺失再回头补装zlib又得重编openssl白费功夫。所以别偷懒按顺序来。1.2 版本选型稳定优先还是安全优先离线环境下装软件版本选型是个很讲究的事。没有外网装了以后发现版本有bug想升级又是一轮折腾。我的建议是能选LTS长期支持版本就选LTS能选安全维护期内的版本就选安全维护期内的。我这次的版本选择如下软件推荐版本选择理由zlib1.3.12023年发布修复了多个累积的CVE漏洞而且1.2.x时代的编译参数完全兼容openssl1.1.1w1.1.1系列的最后一版安全补丁打满对老程序的ABI兼容性最好pam1.5.3当前主线稳定版CentOS 9、银河麒麟等系统内部用的就是这一代这里特别说一下openssl。如果你在内网上跑的中间件是近两年才发布的那选openssl 3.0.x也可以3.0是LTS支持到2026年。但如果内网还跑着一堆老业务程序那我劝你还是选1.1.1w更稳因为很多老程序编译时用的是1.0.2或1.1.1的头文件升级到3.x之后虽然大部分兼容但偶尔会碰到一些奇奇怪怪的ABI问题。我这次因为中间件对openssl版本的最低要求就是1.1.1所以我选了1.1.1w求稳。1.3 安装路径规划统一前缀与多版本共存Linux下装开发库最怕的就是路径不统一。系统自带的openssl在/usr/lib64你自己装一个在/usr/local/openssl如果LD_LIBRARY_PATH没配好程序运行的时候可能加载到旧库要么报version mismatch要么行为异常。我的方案是zlib装到默认的/usr/local/libopenssl单独装到/usr/local/openssl目录pam源码编译时装到/usr/local/lib/security。这样做的第一个好处是目录清晰后期想卸载、升级都有据可查第二个好处是可以用ldconfig配置文件统一管理动态库路径不至于污染系统的默认库目录。有一个细节很多人会忽略openssl编译完成后生成的可执行文件默认在/usr/local/openssl/bin/openssl而系统自带的openssl命令在/usr/bin/openssl。如果直接执行openssl实际用的还是旧版本。我在安装后做了个软链把新版本的openssl覆盖到/usr/local/bin下然后在PATH里把/usr/local/bin放在/usr/bin前面。这样新程序用到openssl命令时优先用新版本老程序依赖系统库也不至于崩。2. 离线准备把源码包和构建工具弄进内网2.1 源码包下载地址与版本确认离线安装的第一步是在有外网的机器上把源码包准备好。对应的官方下载地址如下zlibhttps://zlib.net/zlib-1.3.1.tar.gzopensslhttps://www.openssl.org/source/openssl-1.1.1w.tar.gz1.1.1系列已进入维护尾声建议从官网source目录找Linux-PAMhttps://github.com/linux-pam/linux-pam/releases/download/v1.5.3/Linux-PAM-1.5.3.tar.xz下载的时候我建议顺手把官网的签名文件或SHA256校验文件也一起下载。很多人不校验就拷进内网结果源码包损坏或者被人替换了都不知道。尤其是openssl这种安全组件包一旦有问题后面整条信任链都是崩的。顺便提一句下载Linux-PAM的时候注意它在GitHub的release页面提供的是.tar.xz格式不是.tar.gz解压命令不太一样。我在一开始没注意直接拿tar -zxvf解结果报错“unknown compression type”愣了一会儿才反应过来。2.2 完整性校验与文件传输下载完成后做一个SHA256校验是最基本的操作。我的操作是sha256sum zlib-1.3.1.tar.gz openssl-1.1.1w.tar.gz Linux-PAM-1.5.3.tar.xz然后和官网公布的校验值做比对。比对一致后再把源码包拷贝到内网。传输方式我这边用的是跳板机上scp直接推过去如果你那边是物理隔离可能得走U盘刻录记得传输完再校验一次哈希防止拷贝过程中文件损坏。这里提醒一个文件权限的坑如果走U盘文件传过去后权限可能变成644甚至600如果是600在root用户下还无所谓但如果是普通用户可能无法读取。所以在内网解压前先确认一下文件属主和权限必要时chmod 755。2.3 编译环境检查清单离线环境里源码编译最怕的不是缺库而是根本没有gcc。没有编译器一切都白搭。我在开工前先跑了一遍检查命令gcc --version make --version ldconfig -v 2/dev/null | head -20如果你发现gcc都没装那就需要提前准备开发工具链的rpm包带入内网。在RHEL系系统上可以一次性拿下来yum install -y --downloadonly --downloaddir/root/rpms gcc make装完这些基础工具之后再确认一下openssl编译需要的Perl是否就位perl -v。openssl的config脚本是用Perl写的没有Perl连configure都跑不起来。Debian/Ubuntu系的话是apt-get install --download-only然后手动拷贝deb包思路一样我这里以RHEL系为主来讲。3. 源码编译全流程zlib与openssl详细实操3.1 zlib最基础的地基库zlib的编译比较纯粹依赖很少几乎不用额外配置。我的操作步骤如下tar -zxvf zlib-1.3.1.tar.gz cd zlib-1.3.1 ./configure --prefix/usr/local --shared make -j$(nproc) make install这里重点说一下--shared这个参数。如果不加这个参数configure默认只编译静态库libz.a但很多程序包括nginx、python都倾向于动态链接libz.so所以必须显式指定生成共享库。-j$(nproc)是让make用机器上所有的CPU核并行编译明显加快编译速度内网机器配置一般都不差这个参数值得养成习惯。安装完成后要验证一下动态库是否被系统识别。zlib默认装到/usr/local/lib有些Linux发行版不会自动把这个目录纳入动态库搜索路径需要手动配置echo /usr/local/lib /etc/ld.so.conf.d/local-lib.conf ldconfig ldconfig -p | grep libz如果最后一步能搜到libz.so.1说明zlib安装成功。如果搜不到别慌检查一下/etc/ld.so.conf.d目录下是否有刚才新建的文件以及文件内容是否正确。我这次在银河麒麟系统上就遇到了/usr/local/lib不在搜索路径的问题加上配置文件之后瞬间解决。3.2 openssl核心安全库的编译与配置openssl的编译配置选项比较多但核心参数其实就那几个。我用的完整命令如下tar -zxvf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./config --prefix/usr/local/openssl --openssldir/usr/local/openssl/ssl shared no-ssl2 no-ssl3 -Wl,-rpath,/usr/local/openssl/lib make -j$(nproc) make install ln -s /usr/local/openssl/bin/openssl /usr/local/bin/openssl逐个解释这几个参数--prefix/usr/local/openssl指定openssl的安装目录bin、lib、include都放在这个目录下面。--openssldir/usr/local/openssl/ssl这个是openssl运行时找配置文件、证书目录的位置默认会生成openssl.cnf。shared生成动态库libssl.so和libcrypto.so。如果不加这个程序只能静态链接后期想升级又得重编所有程序麻烦得很。no-ssl2 no-ssl3显式关闭SSLv2和SSLv3这两个协议。这两个协议早就被爆出严重漏洞正常的业务根本不会用到不开反而更安全。-Wl,-rpath,/usr/local/openssl/lib这个参数容易被忽略但非常重要。它告诉程序运行时去哪个目录找libssl.so、libcrypto.so避免出现“编译能过、运行找不到库”的经典问题。编译完成后验证一下/usr/local/openssl/bin/openssl version -a如果显示OpenSSL 1.1.1w说明编译成功。此时再看一眼默认命令which openssl openssl version如果系统原来的openssl版本是1.0.2而openssl version显示的仍然是旧版本那就是PATH的问题。确认一下/usr/local/bin是否在/usr/bin前面然后重新登录shell生效。openssl装完还有一个重要动作把它的库路径写进ldconfig。因为很多程序是运行时动态加载libcrypto.so的比如php、nginx不注册路径的话即使rpath写了对其他程序也没用。echo /usr/local/openssl/lib /etc/ld.so.conf.d/openssl-1.1.1w.conf ldconfig ldconfig -p | grep libssl3.3 用openssl验证安装与常用小工具openssl装好后顺手跑几个常用功能做个功能验证顺便也能给后续业务部署提供便利。我一般会先生成一个临时私钥和自签名证书测试openssl rand -hex 32这个命令生成32字节的随机数并以十六进制显示经常用来生成数据库密码、API密钥之类的随机串非常实用。另外一个高频操作是证书格式转换openssl x509 -in server.crt -inform PEM -outform DER -out server.der比如从第三方拿到的证书是PEM格式以-----BEGIN CERTIFICATE-----开头而java的keystore或者某些硬件设备要求DER格式就用上面这个命令转。反过来DER转PEMopenssl x509 -in server.der -inform DER -outform PEM -out server.crt这两个命令我在部署业务时经常用验证openssl功能正常的同时也顺便把证书处理好了。4. PAM编译最容易出坑的安全组件4.1 为什么说PAM能不动就不动PAMPluggable Authentication Modules是Linux的用户认证框架ssh登录、su切换用户、sudo授权全都依赖它。可以说PAM一旦坏了你的服务器就等于锁死了。所以我在这里先给个诚实的建议如果是内网生产环境PAM最好优先考虑用系统包管理器自带的版本不要轻易源码编译覆盖。以RHEL系为例系统自带的pam一般已经编译进/etc/pam.d配置链里源码装的pam很可能因为路径、配置不一致导致模块加载失败、用户无法登录。我这次为什么要动pam因为新的中间件要求openssl版本升级而系统自带的pam模块是旧openssl链接的两者混用会出现库版本不一致的问题。但即便如此我也是在测试机上反复验证过之后才在生产上操作。而且整个替换过程保留了完整的备份和回滚方案。如果你在内网上已经有pam的安装包或者离线yum源那我建议直接安装pam和pam-devel一条命令搞定yum install -y pam pam-devel只有当你确实需要一个新版本pam、且系统自带的版本无法满足需求时才考虑源码编译。下面我写的源码编译流程也是在这个前提下给出的。4.2 源码编译PAM的完整步骤源码编译PAM最关键的一点是让configure找到openssl的头文件和库文件。我踩过的坑就在这里。先看正确命令tar -xvf Linux-PAM-1.5.3.tar.xz cd Linux-PAM-1.5.3 ./configure --prefix/usr \ --with-libiconv-prefix/usr \ CPPFLAGS-I/usr/local/openssl/include \ LDFLAGS-L/usr/local/openssl/lib -Wl,-rpath,/usr/local/openssl/lib make -j$(nproc) make install这里特别要解释CPPFLAGS和LDFLAGS。Linux-PAM的configure脚本不会自动去/usr/local/openssl/include这个目录找头文件如果你不告诉它openssl头文件在哪它就会在检查openssl时输出一行经典错误ERROR: Failed to build OpenSSL section in pam这个错误我当时在网上搜了半天很多人说什么openssl没装好、版本不对其实都是误诊。真正的原因就是头文件路径没指定。configure脚本去系统默认目录/usr/include找不到openssl/evp.h头文件自然就编译不了。解决办法就是把CPPFLAGS和LDFLAGS显式传进去告诉它openssl头文件在/usr/local/openssl/include库文件在/usr/local/openssl/lib。至于--prefix/usr是为了让pam模块装到系统默认的模块目录。PAM模块必须放在/lib/security或/lib64/security目录下/etc/pam.d里的配置才会按默认路径加载。如果你把prefix指定成/usr/local模块会装到/usr/local/lib/securityPAM默认不会去这个目录找模块用户一登录就提示module not found非常痛苦。4.3 PAM升级的备份与回滚策略在动系统PAM之前不管你是不是源码编译都要先做好备份。这一步不是可选项是必须项。我这次的备份动作如下# 备份原有PAM模块目录 cp -ar /lib64/security /root/backup/security_bak_$(date %F) # 备份PAM配置文件目录 cp -ar /etc/pam.d /root/backup/pam.d_bak_$(date %F) # 记录当前rpm包版本便于回滚时对比 rpm -qa | grep pam /root/backup/pam_rpm_list.txt在执行make install覆盖系统pam模块之前我还做了一个隔离验证而不是直接在生产上装make install DESTDIR/tmp/pam_testDESTDIR参数会让文件安装到/tmp/pam_test这个临时目录不会动系统真实环境。然后我在这个目录里检查模块文件列表确认没有异常后再正式make install。这一步虽然多花几分钟但能帮你发现很多低级错误。正式替换后千万不要立刻关掉当前SSH会话。开一个新终端测试一下登录是否正常、su切换是否正常、sudo是否正常。如果发现异常马上用备份目录还原模块和配置。我见过有人替换pam之后直接重启服务器结果系统卡在“auth could not identify password for user root”最后只能单用户模式去救场面极其狼狈。5. RPM打包路线给批量部署准备的偷懒方案5.1 本地构建pam的rpm包如果你手头不是一两台机器而是十几台、几十台内网服务器源码编译一个个装效率太低了。这时候我建议走RPM打包路线在一台机器上把rpm包构建出来然后分发到其他机器统一安装。以银河麒麟或CentOS为例先在有外网的机器上把pam源码包和构建依赖准备好yum install -y rpm-build gcc make flex bison cracklib-devel audit-libs-devel libselinux-devel mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} cp Linux-PAM-1.5.3.tar.xz ~/rpmbuild/SOURCES/然后需要拿到pam的spec文件。RHEL系发行版都会在系统里带一份pam.spec可以在有外网的机器上先看一下rpm -ql pam | grep -i spec find /usr/share/doc -name pam.spec 2/dev/null如果系统里找不到就下载发行版对应的pam源码RPMSRPM包解包后里面一定有specrpm -ivh pam-1.5.1-19.el9.src.rpm cd ~/rpmbuild/SPECS接下来是最关键的构建命令rpmbuild -ba pam.spec --withopenssl这里有个大坑必须提醒RHEL系发行版的pam.spec默认不启用openssl支持你必须显式加上--withopenssl否则编译出来的pam模块不依赖libcrypto功能不完整。这一点我在第一次构建时没注意生成的rpm包按上去之后部分依赖openssl功能的程序还是报找不到符号返工了一次。构建完成后rpm包会生成在~/rpmbuild/RPMS目录下你只需要把对应架构的rpm包传到内网机器rpm -Uvh pam-1.5.1-19*.rpm安装完成后同样做登录验证。这种方式比源码编译的好处在于rpm包在安装时不会遗漏文件也在rpm数据库里有记录后期卸载升级都有据可查。5.2 混合方案源码装openssl rpm装pam/zlib在实际项目中我最后采用的是“混合方案”openssl用源码安装因为内网中间件对openssl版本有硬性要求而且openssl的rpm包和系统自带的版本很容易冲突zlib和pam走rpm打包路线因为这两个库和系统集成度太高用rpm更稳妥。具体操作是zlib用系统自带的离线源yum安装或者rpmbuild构建一个1.3.1版本的rpm包。openssl源码编译安装到/usr/local/openssl通过ldconfig和PATH让新程序优先使用。pam从源码包构建rpm构建时--withopenssl并指定依赖openssl开发包路径。这套方案的好处是zlib和pam保持和系统包管理器的兼容性不会破坏原有认证链路openssl满足新业务对版本的要求降低ABI冲突风险。混合方案虽然不是最纯的“全源码编译”但它是生产环境里风险最低的做法。最后再提醒一句如果你用的是Debian/Ubuntu系的离线环境思路完全相同只是把yum换成apt把rpm换成deb。Debian系可以这样准备apt-get download libpam0g libpam-modules libpam-runtime zlib1g-dev dpkg -i *.deb虽然命令不一样但“优先用包管理器、万不得已才源码编译”这个原则是通用的。6. 常见问题与避坑技巧速查表6.1 五大高频报错与对策这次离线安装过程中我和同事遇到的问题其实就集中在那几个点上。我把它们整理成一张速查表你按顺序对号入座就行。报错现象根本原因解决办法openssl编译后执行openssl version还是旧版本PATH优先加载了/usr/bin下的旧命令软链到/usr/local/bin并确保PATH顺序openssl运行时提示error while loading shared libraries: libssl.so.1.1动态库路径未注册写/etc/ld.so.conf.d/openssl.conf并执行ldconfigpam configure时ERROR: Failed to build OpenSSL section in pamconfigure找不到openssl头文件配置中加CPPFLAGS和LDFLAGS指定openssl路径替换pam后ssh登录提示module not foundPAM模块目录不对确保模块装在/lib/security或/lib64/security程序编译时提示openssl版本不匹配头文件和库文件来自两个版本的openssl检查CPATH和LIBRARY_PATH统一使用/usr/local/openssl第一条和第二条其实都是我日常踩过的坑很多程序编译时链接的是新openssl但运行时系统加载的是旧libcrypto就导致“version mismatch”一类的诡异问题。排查思路很简单先用ldd查看可执行文件实际依赖了哪些库再确认这些库是否存在、路径是否正确。比如排查一个程序为什么链接到旧的libsslldd /usr/local/bin/your_program | grep ssl如果显示的是/lib64/libssl.so.10而不是/usr/local/openssl/lib/libssl.so.1.1那说明动态库搜索路径优先级不对。这时候用LD_LIBRARY_PATH临时指定一下或者直接给程序加rpath就能把库掰回来。6.2 排查工具与经验心得离线环境下排查问题能用的工具更少所以一些基本功反而更重要。我这次用得最多的三个命令是rpm -qa | grep openssl ldconfig -p | grep libcrypto rpm -V pam第一行看系统通过包管理器装了哪些openssl相关的东西第二行看动态库缓存里有哪些版本的libcrypto第三行检查pam包的文件是否被源码编译覆盖过、有没有被修改。这三个命令组合起来基本能判断一个库是不是“原生”的以及当前系统实际加载的是哪个版本。说句实话离线装软件这件事真正难的从来不是敲命令而是环境不可控。有外网的时候缺什么yum装什么错了删了重来成本极低。内网环境里每一个包都得提前规划每一条命令都代表一段不可逆的操作。我个人的经验是宁可多花半小时做备份和验证也不要图快直接覆盖生产环境的库。PAM这种组件尤其如此系统能跑得好好的就不要为了一个版本号去冒锁死服务器的风险。这一套流程走完中间件最终如期部署上线openssl的版本检查也顺利通过。回过头看zlib和openssl的编译算是最省心的真正的经验教训全在pam这里。如果你也在内网环境里为这几个库头疼先别急着抄命令把版本选型和路径规划想清楚再照着上面的步骤操作能省下不少排查功夫。