3、SPDX标准详解:SPDX文档结构、SPDX字段、SPDX 2.2 vs 2.3 差异、使用spdx-tools生成SPDX
聊到SBOM,就绕不开SPDX。这个标准在软件供应链安全领域,基本就是“通用语言”般的存在。我记得刚接触SBOM那会儿,面对一堆XML和JSON格式的文档,说实话有点懵。但后来摸透了SPDX的结构,发现它其实设计得非常清晰——说白了,就是一张标准化的“物料清单”,只不过记录的是软件组件。
3.1 SPDX文档结构:一张清单长什么样?
一个标准的SPDX文档,你可以把它想象成一个三层结构的文件夹:
- 文档头(Document Creation Info):谁创建的?什么时候?用什么工具?版本号多少?
- 包信息(Packages):清单里到底有哪些组件?每个组件的名称、版本、下载地址、校验和。
- 关系(Relationships):这些组件之间是什么关系?A依赖B?B是A的补丁?
嗯,这里要注意:SPDX文档本身也是一个“包”,它有自己的SPDX ID。这个设计我一开始觉得有点绕,但后来发现它让文档可以互相引用,非常灵活。
核心概念:SPDX文档的根节点是 SPDXRef-DOCUMENT,所有包、文件、片段都通过 SPDXRef-xxx 格式的ID进行引用。这是整个文档的“锚点”。
下面这张图,是我个人习惯用来给团队新人讲解SPDX结构的,你可以看看它怎么把零散的信息串起来:
3.2 核心字段详解:creationInfo、packages、relationships
你想想看,一个SBOM如果只有组件列表,没有创建者信息,也没有组件间的关系,那它就是个半成品。SPDX用三个核心字段解决了这个问题。
3.2.1 creationInfo:文档的“身份证”
这个字段记录了文档本身的元数据。我在项目中遇到过好几次,因为creationInfo里没写清楚工具版本,导致审计时被质疑SBOM的可信度。所以,我建议你养成习惯,把下面这些信息填完整:
| 字段 | 说明 | 示例 |
|---|---|---|
created |
文档创建时间(UTC) | 2025-03-15T10:30:00Z |
creators |
创建者(人/组织/工具) | Tool: spdx-tools-0.8.2 |
licenseListVersion |
使用的许可证列表版本 | 3.21 |
comment |
可选备注 | Generated for release v2.0 |
我的习惯:在creators里同时写上“Person: 你的名字”和“Tool: 生成工具名及版本”。这样既有人工审核痕迹,又有自动化工具背书,审计时少很多麻烦。
3.2.2 packages:清单里的“主角”
每个包都是一个独立的实体。SPDX 2.2里,一个包必须包含以下关键信息:
- SPDXID:唯一标识,比如
SPDXRef-Package-A - name:包名,比如
log4j-core - versionInfo:版本号,比如
2.17.1 - downloadLocation:下载地址,没有就用
NOASSERTION - checksums:校验和(SHA1、SHA256等),这是安全验证的关键
- licenseConcluded:你最终认定的许可证
- copyrightText:版权声明,没有就用
NOASSERTION
嗯,这里有个坑:licenseConcluded 和 licenseDeclared 的区别。前者是你分析后得出的结论,后者是包本身声明的。我曾经见过一个项目直接把 licenseDeclared 当结论用,结果漏掉了包内嵌的其他许可证——这问题在审计时被揪出来,挺尴尬的。
3.2.3 relationships:组件间的“关系网”
光有包列表还不够,你得告诉别人这些包是怎么关联的。SPDX定义了几十种关系类型,常用的有:
| 关系类型 | 含义 | 示例 |
|---|---|---|
DEPENDS_ON |
A依赖B | my-app DEPENDS_ON log4j |
CONTAINS |
A包含B | docker-image CONTAINS nginx |
GENERATED_FROM |
A由B生成 | binary GENERATED_FROM source |
BUILD_TOOL_OF |
A是B的构建工具 | maven BUILD_TOOL_OF my-app |
PATCH_FOR |
A是B的补丁 | hotfix PATCH_FOR log4j |
避坑指南:我曾经在生成SBOM时,把所有依赖都写成了 DEPENDS_ON,结果审计方追问“这个测试依赖也是运行时依赖吗?”——后来我学会了区分 DEPENDS_ON、TEST_DEPENDENCY_OF 和 DEV_DEPENDENCY_OF。关系越精确,SBOM的价值越高。
3.3 SPDX 2.2 vs 2.3:差异在哪里?
SPDX 2.3是2022年发布的,相比2.2,它主要补了几个关键短板。我直接给你列个对比表,这样更清楚:
| 特性 | SPDX 2.2 | SPDX 2.3 |
|---|---|---|
| 安全参考 | 不支持 | 新增 externalRef 安全类别,可引用CVE、CVSS |
| 包验证 | 仅支持SHA1/SHA256 | 新增SHA512、BLAKE2b、BLAKE3等算法 |
| 关系类型 | 约20种 | 新增 DATA_OF、ANCESTOR_OF、VARIANT_OF 等 |
| 生命周期 | 无明确支持 | 新增 lifecycle 字段,支持构建、测试、部署阶段 |
| AI/ML模型 | 不支持 | 新增 AI、ML 包类型 |
我个人觉得,2.3最大的亮点是安全参考。以前你要在SBOM里关联CVE,得自己加注释,现在直接用 externalRef 就能搞定。举个例子:
{
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "http://spdx.org/rdf/references/cve",
"referenceLocator": "CVE-2021-44228"
}
]
}
你想想看,有了这个,安全团队扫描SBOM时,直接就能知道哪个组件有已知漏洞,效率提升不是一点半点。
3.4 使用spdx-tools生成SPDX
理论说完了,咱们来点实际的。spdx-tools是SPDX官方维护的Java工具,支持2.2和2.3。我一般用它来做两件事:一是从现有项目生成SBOM,二是验证别人给的SBOM格式对不对。
3.4.1 安装与基本用法
spdx-tools是Java写的,所以你得先装JDK 8+。然后直接下载jar包:
wget https://github.com/spdx/tools-java/releases/download/v1.1.6/spdx-tools-1.1.6-jar-with-dependencies.jar
生成一个简单的SPDX 2.3文档,命令如下:
java -jar spdx-tools-1.1.6-jar-with-dependencies.jar \
-in /path/to/your/project \
-out output.spdx.json \
-format JSON \
-specVersion 2.3
嗯,这里要注意:-in 参数指向的是项目根目录,工具会自动扫描常见的包管理文件(pom.xml、package.json、go.mod等)。
3.4.2 验证SBOM
我经常收到合作方发来的SBOM,但格式对不对心里没底。这时候就用spdx-tools的验证功能:
java -jar spdx-tools-1.1.6-jar-with-dependencies.jar \
-verify \
-in output.spdx.json
如果输出 SPDX document is valid,那就放心了。如果有错误,它会告诉你具体哪个字段有问题。
我的经验:在CI/CD流水线里,我会加一步spdx-tools验证。只要SBOM验证不通过,构建就失败。这招逼着团队把SBOM质量提上来了,审计时基本没出过问题。
3.4.3 从代码生成更精确的SBOM
如果你需要更精细的控制,可以用spdx-tools的Java API。下面是一个生成单个包的例子:
SpdxDocument document = new SpdxDocumentImpl(
new SpdxDocument("SPDXRef-DOCUMENT"));
document.setCreationInfo(new SpdxCreationInfo(
Arrays.asList(new SpdxCreator("Tool: spdx-tools-1.1.6")),
new DateTime(new Date())));
document.setSpecVersion("SPDX-2.3");
SpdxPackage pkg = new SpdxPackageImpl(
new SpdxPackage("SPDXRef-Package-A"));
pkg.setName("log4j-core");
pkg.setVersionInfo("2.17.1");
pkg.setDownloadLocation("https://repo1.maven.org/...");
pkg.addChecksum(new SpdxChecksum(
SpdxChecksumAlgorithm.SHA256,
"a1b2c3d4e5f6..."));
document.getPackages().add(pkg);
document.getRelationships().add(
new SpdxRelationship(
document.getSpdxDocument(),
pkg,
SpdxRelationshipType.DESCRIBES));
// 输出为JSON
SpdxWriterFactory.getInstance()
.getWriter(SpdxWriterFactory.SpdxWriterType.JSON)
.write(document, new FileOutputStream("output.spdx.json"));
这段代码虽然看着长,但逻辑很清晰:创建文档、加包、加关系、输出。我在自动化生成SBOM的脚本里,就是用这套API批量处理几百个组件的。
避坑指南:生成SBOM时,downloadLocation 和 checksums 一定要填。我曾经见过一个SBOM,所有包的下载地址都是 NOASSERTION,校验和也是空的——这种SBOM基本等于废纸,因为没人能验证你用的到底是哪个版本的包。
好了,SPDX的核心内容就这些。从文档结构到字段细节,再到版本差异和工具实操,你照着这个思路去落地,基本不会跑偏。记住一点:SBOM不是生成出来就完事了,它是要给别人看的、要经得起审计的。所以,每个字段都认真对待,别留坑。