4. SBOM生成工具链选型与集成

SBOM 生成工具链的选型,本质上不是“哪个工具最好”的问题,而是“在什么制品形态、什么构建阶段、什么合规格式要求下,用哪个工具最合适”的问题。Syft、Trivy、cdxgen 以及 SPDX 官方工具族各有侧重:Syft 擅长对容器镜像和文件系统做深度包清单提取,Trivy 把漏洞扫描与 SBOM 生成合并在同一条命令里,cdxgen 面向源码与多语言依赖树生成 CycloneDX,SPDX 工具族则围绕 SPDX 文档的生成、校验与转换。选型时先确定三件事:制品是镜像、二进制还是源码仓库;目标格式是 SPDX 还是 CycloneDX;SBOM 是用于合规存档还是用于后续漏洞比对。

四类工具的定位与关键差异

Syft 的输入对象是容器镜像、目录、归档文件,输出 SPDX 或 CycloneDX,它的强项是“对已构建制品做成分盘点”,不依赖源码构建环境,因此适合在镜像推送后立即生成 SBOM。Trivy 的输入同样支持镜像、文件系统、仓库,但它的输出可以同时包含 SBOM 与漏洞结果,适合“一次扫描、两类产出”的流水线。cdxgen 的输入偏向源码仓库与包管理清单,它会解析 pom.xml、package-lock.json、go.mod 等文件,生成带依赖层级的 CycloneDX,适合在构建前或构建中生成“源码级 SBOM”。SPDX 工具族(如 spdx-sbom-generator、tools-python)更强调 SPDX 文档的规范性、许可证字段完整性和文档校验,适合需要严格 SPDX 合规交付的场景。

一个常见的误区是只按“支持格式数量”选型。实际工程中更关键的是:工具能否识别当前技术栈的包管理器、能否输出依赖层级、能否在离线环境运行、能否与现有 CI 变量和制品仓库对接。例如 Java 多模块项目若只扫描镜像,可能丢失构建期依赖;而纯 Go 静态二进制若只扫源码,又可能漏掉 vendor 目录之外的运行时组件。因此,工具链往往不是单选,而是“源码阶段用 cdxgen、制品阶段用 Syft、扫描阶段用 Trivy”的组合。

SBOM 工具链在 CI/CD 中的嵌入点与触发策略 源码提交 构建 / 打包 镜像推送 制品归档 合规交付 cdxgen 输入:源码仓库 解析:pom / lock / go.mod 输出:CycloneDX(含层级) Syft 输入:镜像 / 目录 / 归档 扫描:已安装包数据库 输出:SPDX / CycloneDX Trivy 输入:镜像 / 文件系统 扫描:SBOM + 漏洞 输出:双结果一次产出 SPDX 工具族 输入:源码 / 制品 校验:许可证字段 输出:规范 SPDX 文档 自动化触发策略 ① push / PR 触发:cdxgen 生成源码级 SBOM,作为构建产物附件 ② 镜像推送后触发:Syft 生成制品 SBOM,Trivy 同步输出漏洞结果 ③ 定时 / 发布触发:SPDX 工具族校验文档完整性,归档至制品库 统一 SBOM 仓库 / 比对基线 格式归一 · 版本关联 · 差异比对 · 合规审计

CI/CD 嵌入点:把生成动作放到正确的位置

嵌入点的选择决定了 SBOM 的“新鲜度”和“覆盖面”。源码提交阶段适合放 cdxgen,因为它能在依赖解析完成后立即生成 CycloneDX,并把 SBOM 作为构建产物的一部分随代码版本一起归档。镜像推送阶段适合放 Syft 或 Trivy:Syft 负责生成制品 SBOM,Trivy 负责在同一镜像上输出漏洞结果,两者可以并行执行,也可以串行执行以减少镜像拉取次数。制品归档阶段适合放 SPDX 工具族,对最终交付的 SBOM 做格式校验和许可证字段补全。

以 GitLab CI 为例,一个可用的片段如下:

stages:
  - build
  - sbom
  - scan

generate-source-sbom:
  stage: sbom
  image: ghcr.io/cyclonedx/cdxgen:latest
  script:
    - cdxgen -o sbom-source.cdx.json -t java .
  artifacts:
    paths:
      - sbom-source.cdx.json

generate-image-sbom:
  stage: sbom
  image: anchore/syft:latest
  script:
    - syft registry.example.com/app:${CI_COMMIT_SHA} -o spdx-json > sbom-image.spdx.json
  artifacts:
    paths:
      - sbom-image.spdx.json

scan-image:
  stage: scan
  image: aquasec/trivy:latest
  script:
    - trivy image --format cyclonedx --output sbom-trivy.cdx.json registry.example.com/app:${CI_COMMIT_SHA}
    - trivy image --exit-code 1 --severity HIGH,CRITICAL registry.example.com/app:${CI_COMMIT_SHA}
  artifacts:
    paths:
      - sbom-trivy.cdx.json

这段配置的关键点在于:cdxgen 在源码阶段运行,Syft 在镜像阶段运行,Trivy 在扫描阶段同时产出 SBOM 和漏洞结果。三个产物都作为 artifacts 保留,后续可以统一上传到 SBOM 仓库。注意 Trivy 的 --exit-code 1 会让高危漏洞导致流水线失败,这是“扫描即门禁”的常见做法,但需要根据团队实际容忍度调整。

自动化触发策略:什么时候生成、什么时候比对

触发策略要回答两个问题:生成频率和比对时机。生成频率上,源码级 SBOM 建议每次 push 或 PR 都生成,因为依赖变更往往发生在提交阶段;制品级 SBOM 建议每次镜像推送都生成,因为镜像层可能因基础镜像更新而变化;SPDX 合规文档建议在发布标签或定时任务中生成,避免每次提交都产生大量冗余文档。比对时机上,建议在“新 SBOM 生成后”立即与基线 SBOM 做差异比对,识别新增组件、版本变化和许可证变化,而不是等到漏洞扫描才发现问题。

一个实用的差异比对逻辑是:以组件坐标(名称 + 版本 + 包管理器)为键,比较新旧 SBOM 的组件集合。新增键表示新引入依赖,删除键表示依赖移除,版本变化表示升级或降级。对于新增组件,应重点检查其许可证和已知漏洞;对于版本变化,应检查是否跨越了安全修复版本。这个逻辑可以用脚本实现,也可以借助 Syft 的 diff 子命令或 Trivy 的 SBOM 比对能力完成。

选型时建议先做一次“工具能力矩阵”验证:用同一个示例项目分别跑 cdxgen、Syft、Trivy、SPDX 工具族,对比组件数量、依赖层级、许可证字段完整度和输出格式兼容性。只有实际跑过,才能判断哪个工具在当前技术栈下漏报最少、字段最全。

选型决策与常见组合

如果团队以容器镜像为主要交付物,推荐“Syft + Trivy”组合:Syft 负责生成标准 SBOM,Trivy 负责漏洞扫描和门禁。如果团队以源码仓库为主、需要依赖层级,推荐“cdxgen + Syft”:cdxgen 生成源码级 CycloneDX,Syft 生成制品级 SPDX,两者互补。如果合规要求严格、必须交付 SPDX 文档,则在上述组合后追加 SPDX 工具族做校验和字段补全。无论哪种组合,都应把 SBOM 作为构建产物的一部分持久化,并与制品版本建立可追溯的关联,否则 SBOM 只是一次性输出,无法支撑后续的风险治理。

不要把 SBOM 生成放在构建成功之后才执行,也不要把 SBOM 只保存在 CI 日志里。日志会滚动丢失,SBOM 必须作为独立制品归档,并与镜像 digest、提交 SHA、构建编号建立映射,否则后续无法回答“这个漏洞对应哪个版本、哪个制品”。