一修漏洞就头大?如何在CentOS 7上构建最新Nginx RPMS

众所周知,CentOS 宣布生命周期结束以后,众多组件也逐渐停止了对CentOS 7的支持和维护,Nginx 同样在 1.26.1 版本以后不再对 CentOS 7 提供新版本的 Nginx RPMS制品。而前一阵的 NGINX ngx_http_rewrite_module 堆缓冲区溢出漏洞(CVE-2026-42945、CVE-2026-9256) 几乎覆盖了历史所有版本,虽然要一定条件才能触发,但也被要求升级到 1.31.1 以上。这个时候仍在运行中的CentOS 7该怎么处理?直接编译吗,但又不够通用,又或者和现有业务部署流程不兼容…

环境准备

本次操作在新加坡节点云主机进行,基于 centos:centos7.6.1810 容器作为编译构建环境。之所以使用 Docker 容器,是为了保证构建环境足够干净,避免宿主机已有依赖、配置或历史文件影响编译结果。

拉取 CentOS 7.6 镜像并启动:

1
2
3
docker pull centos:centos7.6.1810
docker run -itd centos:centos7.6.1810 bash
docker exec -it e02a961ccfe2 bash

修复 CentOS 7 Yum 源

CentOS 7 已经进入 EOL,默认的 mirrorlist 可能无法正常使用,因此需要将 Yum 源切换到 vault.centos.org,进入 Yum 源目录:

1
2
3
cd /etc/yum.repos.d/
mkdir backup
cp *.repo backup/

注释掉 mirrorlist,并将将默认的 mirror.centos.org 替换为 vault.centos.org

1
2
sed -i 's/^mirrorlist=/#mirrorlist=/g' CentOS-*.repo
sed -i 's/^#baseurl=http:\/\/mirror.centos.org/baseurl=http:\/\/vault.centos.org/g' CentOS-*.repo

查看修改后的基础源配置,生成 Yum 缓存:

1
2
cat CentOS-Base.repo
yum makecache

如果你在国内机器上操作,想走国内镜像源快一些,也可以看我之前写的文章CentOS 7停服(EOL)后如何下载RPM包

安装基础编译工具

到这里先准备安装一下编译Nginx的常用编译工具和依赖库:

1
yum install -y make gcc g++ wget curl vim git

安装 NGINX 编译所需依赖:

1
2
3
4
5
6
7
8
9
10
11
12
yum install -y \
GeoIP-devel \
gd-devel \
libedit-devel \
perl-devel \
perl-ExtUtils-Embed \
libxslt \
rpm-build \
openssl-devel \
pcre2-devel \
libxml2-devel \
libxslt-devel

这些依赖主要用于编译 NGINX 本体以及相关动态模块,例如:

  • geoip
  • image-filter
  • perl
  • xslt
  • njs

Nginx pkg-oss 调整模块编译配置

进入 /opt 目录,克隆官方打包仓库:

1
2
3
cd /opt
git clone https://github.com/nginx/pkg-oss.git
cd /opt/pkg-oss/rpm/SPECS/

SPECS 目录下可以看到 Makefile 文件。编辑该文件,找到类似下面的配置:

1
BASE_MODULES= acme geoip image-filter njs otel perl xslt

本次构建中,去掉了 acme 模块。调整后的模块列表类似如下:

1
BASE_MODULES= geoip image-filter njs otel perl xslt

或者根据实际需要,也可以进一步去掉不需要的模块。

为什么去掉 acme 模块?

nginx-module-acme 通常用于 ACME / Let’s Encrypt 证书自动签发与续期相关功能。

本次原本尝试编译 acme 模块,但在 CentOS 7 环境中遇到了多个问题。

vendored-sources 问题

acme 模块构建过程中会使用 Rust、Cargo,并且默认要求使用本地 vendored 依赖。
相关构建逻辑类似如下:

1
2
3
4
5
6
7
8
9
define MODULE_PREBUILD_acme
cd %{bdir} && \
mkdir -p .cargo && \
echo '[source.crates-io]' > .cargo/config.toml && \
echo 'replace-with = "vendored-sources"' >> .cargo/config.toml && \
echo '[source.vendored-sources]' >> .cargo/config.toml && \
echo 'directory = "%{bdir}/nginx-acme-$(NGINX_ACME_VERSION)-vendor"' >> .cargo/config.toml
endef
export MODULE_PREBUILD_acme

也就是说,它要求依赖包已经提前被放在本地 vendor 目录中。

问题在于如果本地不存在 vendor 目录,构建过程不会自动从互联网拉取 crates.io 的依赖,那么编译就会失败。

Rust 依赖和系统工具链版本过低

即使屏蔽掉上述 vendored-sources 相关配置,实际编译过程发现,仍然会遇到 Rust 依赖和系统构建工具链版本不足的问题。

例如:

  • openssl-sys v0.9.114 要求 OpenSSL >= 1.1.0;
  • 更推荐使用 OpenSSL 1.1.1 或 3.x;
  • 需要较新的 libclang / LLVM;
  • 需要较新的 Rust / Cargo;
  • 需要更新的构建工具链。

而 CentOS 7 默认环境中:

  • OpenSSL 版本较旧;
  • LLVM / clang 版本较旧;
  • Rust 工具链需要额外安装和适配;
  • 升级系统级构建链成本较高。

因此,在 CentOS 7 上强行解决这些问题会非常麻烦。最终,本次选择在 BASE_MODULES 中去掉 acme 模块,只编译其他常用模块。

另外,也核对了 Nginx 官方的 RPMS 地址上,对 CentOS 7 也不提供acme模块,只有 CentOS 8 才开始出现。

开始编译 RPM 包

/opt/pkg-oss/rpm/SPECS/ 目录下执行构建命令。具体命令根据 Makefile 中的目标可能略有不同,通常可以执行:

1
make all

或者根据需要执行对应的构建目标。构建过程中会自动下载 NGINX 源码、模块源码,并通过 rpmbuild 生成 RPM 包。编译结束后,可以看到类似如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
Provides: nginx-module-xslt-debuginfo = 1:1.31.1-1.el7.ngx nginx-module-xslt-debuginfo(x86-64) = 1:1.31.1-1.el7.ngx
Requires(rpmlib): rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 rpmlib(CompressedFileNames) <= 3.0.4-1
Checking for unpackaged file(s): /usr/lib/rpm/check-files /opt/pkg-oss/rpm/BUILDROOT/nginx-module-xslt-1.31.1-1.el7.ngx.x86_64
Wrote: /opt/pkg-oss/rpm/SRPMS/nginx-module-xslt-1.31.1-1.el7.ngx.src.rpm
Wrote: /opt/pkg-oss/rpm/RPMS/x86_64/nginx-module-xslt-1.31.1-1.el7.ngx.x86_64.rpm
Wrote: /opt/pkg-oss/rpm/RPMS/x86_64/nginx-module-xslt-debuginfo-1.31.1-1.el7.ngx.x86_64.rpm

real 15.34
user 11.89
sys 5.02

===> all done

看到 all done 说明编译完成。

查看编译产物

进入 RPM 输出目录,查看生成的 RPM 包:

1
2
cd /opt/pkg-oss/rpm/RPMS/x86_64
ls -lrt

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
total 8576
-rw-r--r-- 1 root root 892588 May 22 17:33 nginx-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 2025220 May 22 17:33 nginx-debuginfo-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 23728 May 22 17:34 nginx-module-geoip-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 78084 May 22 17:34 nginx-module-geoip-debuginfo-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 27076 May 22 17:34 nginx-module-image-filter-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 71500 May 22 17:34 nginx-module-image-filter-debuginfo-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 945676 May 22 17:36 nginx-module-njs-1.31.1+0.9.9-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 4406296 May 22 17:36 nginx-module-njs-debuginfo-1.31.1+0.9.9-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 42092 May 22 17:36 nginx-module-perl-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 145480 May 22 17:36 nginx-module-perl-debuginfo-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 25504 May 22 17:36 nginx-module-xslt-1.31.1-1.el7.ngx.x86_64.rpm
-rw-r--r-- 1 root root 76700 May 22 17:36 nginx-module-xslt-debuginfo-1.31.1-1.el7.ngx.x86_64.rpm

整理 debuginfo 包

如果只需要实际安装包,不需要调试符号包,可以将 debuginfo 包单独移动到一个目录。创建 debug 目录:

1
2
mkdir debug
mv *debuginfo* debug/

最终保留下来需要的主要 RPM 包包括:

1
2
3
4
5
6
nginx-1.31.1-1.el7.ngx.x86_64.rpm
nginx-module-geoip-1.31.1-1.el7.ngx.x86_64.rpm
nginx-module-image-filter-1.31.1-1.el7.ngx.x86_64.rpm
nginx-module-njs-1.31.1+0.9.9-1.el7.ngx.x86_64.rpm
nginx-module-perl-1.31.1-1.el7.ngx.x86_64.rpm
nginx-module-xslt-1.31.1-1.el7.ngx.x86_64.rpm

这样操作下来,只需要在现有业务流程中替换 rpm 包即可,其他都保持不变。

总结

本次最大的坑是尝试编译 nginx-module-acme,以及分析acme构建过程,该模块依赖 Rust、Cargo 生态,并且对 OpenSSL、LLVM、libclang 以及 Rust 工具链版本要求较高。在 CentOS 7 这种较老的系统环境中处理起来成本较高,因此本次选择不编译 acme 模块。

最后只想说一句,除了陈年老系统真的无法变更以外,操作系统OS当换则换,别再继续用 CentOS 7 了,一定不要有【又不是不能用】的想法。