首页
/ Trivy RPM 归档(*.rpm)扫描实战:SBOM 生成、漏洞检测与 License 提取

Trivy RPM 归档(*.rpm)扫描实战:SBOM 生成、漏洞检测与 License 提取

2026-09-08 20:43:17作者:蔡怀权

导读:RPM(Red Hat Package Manager)归档文件是 RHEL/CentOS/Fedora 等发行版软件分发的标准载体,但单个 .rpm 文件本身不携带操作系统信息,无法直接匹配系统漏洞库。Trivy 通过实验性的 RPM Archive 分析器(默认关闭,可用 TRIVY_EXPERIMENTAL_RPM_ARCHIVE 环境变量开启),让用户对任意目录下的 .rpm 文件生成 SBOM、提取 License,并结合"先补 OS 信息、再扫 SBOM"的两段式流程完成漏洞检测。读完本文,你将掌握该实验特性的启用方式、三种扫描器的实操命令、两段式漏洞扫描的原理,以及其底层分析器的源码实现细节。

功能总览与适用范围

本特性对应仓库文档 docs/guide/coverage/others/rpm.md,属于覆盖率文档(Coverage)中"Other"分类。该分类在 docs/guide/coverage/others/index.md 中统一索引。需要特别说明的是,该索引第 32 行明确标注:RPM Archive 特性仅在设置了 TRIVY_EXPERIMENTAL_RPM_ARCHIVE 环境变量时才生效

Trivy 的 RPM 归档扫描支持以下三类 Scanner:

Scanner 是否支持 说明
SBOM 直接对 *.rpm 文件生成 CycloneDX / SPDX 格式的 SBOM
Vulnerability 需先借助 SBOM 补充 OS 信息后才能匹配漏洞库(见下文"两段式工作流")
License 若 RPM 头中包含许可证元数据,则直接提取

在深入各扫描器的用法之前,先厘清它和仓库中其他 RPM 相关分析器(Analyzer)的区别,以免混淆:pkg/fanal/analyzer/const.go 中定义了三个不同角色——rpm(针对已安装系统的 RPM 数据库)、rpm-archive(本特性,针对独立的 .rpm 归档文件)与 rpmqa(RPM 查询输出)。本主题只涉及 rpm-archive

实验特性说明与启用方式

本特性当前标记为 EXPERIMENTAL,文档明确警告:"This feature might change without preserving backwards compatibility",即后续版本可能在没有兼容性保障的前提下调整行为。生产使用前应关注版本升级带来的变化。

它默认处于禁用状态,可通过设置环境变量 TRIVY_EXPERIMENTAL_RPM_ARCHIVE=true 来启用。

这一"默认禁用"的机制可以从源码得到直接印证。在 pkg/commands/artifact/run.go 中,构造扫描分析器列表的 disabledAnalyzers 相关逻辑如下:

// Disable RPM archive analyzer unless the environment variable is set
// TODO: add '--enable-analyzers' and delete this environment variable
if os.Getenv("TRIVY_EXPERIMENTAL_RPM_ARCHIVE") == "" {
    analyzers = append(analyzers, analyzer.TypeRpmArchive)
}

也就是说:只有当环境变量非空时,rpm-archive 分析器才会被加入可用列表;环境变量为空时该分析器被禁用。代码注释中的 TODO 也提示,未来计划以 --enable-analyzers 命令行参数替代该环境变量,因此启用方式在后续版本中可能变化。

分析器通过 pkg/fanal/analyzer/all/import.go 中的空白导入完成注册(_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/rpm"),实际的归档解析实现位于 pkg/fanal/analyzer/pkg/rpm/archive.go

场景一:从 RPM 归档目录生成 SBOM

SBOM 扫描是其余场景的基础。Trivy 会分析所有扩展名为 .rpm 的归档文件,对应分析器的 Required 判断逻辑(archive.go):

func (a *rpmArchiveAnalyzer) Required(filePath string, _ os.FileInfo) bool {
    return filepath.Ext(filePath) == ".rpm"
}

基本命令示例(以本地 ./rpms 目录为扫描目标):

TRIVY_EXPERIMENTAL_RPM_ARCHIVE=true trivy fs ./rpms --format cyclonedx --output rpms.cdx.json

参数拆解:

  • TRIVY_EXPERIMENTAL_RPM_ARCHIVE=true:临时启用实验特性(只对本次命令生效,不会写入配置文件)。
  • trivy fs ./rpms:以 filesystem 模式扫描目录,属于"本地文件系统扫描"目标类型(相关的 --format--output 等通用标志可参考 trivy_filesystem 命令参考)。
  • --format cyclonedx:指定 SBOM 输出格式。
  • --output rpms.cdx.json:结果写入文件而非标准输出。

输出格式限制:目前该特性只在以下三种格式下工作:

  • --format cyclonedx:CycloneDX JSON。
  • --format spdx:SPDX 的 tag-value 文本格式。
  • --format spdx-json:SPDX JSON 格式。

如果使用其他格式(例如默认的 table 或 json),RPM 归档分析器不会被触发,自然也不会产出对应组件。

需要提醒的是,生成的 SBOM 中每个组件(component)虽然携带包名、版本、架构等元素(见下节底层实现),但组件中不包含操作系统层面的信息,这正是下一场景必须做二次加工的原因。

场景二:两段式漏洞检测(SBOM + OS 信息补全)

单个 RPM 归档文件不携带 OS 信息,而 Trivy 的漏洞扫描器需要将软件包与特定发行版(如 Red Hat、CentOS)的 CVE 数据匹配——例如 pkg/detector/ospkg/redhat/redhat.gopkg/detector/ospkg/centos 等 的实现都依赖 OS 的 family/version 去选择对应的漏洞数据库。

因此文档给出的漏洞检测方法是两段式工作流

  1. 第一步:对 RPM 归档生成 CycloneDX SBOM;
  2. 第二步:用 jq 手工向 SBOM 中的 operating-system 类型组件填入 OS 名称与版本;
  3. 第三步:再让 Trivy 以 SBOM 作为输入做漏洞扫描。

完整命令如下:

$ TRIVY_EXPERIMENTAL_RPM_ARCHIVE=true trivy fs ./rpms -f cyclonedx -o rpms.cdx.json
$ jq '(.components[] | select(.type == "operating-system")) |= (.name = "redhat" | .version = "7.9")' rpms.cdx.json > rpms-res.cdx.json
$ trivy sbom ./rpms-res.cdx.json

逐步解读:

  • -f cyclonedx -o rpms.cdx.json:分别是 --format--output 的短写,与场景一命令等价。
  • jq 表达式:在 CycloneDX 的 components 数组中,找出 type == "operating-system" 的元素,将其 name 强制改写为 redhatversion 改写为 7.9,其余字段保持不变,并把结果写入 rpms-res.cdx.json
  • trivy sbom ./rpms-res.cdx.json:以补全后的 SBOM 文件为输入执行漏洞扫描(该子命令的完整参数见 trivy_sbom 命令参考)。

其中"OS 名称"必须与 Trivy 已支持的发行版命名一致(如 redhatcentosfedoraamazon 等,可对照 pkg/detector/ospkg 下的各发行版目录),"OS 版本"则决定匹配该发行版的哪个版本系列数据。上面的例子将 OS 声明为 Red Hat 7.9,那么后续漏洞比对会以 Red Hat 7.9 对应的漏洞库为准。若名称或版本写错(如填了 Trivy 不认识的发行版),漏洞扫描将无法得到正确结果。这就是为什么文档脚注 [^1] 特别注明:漏洞检测"需要先生成 SBOM,并在 SBOM 中加入 OS 信息"

为什么是 operating-system 组件?CycloneDX 规范允许在 components 中以 type: operating-system 描述软件所依赖的操作系统。Trivy 在解析 SBOM 输入时会读取该组件来还原 OS 上下文,从而把包与 OS 级 CVE 关联起来。若直接扫描原始 rpms.cdx.json(没有操作系统组件或 OS 字段为空),漏洞匹配将无从谈起。

场景三:从 RPM 归档提取 License

License 扫描相对简单。RPM 格式的标准头(header)中有一个 LICENSE 标签字段(由规范定义,tag 名即 LICENSE),记录了该软件包的许可证声明。Trivy 的 RPM 归档分析器在解析头时会读取该字段:

licenses, err := h.GetStrings(rpmutils.LICENSE)

(见 archive.go。)如果归档中包含许可证信息,Trivy 便会将其提取并写入包元数据的 Licenses 字段。换言之,License 扫描完全离线、直接可用,无需像漏洞扫描那样补充 OS 信息。

仓库测试夹具可以佐证这一点:archive_test.go 中针对 socat-1.7.3.2-2.el7.x86_64.rpm 的断言显示,解析结果包含 Licenses: []string{"GPLv2"} 以及 Maintainer: "Red Hat, Inc." 等字段——许可证正是从 RPM 头部直接读出的。

执行 License 扫描时,将 --scanners license 与文件系统目标结合即可(当需要输出 License 全文等更细粒度信息时,可配合 --license-full;从 run.go 的源码注释可以看出,扫描 license 文件头与全文是开销较高的操作,仅在 license--license-full 同时指定时才执行):

TRIVY_EXPERIMENTAL_RPM_ARCHIVE=true trivy fs ./rpms --scanners license

底层实现:RPM 归档分析器做了什么

理解了三个使用场景后,再来看底层实现可以让"为什么会得到这样的结果"更加清晰。完整的分析器实现在 pkg/fanal/analyzer/pkg/rpm/archive.go,核心逻辑可以分为四步。

1. 读取与解析 RPM 头(AnalyzeparseHeader

Analyze 方法(archive.go)调用第三方库 github.com/sassoftware/go-rpmutilsrpmutils.ReadRpm 读取归档,再解析其 RPM header。parseHeaderarchive.go)提取的元数据包括:

  • NEVRA(Name-Epoch-Version-Release-Architecture):通过 h.GetNEVRA() 获取,拆分成 NameVersionReleaseEpochArch 等字段;
  • License:来自 RPM 头 LICENSE 标签;
  • Vendor / 发行方:来自 VENDOR 标签,写入包的 Maintainer 字段;
  • Source RPMSOURCERPM 标签(如 socat-1.7.3.2-2.el7.src.rpm),会被拆分出源码包的名称、版本、发行号;若取值为 (none) 或空串则跳过;
  • Modularity Label:读取 tag 5096 对应的模块化标签(ModularityLabel),这是 RPM 4.16+ 定义的标签,源码注释中给出了其在 rpm 上游 rpmtag.h 中的出处。

2. 生成 PURL

generatePURLarchive.go)把包信息转换成 Software Package URL(purl)。它根据 Vendor 名称(统一转小写后做子串匹配)推断命名空间(namespace):

Vendor 包含关键字 PURL namespace
red hat redhat
fedora fedora
opensuse opensuse
suse suse
其他 空(未处理,源码中标注了 TODO: Handle more vendors)

同时把架构作为 arch 限定符拼入 purl。结合测试断言(archive_test.go),socat-1.7.3.2-2.el7.x86_64.rpm 最终生成的 purl 形如 pkg:rpm/redhat/socat@1.7.3.2-2.el7?arch=x86_64。可以看到:版本号是 Version-Release 的拼接形式1.7.3.2-2.el7),这一点对漏洞匹配很重要,因为 RPM 的修复版本通常以 release 粒度发布。

3. 处理缺失标签的容错

RPM 头并非总能包含上述全部标签。unexpectedErrorarchive.go)用于捕获 rpmutils.NoSuchTagError 这类"标签不存在"错误并以 Debug 日志记录、返回 nil,从而让分析继续而非中断;其他真正异常才向上返回。

4. 错误文件与版本管理

Analyze 处理损坏的 .rpm 文件时会返回错误(archive_test.go 中的 "broken" 用例即以 strings.NewReader("broken") 验证错误路径)。分析器还实现了 Version() 返回 archiveVersion = 1,与 rpm-archive 分析器类型常量(见 pkg/fanal/analyzer/const.go)共同服务于 Trivy 的分析器缓存与升级失效机制。

实操建议与注意事项

综合文档与源码,使用该特性时有几点值得注意:

  1. 启用前先确认版本行为:特性处于实验阶段且不保证向后兼容,升级 Trivy 后应重新验证命令与输出。
  2. 环境变量作用范围TRIVY_EXPERIMENTAL_RPM_ARCHIVE 仅作用于单个命令进程,CI 中可放在步骤级 env 中,避免全局污染。
  3. 三种格式之外勿期待 SBOM 输出:仅 cyclonedxspdxspdx-json 三种格式会触发归档解析。
  4. 漏洞扫描前务必补齐 OS 信息:直接扫描原始 SBOM 不会得到 OS 级 CVE 结果;OS name/version 的取值要与 Trivy 支持的发行版对齐。
  5. 该特性适用于独立 .rpm 文件集合:如果你面对的是已安装系统(/var/lib/rpm 数据库或镜像内的 rpm 数据库),应使用既有的系统包扫描路径(对应 rpm / rpmqa 分析器),而不是本实验特性。

对开发与测试人员而言,测试数据可用仓库的 mage 任务 mage rpm:fixtures 生成(见 archive_test.go 注释),而归档分析的入口与常量可在 pkg/fanal/analyzer/all/import.gopkg/fanal/analyzer/const.go 中继续追溯。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391