2、SBOM生成实战:用Syft和Trivy动手生成,搞懂JSON与XML的差异
说实话,很多团队把SBOM当成一个「交作业」的东西——CI/CD里加个插件,跑完生成个文件,扔给安全部门就完事了。但如果你真这么干,后面漏洞修复那一步肯定会踩坑。
我个人习惯是,先亲手跑一遍工具,看看生成的SBOM到底长什么样。你只有亲眼见过它的结构,才知道怎么用它来驱动后续的修复流程。这一章我们就来动手,用Syft和Trivy生成真实的SBOM,然后掰开揉碎看看JSON和XML这两种格式到底差在哪。
2.1 选对工具:Syft vs Trivy,我什么时候用哪个?
市面上能生成SBOM的工具不少,但最主流的就两个:Anchore家的Syft,和Aqua Security家的Trivy。我在项目里两个都用过,简单说说我的感受。
| 特性 | Syft | Trivy |
|---|---|---|
| 核心定位 | 专注SBOM生成,轻量、快速 | 全能型:SBOM + 漏洞扫描 + 密钥检测 |
| 支持格式 | SPDX、CycloneDX、Syft自有的JSON | SPDX、CycloneDX(输出时指定) |
| 扫描速度 | 极快,适合CI流水线 | 稍慢(因为同时做漏洞匹配) |
| 我的使用场景 | 纯生成SBOM,不关心漏洞 | 生成SBOM后直接看漏洞 |
2.2 动手实战:用Syft生成第一个SBOM
我们先从Syft开始。安装很简单,一行命令搞定:
# macOS / Linux
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# 验证安装
syft version
然后找一个你本地的Docker镜像,比如我常用的 nginx:alpine:
syft nginx:alpine -o spdx-json > sbom-nginx-spdx.json
跑完之后,你会得到一个JSON文件。打开看看,结构大概是这样的:
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "nginx:alpine",
"packages": [
{
"name": "libcrypto1.1",
"versionInfo": "1.1.1k-r0",
"SPDXID": "SPDXRef-1",
"supplier": "Organization: Alpine Linux",
"licenseConcluded": "OpenSSL"
},
{
"name": "nginx",
"versionInfo": "1.21.6-r0",
"SPDXID": "SPDXRef-2",
"supplier": "Organization: Alpine Linux",
"licenseConcluded": "BSD-2-Clause"
}
]
}
嗯,这里要注意:packages数组里每个元素就是一个组件。我刚开始看的时候觉得挺直观的,但后来发现一个问题——SPDX格式的JSON里,依赖关系是单独放在relationships字段里的,不是嵌套在包里面。这意味着你要解析依赖树,得把两个字段关联起来看。
2.3 换一种格式:生成CycloneDX的XML
SPDX是Linux基金会推的,CycloneDX则是OWASP推的。很多安全工具更倾向于CycloneDX,尤其是XML版本。我们来生成一个看看:
syft nginx:alpine -o cyclonedx-xml > sbom-nginx-cyclonedx.xml
打开XML文件,你会发现结构完全不同:
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:..." version="1">
<components>
<component type="library">
<name>libcrypto1.1</name>
<version>1.1.1k-r0</version>
<licenses>
<license>
<id>OpenSSL</id>
</license>
</licenses>
</component>
<component type="library">
<name>nginx</name>
<version>1.21.6-r0</version>
<licenses>
<license>
<id>BSD-2-Clause</id>
</license>
</licenses>
</component>
</components>
<dependencies>
<dependency ref="pkg:alpine/nginx@1.21.6-r0">
<dependency ref="pkg:alpine/libcrypto1.1@1.1.1k-r0"/>
</dependency>
</dependencies>
</bom>
<components>和<dependencies>两个独立的部分。而SPDX的JSON里,依赖关系是通过relationships字段表达的,每个relationship有一个spdxElementId和relatedSpdxElement。
2.4 JSON vs XML:到底差在哪?
我经常被问到:「到底该用JSON还是XML?」说实话,没有绝对答案。但根据我踩过的坑,可以给你几个判断依据:
| 对比维度 | JSON | XML |
|---|---|---|
| 可读性 | 人类友好,结构紧凑 | 标签多,文件体积大 |
| 解析性能 | 快,原生JavaScript/Python支持 | 慢,需要XML解析器 |
| Schema验证 | 较弱(JSON Schema不够成熟) | 强(XSD、DTD成熟) |
| 工具生态 | 现代工具链首选 | 传统企业工具链常用 |
| 我的推荐 | CI/CD流水线、微服务场景 | 合规审计、与旧系统对接 |
2.5 用Trivy一步到位:生成SBOM + 漏洞扫描
如果你像我一样懒,不想先跑Syft再跑漏洞扫描,那直接用Trivy吧。它可以在生成SBOM的同时告诉你哪些组件有漏洞:
# 生成CycloneDX格式的SBOM,同时显示漏洞
trivy image --format cyclonedx --output sbom-trivy.json nginx:alpine
# 如果你只想看漏洞,不想输出SBOM文件
trivy image nginx:alpine
Trivy的输出里,SBOM部分和Syft差不多,但多了一个vulnerabilities字段。我个人习惯是:在CI/CD里用Trivy生成SBOM并直接做漏洞阻断,这样一次扫描解决两个问题。
2.6 核心知识图谱:SBOM生成流程
下面这张图总结了从镜像到SBOM再到漏洞修复的完整链路,你可以把它贴在工位旁边:
2.7 避坑指南:我踩过的三个坑
-
格式混用导致解析失败
我曾经把SPDX JSON格式的SBOM直接传给一个只认CycloneDX的工具,结果它报了一堆莫名其妙的错误。后来我学乖了:生成之前先问清楚下游要什么格式。 -
依赖关系不完整
有一次我用Syft扫描一个Java应用,生成的SBOM里只有直接依赖,没有传递依赖。后来发现是因为我没有指定--scope all-layers参数。默认情况下,Syft只扫描最后一层,如果你需要完整的依赖树,记得加这个参数。 -
版本号不一致
Trivy和Syft对同一个组件的版本号解析方式可能不同。比如某个Alpine包,Syft显示的是1.1.1k-r0,Trivy可能显示1.1.1k。这会导致漏洞匹配时出现偏差。我的做法是:统一用一个工具生成SBOM和做漏洞扫描,避免版本号打架。