3、SBOM(软件物料清单)深度解析:SPDX与CycloneDX标准,SBOM的生成、验证与消费

聊到软件供应链安全,SBOM 是个绕不开的话题。说白了,SBOM 就是一份软件的“成分表”。你买包薯片都知道看看配料表,用别人的软件组件,怎么能不知道里面装了啥?

我最早接触 SBOM,是在一次客户合规审计中。对方要求我们提供所有开源组件的清单,当时我们团队翻遍了 Git 历史才凑出来一份,还漏了好几个。嗯,从那以后我就意识到,SBOM 这件事必须自动化,而且要标准化。

3.1 为什么需要 SBOM?

没有 SBOM,你的软件就像个黑盒子。出了漏洞,你根本不知道哪个组件中招了。举个例子,Log4j 漏洞爆发时,有 SBOM 的团队几小时内就能定位受影响的服务,没有的只能加班加点人工排查。

SBOM 的核心价值有三点:

  • 透明度:清楚知道软件里用了哪些组件、什么版本。
  • 可追溯:每个组件的来源、许可证、依赖关系一目了然。
  • 快速响应:漏洞出现时,能立刻判断是否受影响。

核心观点:SBOM 不是“要不要做”的问题,而是“什么时候做、怎么做”的问题。尤其在金融、医疗、政府等强监管行业,SBOM 已经是准入门槛。

3.2 两大主流标准:SPDX 与 CycloneDX

目前业界最主流的 SBOM 标准有两个:SPDX 和 CycloneDX。很多人问我该选哪个,我的回答是:看场景。

维度 SPDX CycloneDX
发起方 Linux 基金会 OWASP
侧重点 许可证合规、版权信息 安全漏洞、依赖关系
格式 Tag:Value、JSON、RDF/XML JSON、XML
适用场景 法律合规、审计 安全运维、漏洞管理
版本 2.3(当前稳定版) 1.5(当前稳定版)

我个人习惯是:许可证合规用 SPDX,安全漏洞管理用 CycloneDX。当然,两个都支持也没问题,很多工具可以互相转换。

3.2.1 SPDX 标准详解

SPDX(Software Package Data Exchange)是历史最悠久的 SBOM 标准。它最初就是为了解决许可证合规问题而生的。你想想看,一个项目里混了 GPL、MIT、Apache 的代码,如果不搞清楚,哪天被告了都不知道。

SPDX 的核心概念包括:

  • SPDX Document:整个 SBOM 的容器,包含元数据。
  • Package:一个软件包,包含名称、版本、下载位置等。
  • File:包内的具体文件,可以单独声明许可证。
  • Relationship:包与包、包与文件之间的关系(如 DEPENDS_ON、BUILD_TOOL_OF)。
  • License:许可证信息,支持 SPDX License List 中的标准标识符。

下面是一个 SPDX 2.3 的 JSON 示例片段:

{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "my-app-sbom",
  "packages": [
    {
      "SPDXID": "SPDXRef-Package-log4j-core",
      "name": "log4j-core",
      "versionInfo": "2.17.1",
      "supplier": "Organization: Apache Software Foundation",
      "downloadLocation": "https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j-core/2.17.1/",
      "licenseConcluded": "Apache-2.0",
      "licenseDeclared": "Apache-2.0",
      "copyrightText": "Copyright 2021 The Apache Software Foundation"
    }
  ],
  "relationships": [
    {
      "spdxElementId": "SPDXRef-DOCUMENT",
      "relatedSpdxElement": "SPDXRef-Package-log4j-core",
      "relationshipType": "DESCRIBES"
    }
  ]
}

小技巧:SPDX 的 licenseConcluded 字段表示“你最终认定的许可证”,而 licenseDeclared 是“组件自己声明的”。两者不一致时,以 licenseConcluded 为准。我曾经遇到过组件声明是 MIT,但实际代码里混了 GPL 的情况,这时候 licenseConcluded 就派上用场了。

3.2.2 CycloneDX 标准详解

CycloneDX 是 OWASP 推出的标准,更侧重于安全场景。它的设计更轻量,JSON 结构也更简洁。如果你主要关注漏洞管理,CycloneDX 会是更好的选择。

CycloneDX 的核心概念:

  • Bom:根对象,包含元数据和组件列表。
  • Component:组件,支持 type(library、framework、application 等)。
  • Dependency:依赖关系,支持层级嵌套。
  • Vulnerability:漏洞信息,可以内嵌或引用外部源。
  • Service:服务组件,适用于微服务架构。

CycloneDX 1.5 的 JSON 示例:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "version": 1,
  "components": [
    {
      "type": "library",
      "name": "log4j-core",
      "version": "2.17.1",
      "purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.1",
      "licenses": [
        {
          "license": {
            "id": "Apache-2.0"
          }
        }
      ]
    }
  ],
  "dependencies": [
    {
      "ref": "pkg:maven/com.mycompany/my-app@1.0.0",
      "dependsOn": [
        "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.1"
      ]
    }
  ]
}

注意:CycloneDX 使用 PURL(Package URL)来唯一标识组件。PURL 的格式是 pkg:type/namespace/name@version。不要自己拼写,用工具生成。我曾经手写 PURL 写错了分隔符,导致整个 SBOM 验证失败,排查了半天才发现是少了个斜杠。

3.3 SBOM 的生成

生成 SBOM 的方式有很多,取决于你的技术栈。我一般推荐在 CI/CD 流水线中自动生成,而不是手动维护。

3.3.1 常用生成工具

工具 支持标准 适用语言/生态
Syft SPDX, CycloneDX 多语言(容器镜像、文件系统)
Trivy SPDX, CycloneDX 多语言(侧重漏洞扫描)
CycloneDX Maven Plugin CycloneDX Java/Maven
SPDX Maven Plugin SPDX Java/Maven
npm sbom SPDX Node.js/npm

我个人最常用的是 Syft,因为它支持容器镜像和文件系统两种模式,而且输出格式丰富。举个例子,生成一个 CycloneDX 格式的 SBOM:

# 扫描一个容器镜像
syft my-app:latest -o cyclonedx-json > sbom.cyclonedx.json

# 扫描一个项目目录
syft ./my-project -o spdx-json > sbom.spdx.json

3.3.2 在 CI/CD 中集成

生成 SBOM 的最佳时机是构建阶段。我习惯在 Jenkins 或 GitHub Actions 中加一个步骤:

# GitHub Actions 示例
- name: Generate SBOM
  run: |
    syft . -o cyclonedx-json > sbom.json
- name: Upload SBOM as artifact
  uses: actions/upload-artifact@v3
  with:
    name: sbom
    path: sbom.json

建议:SBOM 生成后,最好和构建产物一起归档。这样以后回溯某个版本的依赖时,直接翻 SBOM 就行,不用重新构建。

3.4 SBOM 的验证

生成 SBOM 只是第一步,验证它是否合法、完整同样重要。我见过太多人生成完 SBOM 就扔一边了,结果里面字段缺失、格式错误,根本没法用。

3.4.1 验证工具

  • SPDX Tools:官方提供的验证器,支持 Tag:Value 和 JSON 格式。
  • CycloneDX CLI:CycloneDX 官方命令行工具,可以验证、转换、合并 SBOM。
  • sbom-validator:社区工具,支持多标准。

用 CycloneDX CLI 验证 SBOM:

cyclonedx validate --input-file sbom.cyclonedx.json --fail-on-errors

如果验证通过,你会看到类似 Valid BOM 的输出。如果有错误,工具会指出具体哪个字段有问题。

3.4.2 常见验证失败原因

  • PURL 格式错误:比如版本号前少了 @。
  • 许可证标识符不合法:SPDX License List 里没有这个 ID。
  • 依赖关系循环:A 依赖 B,B 又依赖 A,这在 SBOM 里是不允许的。
  • 必填字段缺失:比如 SPDX 的 downloadLocation 不能为空。

避坑指南:我曾经在验证一个大型项目的 SBOM 时,发现 CycloneDX CLI 报了一堆“missing component version”错误。排查后发现是某个内部依赖的版本号在构建时没传进去。从那以后,我要求所有构建脚本必须显式声明版本号,不能用 latest 或 SNAPSHOT。

3.5 SBOM 的消费

SBOM 生成并验证通过后,怎么用起来?这才是关键。SBOM 不是用来“供着”的,而是要融入日常的开发和运维流程。

3.5.1 漏洞扫描

把 SBOM 喂给漏洞扫描工具,可以快速知道哪些组件有已知漏洞。比如用 Grype 扫描 CycloneDX 格式的 SBOM:

grype sbom:sbom.cyclonedx.json

输出会列出每个组件的漏洞信息,包括 CVE 编号、严重等级、修复版本等。

3.5.2 许可证合规检查

SPDX 格式的 SBOM 包含完整的许可证信息。可以用工具检查是否有不允许的许可证(比如 GPL 在商业软件中)。我常用 license-checker 配合 SPDX SBOM 做合规门禁。

3.5.3 依赖关系分析

SBOM 里的依赖关系图可以帮助你理解组件之间的影响范围。比如某个底层库出了漏洞,通过 SBOM 可以快速找出所有直接或间接依赖它的服务。

3.5.4 策略门禁

在 CI/CD 中,可以用 SBOM 做策略检查。比如:

  • 不允许使用已知高危漏洞的组件。
  • 不允许使用 GPL 许可证的组件。
  • 不允许使用超过 N 个月未更新的组件。

一旦策略失败,流水线直接中断,阻止问题代码进入生产环境。

3.6 SBOM 全流程框架图

下面这张图展示了 SBOM 从生成到消费的完整流程,我把它总结为“生成 → 验证 → 存储 → 消费”四个阶段。

SBOM 全流程框架图 SBOM 全流程框架图 1. 生成 2. 验证 3. 存储 4. 消费 验证失败,回退修复 生成方式 Syft / Trivy / Maven Plugin CI/CD 流水线自动生成 验证方式 CycloneDX CLI / SPDX Tools 格式检查 + 必填字段校验 存储方式 构建产物归档 / OCI Registry 版本化管理 消费场景 漏洞扫描 / 许可证检查 / 策略门禁

3.7 总结与最佳实践

SBOM 这件事,说起来简单,做起来细节很多。我最后分享几条经验:

  • 尽早自动化:不要在项目快上线了才想着补 SBOM,从第一个 commit 开始就集成。
  • 标准要统一:团队内部统一用 SPDX 还是 CycloneDX,不要混用,否则消费工具要适配两套。
  • 验证不能省:生成完 SBOM 一定要跑一遍验证,否则可能是一份“假清单”。
  • 消费要落地:SBOM 生成后要真正用起来,比如集成到漏洞扫描、策略门禁中,否则就是一份“死文档”。
  • 版本要对应:每个版本的软件都要有对应的 SBOM,不能一个 SBOM 覆盖所有版本。

一句话总结:SBOM 是软件供应链安全的基石。选对标准(SPDX 或 CycloneDX),用对工具(Syft、Trivy、CycloneDX CLI),在 CI/CD 中自动生成、验证、消费,才能真正发挥它的价值。