阅读约 7 分钟
如何把 CI/CD 构建证据锚定到一个公开的时间戳?
CI/CD 流水线可以对其构建产物、SBOM、日志和发布清单计算哈希,把它们批量汇聚成一个 Merkle 根,再发布一份 Label 309 证明——为日后的审计提供一个独立的公开时间锚点。

可以——CI/CD 流水线能够发布证明,说明它在何时构建了什么。这套做法是:对重要的输出(构建产物、SBOM、日志、发布清单)计算哈希,把这些哈希汇聚成一个 Merkle 根,再为该次构建、发布或时间窗口发布一份 Label 309 记录。日后,任何人都能证明某个具体产物或清单属于那个已承诺的批次,且该批次在某个公开区块时间或更早之前就已存在。
这并不取代 SLSA、Sigstore、GitHub Artifact Attestations 或 in-toto,而是补上它们都不直接提供的一样东西:一个独立、公开、与签发方无关、且不受任何厂商控制的时间锚点。
CI/CD 流水线究竟该证明什么?
先从你一年后可能需要拿出的证据想起。
一份 CI/CD 证明可以承诺以下内容:
- 发布产物
- 容器镜像摘要
- SBOM(软件物料清单)文件
- SLSA 来源溯源证明
- in-toto link 元数据
- 构建日志
- 测试报告
- 部署清单
- 变更日志快照
- 源码提交引用
- 依赖锁文件
- 已签名的校验和
- 发布说明
不要仅仅因为某样东西存在,就把它拿去做哈希。只对那些关乎安全、审计、事件响应、客户信任或发布完整性的产物计算哈希。
一份好的证明,回答的是一个具体的未来问题:「这个产物或清单,是不是当时我们承诺的那批发布证据中的一员?」
Merkle 批处理这套做法是怎么运作的?
对单个产物来说,一份哈希证明就够了。
但一次发布通常包含许多产物,把每一个都作为单独的交易发布太浪费了。Merkle 根解决了这个问题。你把每一项证据计算哈希得到一个叶子,再把有序的叶子汇聚成一个 32 字节的根,然后只发布这个根——一条链上记录就覆盖了整次发布。
流水线的步骤是:
- 构建产物。
- 生成或收集 SBOM、证明、日志和清单。
- 对每一项证据计算哈希,得到一个叶子。
- 组装成有序的叶子列表。
- 构建 Merkle 树并计算出根。
- 发布一份携带该根的 Label 309 记录。
- 把叶子列表和包含证明与发布证据一起保存。
日后,验证方拿出一个文件或摘要,检查它的包含证明,确认该项属于 Label 309 记录中的那个根。包含证明的大小只随批次规模的对数增长,因此即便一个根覆盖了上千个叶子,验证仍只需毫秒——完全离线,不需要网关,也不需要网络。完整的运作机制,见一条记录覆盖上千份文件。
为什么不直接依赖现有的证明就好?
用现有的证明——然后再把它们锚定到链上。
SLSA 来源溯源描述了一个软件产物在哪里、何时、以何种方式被生产出来。GitHub Artifact Attestations 为二进制文件、容器镜像等产物建立构建来源。Sigstore 把已签名的供应链元数据记录到一个公开、只追加的透明日志(名为 Rekor)中。in-toto 则是一个框架,用于验证供应链中的每一步是否按计划、由正确的一方、对正确的输入完成。
这些方案各自解决了一个真实的问题。Label 309 解决的是另一个问题:发布一份锚定在 Cardano 上、可独立验证的证明,证明某一组特定的证据在某个公开时间之前就已存在。一条稳健的流水线不会在「证明(attestation)还是存在性证明」之间二选一,而是两者并用:
- 用 SLSA 或 GitHub 证明记录构建来源
- 用 Sigstore 完成签名与透明日志相关流程
- 用 in-toto 验证供应链各步骤
- 用 Label 309 为整组证据提供一个公开时间锚点
Label 309 在此之上又增加了什么?
它增加了一份持久的、记录在 Cardano 上的公开承诺。
这份承诺可以只覆盖一份发布清单,也可以覆盖一个汇聚了众多证据项的 Merkle 根。它可以用项目或公司的身份来签名。而且,仅凭交易元数据和一个公开的 Cardano 浏览器就能完成验证——无需账户、无需登录,也不依赖原 CI 提供商的控制台还在线上。
当一个团队需要证明这些证据在某个后续事件「之前」就已存在时,这种独立性才最为关键:
- 漏洞披露
- 安全事件
- 客户安全评审
- 采购审计
- 合规期限
- 发布纠纷
- 供应链调查
这份证明给时间线提供了一个锚点,任何相关方都无法悄悄挪动它。
审计方会验证什么?
审计方应当能够从单个产物一路追溯回链上记录:
- 取出受审查的产物或 SBOM。
- 计算它的哈希。
- 用发布的 Merkle 根核对它的包含证明。
- 确认该根出现在某份 Label 309 记录中。
- 查到那笔 Cardano 交易,读取它的区块时间。
- 验证记录上的任何签名。
- 按照各自的规则去检查相关的来源溯源信息或证明。
这样就把两个问题清晰地区分开了。Label 309 证明回答的是:这组证据是否在这个公开时间之前就已被承诺? 而 SLSA、Sigstore、GitHub 或 in-toto 那一层回答的是:这组证据关于该产物是如何被构建、签名或分发的,说了些什么? 两者都不试图去做对方的事。
发布清单里该放什么?
一份发布清单应当平淡而完整。它可能包括:
- 发布名称与版本
- 仓库 URL
- 源码提交
- 构建工作流标识
- 构建调用 ID
- 产物名称与摘要
- 容器镜像摘要
- SBOM 文件摘要
- 证明文件摘要
- 测试报告摘要
- 构建日志摘要
- CI 系统报告的时间戳
- Label 309 交易引用,在发布之后补上
清单本身也可以被计算哈希并作为一个叶子收入。它同时还是那份人类可读的索引,逐一解释批次中其余每个叶子的含义。让格式保持稳定:当证据结构在一次次发布之间都可预期时,证明就更容易验证。
流水线应该给记录签名吗?
通常应该签。
Merkle 根证明的是某个列表已被承诺。签名则进一步表明:某个项目、公司、发布系统或经批准的身份「为」这份承诺背书。在 Label 309 中,这是一个可选的记录级签名——存在性主张的验证从不要求署名,但当你需要可追责性时,它随时可用。
要审慎地管理签名密钥。不要把长期有效的身份种子撒在任意的 CI 运行器上。视你的威胁模型而定,可以使用一个专用的发布身份、一个受控的签名服务、一套硬件支持的工作流,或一个有严格访问控制的自托管运行器。开源的 cardanowall CLI 正是为此而生——它与网关无关、以原始种子优先,因此能直接嵌入自动化流程,全程无需任何网站参与。端到端的流程,见在自动化中使用 CLI。
签名增加的是可追责性。它本身并不能让一条薄弱的构建流水线变得安全。
叶子列表应该存放在哪里?
把它和发布证据一起存放。
叶子列表和包含证明,正是让你日后能证明单个项的东西。如果你只发布了根,却随后弄丢了叶子列表,你仍能证明「某个」列表曾经存在——但你可能再也无法证明「某个具体」的产物就在其中。
合适的存储方式取决于工作流:
- 把它附加到发布归档里
- 把它存进产物仓库
- 把它留在内部的证据存储桶中
- 对于机密证据,把它封存为加密内容
- 当可以安全公开时,通过内容寻址存储发布它
根是锚点。叶子列表是地图。
流水线应该多久发布一次?
让证明的节奏与发布的节奏相匹配。常见的选择有:
- 每次发布对应一份证明
- 每次构建对应一份证明
- 每次部署对应一份证明
- 对于持续产生的日志,每天对应一份证明
- 每发生一次有安全意义的事件就对应一份证明
发布得太少会削弱时间线;发布得太频繁则增加成本和运营噪音。Merkle 批处理让你能挑选一个与业务问题相匹配的节奏。如果客户问「2.3.1 版到底交付了什么?」,每次发布一份证明就绰绰有余。如果监管方或审计方问的是监控是否「持续」进行,那么按天或按小时的承诺就更合适。
发布是要花钱的,因为网关要支付真实的 Cardano 交易费用外加存储费用——所以节奏是一个实打实的成本与证据之间的权衡,而不是一个免费可调的旋钮。
一份 CI/CD 证明不能证明什么?
它不能证明构建是安全的。
一份存在性证明能够表明:某个产物、SBOM、日志或清单在某个公开时间之前就已存在;该项被收入了一个已承诺的批次;以及某个密钥为记录签了名。它所认证的,到此为止。
它不能证明源码是安全的。它不能证明运行器没有被攻陷。它不能证明依赖项不含漏洞。它不能证明 SBOM 是完整的。它也不能证明某份证明是可信的——除非那份证明自身的验证规则通过。这正是为什么 Label 309 是「伴随」供应链安全工具一起使用,而非取代它们——关于这类总体边界,见一份证明不能证明什么。
一个好的初始实现是什么样的?
从一份发布证明开始。对每次发布:
- 生成你平常的产物。
- 生成或收集 SBOM 和证明。
- 创建一份发布清单。
- 对每个证据文件计算哈希,得到一个叶子。
- 构建出 Merkle 根。
- 发布一份已签名的 Label 309 记录。
- 把交易引用保存到发布说明里。
- 把清单、叶子列表和包含证明与本次发布一起存放。
这样你就有了一条实用的证据链,而无需在第一天就重新架构你的构建系统。从这里出发,再扩展到每日日志、部署清单或更高频次的自动化。
精简版
一份 CI/CD 证明,就是对发布证据的一次带时间戳的承诺。
对重要的产物、SBOM、日志、证明和清单计算哈希。把它们批量汇聚成一个 Merkle 根。把这个根发布在一份 Label 309 记录里。如果你想让某个项目或公司身份为它背书,就给记录签名。
继续用 SLSA、Sigstore、GitHub Artifact Attestations 和 in-toto 来处理来源溯源和供应链元数据。再加上 Label 309,作为那个独立的公开时间锚点,把整组证据系在一个没有任何厂商能挪动的时刻上。
延伸阅读
- 一条记录覆盖上千份文件——Merkle 批处理如何把整组文件锚定在单个根之下。
- 在自动化中使用 CLI——如何从流水线中驱动发布与验证。
- 存在性证明与 Sigstore 的对比——这两层有何不同,又在何处契合。
- SLSA 构建来源溯源——SLSA 来源溯源规范。
- Sigstore 与 Rekor 透明日志——配合公开只追加日志的无密钥签名。
- GitHub Artifact Attestations——为 GitHub 构建的产物提供构建来源。
- in-toto——软件供应链完整性框架。