4、SBOM生成实战:从容器镜像、源码、二进制文件生成SBOM
好,咱们进入实战环节了。前面几章讲了SBOM是什么、为什么重要,这一章咱们直接上手——怎么把SBOM真正生成出来。
我见过不少团队,工具选了一堆,文档看了好几遍,结果一跑就报错,或者生成的SBOM缺胳膊少腿。说白了,生成SBOM这件事,选对工具只是第一步,理解每个场景的坑才是关键。
这一章,我带你从三个最常见的场景入手:容器镜像、源码项目、二进制文件。每个场景我都会给出具体的工具、命令、以及我踩过的坑。
核心观点:没有一种工具能通吃所有场景。容器镜像用Syft最顺手,源码项目用CycloneDX插件最规范,二进制文件……嗯,这个最头疼,我后面细说。
4.1 容器镜像:Syft 实战
容器镜像的SBOM生成,我个人最推荐 Syft。原因很简单:快、准、输出格式全。当然Trivy也能做,但Syft更专注SBOM生成这件事。
先看一个最基本的命令:
# 安装 Syft(macOS/Linux)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# 生成 SBOM(默认 SPDX JSON 格式)
syft nginx:latest -o spdx-json > nginx-sbom.spdx.json
# 指定 CycloneDX 格式
syft nginx:latest -o cyclonedx-json > nginx-sbom.cdx.json
跑完之后,你会得到一个JSON文件。里面包含了nginx镜像里所有操作系统包(apt、rpm)和语言包(Python、Node.js等)。
我的习惯:生成之后,先用 syft nginx:latest -o table 看一眼概览。这样能快速确认工具是否识别到了所有层。有一次我发现某个镜像少了Python依赖,后来才意识到是基础镜像用了distroless——Syft对distroless的支持有限。
如果你在CI/CD里用,建议加上 --scope all-layers 参数:
syft nginx:latest --scope all-layers -o spdx-json > sbom.json
为什么要加这个?默认情况下Syft只扫描最后一层。但有些依赖可能藏在中间层里。我在一个项目里遇到过,基础镜像里装了一个旧版OpenSSL,但最终层把它删了——结果SBOM里没包含,审计时被问住了。从那以后,我只要做合规审计,一律用 --scope all-layers。
4.2 源码项目:CycloneDX Maven/Gradle 插件
源码项目的SBOM生成,我建议直接用 CycloneDX 官方插件。为什么?因为源码的依赖关系最复杂——传递性依赖、作用域(compile/test/provided)、版本冲突……这些只有构建工具自己最清楚。
以Maven项目为例:
<!-- pom.xml 中添加插件 -->
<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>
</plugin>
# 执行生成
mvn package
生成的文件在 target/bom.json 里。打开看看,你会发现它把每个依赖的 purl(Package URL)、版本、licenses 都列得清清楚楚。
Gradle项目类似:
// build.gradle
plugins {
id 'org.cyclonedx.bom' version '1.7.4'
}
// 执行
gradle cyclonedxBom
注意:如果你用的是多模块项目,记得用 makeAggregateBom 目标(Maven)或 cyclonedxBom 任务(Gradle)。否则每个子模块只会生成自己的SBOM,合起来很麻烦。我曾经吃过这个亏——审计时发现SBOM里只有主模块的依赖,子模块的依赖全漏了。
4.3 二进制文件:tern 与手动分析
二进制文件是最头疼的场景。为什么?因为二进制文件不像源码有明确的依赖声明,也不像容器镜像有清晰的层结构。你拿到一个 .so 文件或者一个编译好的可执行文件,怎么知道它用了哪些库?
我常用的工具是 tern。它专门针对二进制文件做SBOM生成:
# 安装 tern
pip install tern
# 分析一个二进制文件
tern report --image /path/to/binary --output-format spdx
但说实话,tern 的准确率有限。它主要靠扫描文件系统中的已知库文件(比如 libssl.so.1.1)来推断。如果二进制是静态编译的,或者库被重命名了,tern 基本就抓瞎了。
这时候怎么办?我分享一个我自己的“土办法”:
- 先用
strings提取版本信息:strings binary | grep -i "version\|openssl\|libc" - 再用
ldd看动态链接库:ldd binary列出所有运行时依赖 - 最后手动查每个库的版本:比如
libc.so.6对应 glibc 的哪个版本
这个过程很痛苦,但合规审计时不得不做。我建议你把二进制文件的SBOM生成放在 构建阶段 做——在编译完成后、打包之前,直接从构建环境里生成SBOM,这样准确率最高。
4.4 三种场景对比总结
| 场景 | 推荐工具 | 输出格式 | 准确率 | 自动化难度 |
|---|---|---|---|---|
| 容器镜像 | Syft / Trivy | SPDX, CycloneDX | 高(层结构清晰) | 低(一条命令) |
| 源码项目 | CycloneDX Maven/Gradle 插件 | CycloneDX | 高(构建工具直接解析) | 低(集成到构建流程) |
| 二进制文件 | tern + 手动分析 | SPDX | 中低(依赖文件系统扫描) | 高(需要人工介入) |
我的建议:能走源码就走源码,能走镜像就走镜像。二进制文件SBOM只作为“最后手段”。如果你在合规审计中必须提供二进制文件的SBOM,记得在文档里注明“基于文件系统扫描,可能存在遗漏”。
4.5 避坑指南:我踩过的三个坑
最后,分享三个我亲身踩过的坑,希望能帮你省点时间:
- 坑一:SBOM 里没有 license 字段。 有一次我用Syft生成SBOM,发现很多包的license是“NOASSERTION”。后来查了才知道,是因为镜像里的包没有嵌入license信息。解决办法:用
--add-license参数强制从已知数据库补充。 - 坑二:多架构镜像的SBOM不一致。 同一个镜像名,在amd64和arm64上跑的依赖可能不同。我建议对每个架构单独生成SBOM,并在文件名里标注架构。
- 坑三:SBOM 文件太大。 一个大型Java项目的SBOM可能有几万行。CI/CD里每次构建都生成一次,存储和传输都是问题。我的做法是:只保留最新版本的SBOM,历史版本用diff工具对比变化。