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 从生成到消费的完整流程,我把它总结为“生成 → 验证 → 存储 → 消费”四个阶段。
3.7 总结与最佳实践
SBOM 这件事,说起来简单,做起来细节很多。我最后分享几条经验:
- 尽早自动化:不要在项目快上线了才想着补 SBOM,从第一个 commit 开始就集成。
- 标准要统一:团队内部统一用 SPDX 还是 CycloneDX,不要混用,否则消费工具要适配两套。
- 验证不能省:生成完 SBOM 一定要跑一遍验证,否则可能是一份“假清单”。
- 消费要落地:SBOM 生成后要真正用起来,比如集成到漏洞扫描、策略门禁中,否则就是一份“死文档”。
- 版本要对应:每个版本的软件都要有对应的 SBOM,不能一个 SBOM 覆盖所有版本。
一句话总结:SBOM 是软件供应链安全的基石。选对标准(SPDX 或 CycloneDX),用对工具(Syft、Trivy、CycloneDX CLI),在 CI/CD 中自动生成、验证、消费,才能真正发挥它的价值。