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 绑定,不可篡改。

搭建步骤大致如下:

  1. 部署 Harbor 2.5+,启用 OCI Artifact 支持
  2. 配置 cosign 或 notation 作为签名工具
  3. 在 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 管理与存储的核心逻辑。我把它画出来,方便你对照理解。

SBOM 管理与存储核心架构 SBOM 生成 Harbor(制品仓库) GitLab(代码仓库) 版本管理:双版本策略(软件版本 + SBOM 修订号) 签名:cosign / notation / GPG 验证:CI / 部署 / 扫描 下游消费:漏洞扫描 / 合规审计 / 供应链管理 核心原则:生成即签名,签名即验证,验证才消费

3.6 避坑指南

最后分享几个我踩过的坑:

  • 签名密钥管理: 我曾经把 cosign 私钥放在 CI 的 Secret 里,结果密钥轮换时忘了更新,导致所有 CI 构建都失败。建议用 KMS 或 Vault 管理密钥。
  • SBOM 文件过大: 一个大型 Java 项目的 SBOM 可能超过 50MB。GitLab 对单文件有限制,Harbor 则没有。如果 SBOM 太大,考虑用 SPDX 的 JSON 格式而不是 Tag 格式。
  • 版本号冲突: 当软件版本回滚时,SBOM 版本也会回滚。这时候如果 SBOM 修订号没处理好,会出现“新版本旧 SBOM”的混乱。我的做法是:每次回滚都强制递增修订号。

好了,SBOM 的管理与存储就聊到这里。记住一句话:SBOM 不是生成出来就完事了,管好它、签好它,才能真正为你的供应链安全保驾护航。