3. SBOM 生成工具:从选型到实战
聊到 SBOM 的落地,第一个绕不开的问题就是:用什么工具生成?
市面上工具不少,但真正能打、社区活跃、生态好的,其实就几个流派。我个人这些年折腾下来,主力工具基本固定在 SPDX 官方工具链、CycloneDX 工具链,以及 Syft + Trivy 这个黄金组合上。
今天我就把这几个工具怎么用、各自擅长什么、有哪些坑,掰开揉碎了讲一遍。
核心观点:没有万能工具。SPDX 适合合规审计,CycloneDX 适合集成到 CI/CD,Syft + Trivy 适合快速扫描和容器场景。选对工具,事半功倍。
3.1 SPDX 工具链:合规审计的基石
SPDX 工具链是 Linux 基金会维护的官方工具集。说白了,它是为合规审计而生的。
我记得第一次用 SPDX 工具,是在一个金融客户的项目里。对方要求所有第三方组件必须提供 SPDX 格式的 SBOM,否则不予上线。那时候我才真正意识到,SPDX 在合规领域的统治力有多强。
3.1.1 核心工具一览
| 工具名称 | 功能 | 适用场景 |
|---|---|---|
| spdx-tools | SPDX 文档生成、验证、转换 | Java 项目、合规审计 |
| spdx-sbom-generator | 自动生成 SPDX SBOM | 多语言项目 |
| spdx-license-matcher | 许可证匹配与识别 | 许可证合规检查 |
| spdx-validator | SPDX 文档格式验证 | 质量门禁 |
3.1.2 实战:生成 SPDX SBOM
以 Java 项目为例,用 spdx-tools 生成 SBOM 其实不复杂:
# 下载 spdx-tools
wget https://github.com/spdx/tools/releases/download/v2.2.6/spdx-tools-2.2.6.jar
# 生成 SPDX 文档(Tag:Value 格式)
java -jar spdx-tools-2.2.6.jar \
-in /path/to/project \
-out output.spdx \
-format tagvalue
# 验证生成的 SPDX 文档
java -jar spdx-tools-2.2.6.jar \
-verify output.spdx
我的经验:SPDX 的 Tag:Value 格式可读性最好,适合人工审查。但如果你要集成到自动化流水线,建议用 JSON 格式,解析起来更方便。
3.1.3 避坑指南
我曾经在生成 SPDX 文档时,遇到过一个诡异的错误——文档验证不通过,但怎么看格式都没问题。后来发现,是项目路径里包含了中文目录名,spdx-tools 对 Unicode 支持不够好。
所以,项目路径尽量用纯英文,这是个小坑,但能省你半天时间。
3.2 CycloneDX 工具链:CI/CD 集成的利器
如果说 SPDX 是合规审计的「老大哥」,那 CycloneDX 就是 CI/CD 集成的「尖刀」。它由 OWASP 社区维护,天生就带着安全基因。
我个人更喜欢 CycloneDX 的一点是:它原生支持依赖图。你想想看,一个 SBOM 如果只列出组件清单,却不告诉你组件之间的依赖关系,那价值就大打折扣了。
3.2.1 核心工具一览
| 工具名称 | 功能 | 适用场景 |
|---|---|---|
| cyclonedx-maven-plugin | Maven 项目 SBOM 生成 | Java/Maven 项目 |
| cyclonedx-gradle-plugin | Gradle 项目 SBOM 生成 | Java/Gradle 项目 |
| cyclonedx-npm | Node.js 项目 SBOM 生成 | JavaScript/TypeScript |
| cyclonedx-python | Python 项目 SBOM 生成 | Python 项目 |
| cyclonedx-cli | 命令行工具,格式转换 | 通用场景 |
3.2.2 实战:在 Maven 项目中集成 CycloneDX
我建议直接在 pom.xml 里配置插件,这样每次构建都会自动生成 SBOM:
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.7.9</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>makeAggregateBom</goal>
</goals>
</execution>
</executions>
<configuration>
<projectType>application</projectType>
<schemaVersion>1.4</schemaVersion>
<includeBomSerialNumber>true</includeBomSerialNumber>
<includeCompileScope>true</includeCompileScope>
<includeProvidedScope>true</includeProvidedScope>
<includeRuntimeScope>true</includeRuntimeScope>
<includeSystemScope>false</includeSystemScope>
<includeTestScope>false</includeTestScope>
<outputFormat>json</outputFormat>
<outputName>bom</outputName>
</configuration>
</plugin>
执行 mvn package 后,在 target/ 目录下会生成 bom.json 文件。这就是你的 CycloneDX 格式 SBOM。
注意:默认配置下,test scope 的依赖不会包含在 SBOM 中。如果你的测试框架有安全风险(比如 JUnit 的某些旧版本),记得手动调整 includeTestScope 为 true。
3.2.3 格式转换:CycloneDX ↔ SPDX
实际项目中,你可能会遇到「甲方要 SPDX,但团队用 CycloneDX」的情况。别慌,cyclonedx-cli 可以帮你做格式转换:
# CycloneDX JSON 转 SPDX JSON
cyclonedx-cli convert \
--input-file bom.json \
--output-file bom.spdx.json \
--output-format spdxjson
嗯,这里要注意:转换后的 SPDX 文档可能丢失一些 CycloneDX 特有的字段(比如依赖图信息)。所以,尽量保持原生格式,转换只是权宜之计。
3.3 Syft 与 Trivy:容器时代的黄金搭档
聊完两个「老牌」工具链,咱们来看看新生代的力量——Syft 和 Trivy。
这两个工具都是 Anchore 公司开源的,一个负责生成 SBOM(Syft),一个负责漏洞扫描(Trivy)。配合起来用,效果 1+1 > 2。
我为什么喜欢它们?快,真的快。 扫描一个几百 MB 的容器镜像,Syft 几秒钟就能生成 SBOM,Trivy 也只要十几秒就能完成漏洞检测。
3.3.1 Syft:容器镜像的 SBOM 生成器
Syft 支持从容器镜像、文件系统、OCI 目录等多种来源生成 SBOM。安装也简单:
# 安装 Syft(macOS/Linux)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# 生成容器镜像的 SBOM
syft nginx:latest -o json > nginx-sbom.json
# 生成文件系统的 SBOM
syft dir:/path/to/project -o spdx-json > project-sbom.spdx.json
Syft 默认输出 CycloneDX 格式,但你可以通过 -o 参数指定 SPDX、Syft 自己的 JSON 格式等。
我的习惯:在 CI/CD 流水线里,我会先用 Syft 生成 SBOM,然后把 SBOM 传给 Trivy 做漏洞扫描。这样,SBOM 和漏洞报告是关联的,审计时一目了然。
3.3.2 Trivy:不只是漏洞扫描
很多人以为 Trivy 只是个漏洞扫描器,其实它也能生成 SBOM。不过,我更推荐用 Syft 生成 SBOM,用 Trivy 做漏洞检测——各司其职。
# 安装 Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# 扫描容器镜像漏洞
trivy image nginx:latest
# 扫描 SBOM 文件中的漏洞
trivy sbom nginx-sbom.json
# 生成 SBOM(Trivy 也支持)
trivy image --format cyclonedx nginx:latest > nginx-sbom-trivy.json
Trivy 的 trivy sbom 命令特别实用——你不需要重新扫描镜像,直接拿已有的 SBOM 文件就能做漏洞匹配。这在离线环境或者镜像已经删除的场景下,简直是救命功能。
3.3.3 实战:Syft + Trivy 集成到 Jenkins
我曾在 Jenkins 流水线里这样配置:
stage('SBOM 生成') {
steps {
script {
// 生成 SBOM
sh "syft ${DOCKER_IMAGE}:${BUILD_TAG} -o json > sbom.json"
// 存档 SBOM
archiveArtifacts artifacts: 'sbom.json'
}
}
}
stage('漏洞扫描') {
steps {
script {
// 基于 SBOM 做漏洞扫描
sh "trivy sbom sbom.json --format json --output trivy-report.json"
// 如果存在高危漏洞,构建失败
sh "trivy sbom sbom.json --exit-code 1 --severity CRITICAL"
}
}
}
注意:Trivy 的 --exit-code 参数要谨慎使用。如果设得太严(比如所有 CRITICAL 漏洞都阻断构建),开发团队可能会抱怨。我建议先设为 --exit-code 0 只做报告,等团队适应了再逐步收紧。
3.4 生成策略与最佳实践
工具讲完了,但光有工具还不够。你得有一套生成策略,否则 SBOM 就是一盘散沙。
3.4.1 生成频率:什么时候生成?
| 场景 | 生成频率 | 说明 |
|---|---|---|
| CI/CD 构建 | 每次构建 | 确保 SBOM 与制品版本一一对应 |
| 依赖更新 | 依赖变更时 | 比如升级了某个库,重新生成 |
| 定期审计 | 每周/每月 | 对存量系统做周期性扫描 |
| 发布前 | 每次发布 | 作为发布工单的必选项 |
3.4.2 生成范围:包含什么?
我见过不少团队,生成的 SBOM 只包含「直接依赖」,忽略了「传递依赖」。这其实是个大坑。
举个例子:你的项目直接依赖了 Log4j 2.14.0,但 Log4j 又依赖了 JNDI 库。如果只列直接依赖,Log4Shell 漏洞(CVE-2021-44228)根本不会出现在你的 SBOM 里。你想想看,这 SBOM 还有什么用?
所以,一定要包含完整的依赖树。具体来说:
- 直接依赖:项目显式声明的依赖
- 传递依赖:依赖的依赖,递归解析
- 系统组件:操作系统包、基础镜像层
- 构建工具:Maven、Gradle、npm 等工具本身
3.4.3 最佳实践清单
- 统一格式:团队内统一使用 CycloneDX 或 SPDX,不要混用
- 版本锁定:SBOM 文件名包含制品版本号,比如
myapp-v1.2.3-bom.json - 签名验证:对 SBOM 文件做 GPG 签名,防止篡改
- 存储归档:SBOM 随制品一起归档,存到 OCI Registry 或 S3
- 自动化门禁:SBOM 生成失败 = 构建失败,强制团队执行
- 定期刷新:即使代码没变,也定期重新生成 SBOM(因为漏洞库会更新)
一句话总结:SBOM 生成不是一次性工作,而是持续的过程。工具选对、策略定好、自动化跑起来,你就能在供应链安全上迈出坚实的一步。