4、SBOM生成实战:从容器镜像、源码、二进制文件生成SBOM

好,咱们进入实战环节了。前面几章讲了SBOM是什么、为什么重要,这一章咱们直接上手——怎么把SBOM真正生成出来。

我见过不少团队,工具选了一堆,文档看了好几遍,结果一跑就报错,或者生成的SBOM缺胳膊少腿。说白了,生成SBOM这件事,选对工具只是第一步,理解每个场景的坑才是关键。

这一章,我带你从三个最常见的场景入手:容器镜像、源码项目、二进制文件。每个场景我都会给出具体的工具、命令、以及我踩过的坑。

核心观点:没有一种工具能通吃所有场景。容器镜像用Syft最顺手,源码项目用CycloneDX插件最规范,二进制文件……嗯,这个最头疼,我后面细说。

SBOM 生成三大场景与工具链 容器镜像 Syft / Trivy 源码项目 CycloneDX Maven/Gradle 二进制文件 tern / 手动分析 SPDX / CycloneDX JSON CycloneDX XML/JSON SPDX Tag:Value 💡 提示:容器镜像和源码项目已有成熟工具链,二进制文件仍是“手工活”居多

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 基本就抓瞎了。

这时候怎么办?我分享一个我自己的“土办法”:

  1. 先用 strings 提取版本信息:strings binary | grep -i "version\|openssl\|libc"
  2. 再用 ldd 看动态链接库:ldd binary 列出所有运行时依赖
  3. 最后手动查每个库的版本:比如 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工具对比变化。