3、SBOM管理与存储:从仓库搭建到签名验证
SBOM 生成出来只是第一步。怎么存、怎么管、怎么保证别人拿到的 SBOM 没被篡改——这些才是真正让人头疼的问题。我见过不少团队,SBOM 倒是生成了,往 Git 里一丢就完事。结果呢?版本混乱、权限失控、下游拿到的文件跟原始对不上。嗯,今天我们就来聊聊怎么把 SBOM 管好。
3.1 SBOM 仓库选型:Harbor vs GitLab
我个人习惯把 SBOM 仓库分成两类:一类是制品仓库,一类是代码仓库。说白了,SBOM 本身是个制品,但它又跟代码版本强相关。所以选哪个,得看你的场景。
| 维度 | Harbor | GitLab |
|---|---|---|
| 定位 | 容器镜像 & 制品仓库 | 代码仓库 + CI/CD 平台 |
| SBOM 存储方式 | 作为镜像附件(cosign/notation) | 作为仓库文件(JSON/SPDX) |
| 版本管理 | 基于镜像 Tag + Digest | 基于 Git Commit + Tag |
| 访问控制 | 项目级 + Robot 账户 | 仓库级 + CI Job Token |
| 签名集成 | 原生支持 cosign/notation | 需通过 CI 调用外部工具 |
| 适用场景 | 容器化部署、镜像供应链 | 源码级追溯、多语言项目 |
我在项目中遇到过一种情况:团队用 GitLab 管理代码,SBOM 也放在 GitLab 里。但后来镜像发布到 Harbor,发现 SBOM 跟镜像对不上——因为 CI 里生成 SBOM 的时机跟镜像构建差了 30 秒。你想想看,这 30 秒里依赖可能已经变了。所以我的建议是:如果走容器化路线,SBOM 必须跟镜像绑定,用 Harbor 附件方式存;如果走源码审计路线,SBOM 放 GitLab 更灵活。
3.2 SBOM 仓库搭建实战
3.2.1 基于 Harbor 的 SBOM 仓库
Harbor 从 2.5 版本开始支持通过 OCI Artifact 存储 SBOM。说白了,就是把 SBOM 当成镜像的一个“标签附件”。
核心思路: 每个镜像推送后,立即生成 SBOM 并 attach 到该镜像上。这样 SBOM 跟镜像 digest 绑定,不可篡改。
搭建步骤大致如下:
- 部署 Harbor 2.5+,启用 OCI Artifact 支持
- 配置 cosign 或 notation 作为签名工具
- 在 CI 中集成 SBOM 生成与上传
这里给一段我常用的 CI 脚本片段:
# 生成 SBOM(以 Syft 为例)
syft packages ${IMAGE_REF} -o spdx-json > sbom.spdx.json
# 将 SBOM attach 到镜像
cosign attach sbom --sbom sbom.spdx.json --type spdx ${IMAGE_REF}
# 签名 SBOM
cosign sign --key cosign.key ${IMAGE_REF}
小提示: 记得把 cosign.key 放在密钥管理服务里,别硬编码在 CI 变量中。我曾经见过有人把私钥写在 .gitlab-ci.yml 里……嗯,后果你懂的。
3.2.2 基于 GitLab 的 SBOM 仓库
如果你更倾向于源码级管理,GitLab 是个好选择。我建议在项目根目录下建一个 sboms/ 目录,按版本号存放。
项目根目录/
├── sboms/
│ ├── v1.0.0/
│ │ ├── sbom.spdx.json
│ │ └── sbom.spdx.json.sig
│ ├── v1.1.0/
│ │ ├── sbom.spdx.json
│ │ └── sbom.spdx.json.sig
│ └── latest/
│ └── sbom.spdx.json
├── .gitlab-ci.yml
└── ...
为什么还要放一个 latest 目录?因为下游工具经常需要拉取最新 SBOM,每次都去翻 Tag 太麻烦。但注意,latest 必须是指向最新 Tag 的软链接或副本,不能手动维护。
3.3 SBOM 版本管理策略
SBOM 的版本管理,说白了就是回答三个问题:
- 什么时候生成新版本? 每次构建时?每次发布时?
- 版本号怎么定? 跟软件版本走?还是独立版本?
- 旧版本怎么处理? 保留多久?
我个人习惯采用双版本策略:
| 版本维度 | 规则 | 示例 |
|---|---|---|
| 软件版本 | 与发布版本一致 | v2.3.1 |
| SBOM 修订号 | 当 SBOM 内容修正时递增 | v2.3.1-sbom.2 |
为什么会需要修订号?我在项目中遇到过:生成 SBOM 时 Syft 版本太老,漏掉了某个依赖。后来升级了 Syft,重新生成 SBOM,但软件版本没变。这时候如果 SBOM 版本跟软件版本完全一致,下游就分不清哪个是最新的。加个 -sbom.2 后缀,一目了然。
注意: 修订号只用于修正 SBOM 内容,不能改变软件本身的依赖关系。如果软件依赖真的变了,那应该发新版本,而不是改 SBOM。
3.4 SBOM 签名与验证机制
SBOM 不签名,等于裸奔。你想想看,攻击者可以轻易替换你的 SBOM 文件,把恶意依赖伪装成合法组件。我见过最离谱的一次,是有人直接改了 GitLab 仓库里的 SBOM 文件,然后下游的漏洞扫描系统还信了……
3.4.1 签名流程
目前主流方案是 cosign(针对容器镜像)和 GPG(针对文件)。我推荐用 cosign,因为它跟 Harbor 集成度更高。
# 生成密钥对
cosign generate-key-pair
# 签名 SBOM 文件
cosign sign-blob --key cosign.key sbom.spdx.json > sbom.spdx.json.sig
# 验证签名
cosign verify-blob --key cosign.pub --signature sbom.spdx.json.sig sbom.spdx.json
如果是 Harbor 环境,更推荐用 Notation,因为它符合 OCI Artifact 规范,未来兼容性更好。
3.4.2 验证策略
签名只是手段,验证才是目的。我建议在以下环节强制验证:
- CI 构建时: 拉取基础镜像的 SBOM,验证签名后再构建
- 部署前: 验证最终镜像的 SBOM 签名
- 漏洞扫描时: 只信任已签名的 SBOM
关键原则: 不验证签名,就不使用 SBOM。这条规则应该写进你的安全策略里,而不是靠自觉。
3.5 知识体系总览
下面这张图概括了 SBOM 管理与存储的核心逻辑。我把它画出来,方便你对照理解。
3.6 避坑指南
最后分享几个我踩过的坑:
- 签名密钥管理: 我曾经把 cosign 私钥放在 CI 的 Secret 里,结果密钥轮换时忘了更新,导致所有 CI 构建都失败。建议用 KMS 或 Vault 管理密钥。
- SBOM 文件过大: 一个大型 Java 项目的 SBOM 可能超过 50MB。GitLab 对单文件有限制,Harbor 则没有。如果 SBOM 太大,考虑用 SPDX 的 JSON 格式而不是 Tag 格式。
- 版本号冲突: 当软件版本回滚时,SBOM 版本也会回滚。这时候如果 SBOM 修订号没处理好,会出现“新版本旧 SBOM”的混乱。我的做法是:每次回滚都强制递增修订号。
好了,SBOM 的管理与存储就聊到这里。记住一句话:SBOM 不是生成出来就完事了,管好它、签好它,才能真正为你的供应链安全保驾护航。