登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  业界新闻

SLSA provenance 怎样描述构建来源

来源:17golang原创

时间:2026-11-11 22:49:56 373浏览 收藏

SLSA provenance 怎样描述构建来源?最容易犯的错误,是只写一个 Git 仓库地址,然后把它当成“来源已经完整记录”。更准确的做法是把一次构建拆成三件事:最终产物是谁、构建输入是什么、哪个受信任的构建平台执行了这次构建。SLSA provenance 正是用这三个边界描述产物是怎样产生的。

官方地址:https://slsa.dev/spec/v1.2/build-provenance

如果只记住一个判断:源码仓库或分支属于构建接口参数,解析后的具体提交及其摘要属于已解析依赖,构建平台和调用标识则属于本次执行详情。三者不能互相替代。

先把构建来源拆成三个边界

SLSA provenance 外层使用 in-toto Statement,最重要的连接点是 subject 和 predicate。subject 指向构建产生的文件、镜像或其他软件产物,并带有摘要;predicate 里的 provenance 再说明它是怎样被构建出来的。

SLSA provenance 中构建输入、源码提交摘要、执行详情和产物 subject 的结构说明图
图1:SLSA provenance 字段分层的结构说明图,展示构建来源如何连接到产物,不是运行截图。

可以先用下面这张职责表建立口径:

字段区域描述什么典型内容
subject构建输出产物名称与 sha256 摘要
buildDefinition构建定义与输入buildType、externalParameters、resolvedDependencies
runDetails本次构建执行builder.id、调用标识、时间和副产物

因此,“构建来源”不是一个单独字段。它是构建参数、被解析的依赖和可信执行平台共同组成的可解释关系。

用 externalParameters 描述构建接口

externalParameters 表示从构建平台外部传入的顶层输入,例如仓库地址、分支引用、发布通道或构建开关。它描述的是“调用构建时传了什么”,不是把所有最终解析结果都塞进一个对象。

例如,构建平台收到一个仓库地址和分支引用,可以这样表达。这个 JSON 块保持严格有效,字段含义放在代码外解释:

{
  "buildDefinition": {
    "buildType": "https://ci.example.com/build-types/container-v1",
    "externalParameters": {
      "repository": "https://git.example.com/team/checkout.git",
      "ref": "refs/heads/main"
    },
    "resolvedDependencies": []
  }
}

buildType 是解释这些参数的模板标识。它不只是一个随手填写的字符串,应该能指向描述参数结构、依赖解释方式和构建入口的规范。这样下游才知道 repository、ref 的语义是什么。

另一个边界是内部参数。由平台自己决定的运行环境信息可以放到 internalParameters,但它不是外部调用者传入的来源声明,也不应该和 externalParameters 混用同一个字段名。只有确实有助于复现、排障或事件响应时,才需要记录这部分信息。

用 resolvedDependencies 固化实际来源

仓库 URL 和分支名可能在不同时间指向不同提交,所以只记录 refs/heads/main 不能说明这一次构建到底读到了哪份源码。构建平台解析引用后,应把实际提交作为依赖资源,至少记录可定位的 URI 和摘要。

{
  "resolvedDependencies": [
    {
      "uri": "git+https://git.example.com/team/checkout@refs/heads/main",
      "digest": {
        "gitCommit": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d"
      },
      "name": "checkout-source"
    }
  ]
}

这里的分工很清楚:externalParameters.repository 和 ref 是构建接口收到的原始值,resolvedDependencies 是平台实际解析出的输入。两者都保留,才能同时回答“构建请求指向哪里”和“这次构建实际用了什么”。

如果构建脚本又下载了一个工具包、配置文件或压缩包,只要它是构建过程中获取并可能影响输出的输入,就应在可获得时作为另一个资源描述。不要把依赖摘要写成外部参数里的装饰字段,否则下游难以判断它究竟是调用者输入,还是解析后的具体资源。

用 runDetails 说明谁执行了构建

runDetails 关注“这一次执行发生在哪里”。其中最关键的是 builder.id:它标识被信任、能够正确执行构建并生成 provenance 的构建平台边界。它不是 Git 提交者,也不是某个编译器版本的别名。

{
  "runDetails": {
    "builder": {
      "id": "https://ci.example.com/builders/isolated-linux",
      "version": {
        "runner": "2026.11"
      }
    },
    "metadata": {
      "invocationId": "build-2026-11-11-0042",
      "startedOn": "2026-11-11T10:02:00Z",
      "finishedOn": "2026-11-11T10:05:12Z"
    }
  }
}

invocationId 便于把 provenance 和一次 CI 运行、审计记录或故障调查关联起来;它不能替代产物摘要,也不能替代源码提交摘要。若某些日志或完整解析配置只对排障有帮助、又不是主要构建输出,可以按实际需要作为 byproducts 引用,但不必把所有中间文件都塞进去。

把三类信息合成最小 provenance

实际写入时,可以把前面的片段合并为一个最小示例。为了让示例仍然适合复制,下面只保留结构,不伪装成某个真实 CI 产品生成的完整回执:

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "checkout-image.tar",
      "digest": {
        "sha256": "ff332109abdd"
      }
    }
  ],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://ci.example.com/build-types/container-v1",
      "externalParameters": {
        "repository": "https://git.example.com/team/checkout.git",
        "ref": "refs/heads/main"
      },
      "resolvedDependencies": [
        {
          "uri": "git+https://git.example.com/team/checkout@refs/heads/main",
          "digest": {
            "gitCommit": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d"
          }
        }
      ]
    },
    "runDetails": {
      "builder": {
        "id": "https://ci.example.com/builders/isolated-linux"
      },
      "metadata": {
        "invocationId": "build-2026-11-11-0042"
      }
    }
  }
}

注意 predicateType 使用稳定的 https://slsa.dev/provenance/v1。规范页面的版本路径用于阅读具体版本,但 predicateType 会按 SLSA 的解析规则保持主版本语义;不要把页面地址栏中的版本后缀直接复制成 predicateType。

下游验证时要检查哪些关系

provenance 只有被检查才有价值。一个实用的最小顺序是:先验证证明包签名,再确认 subject 摘要等于手头产物摘要,然后确认 predicateType、可信 builder.id、buildType 和 externalParameters 都符合预期。依赖检查可以再沿 resolvedDependencies 递归展开。

SLSA provenance 下游验证边界与符合预期或拒绝结果的结构说明图
图2:SLSA provenance 验证边界的结构说明图,展示字段比较和依赖递归检查,不是运行截图。
检查项要回答的问题不匹配时的处理
签名与信任根证明是否由认可的身份签发拒绝继续使用该 provenance
subject证明是否对应当前产物摘要视为产物或证明被替换
builder.id执行平台是否在信任范围内不能只看它声称的 SLSA 等级
buildType 与外部参数构建是否来自允许的模板和源码入口拒绝未识别或不符合策略的输入

这里的“符合预期”不是看到字段存在就算通过。例如,externalParameters 中的仓库地址必须是策略允许的规范来源;builder.id 也要和验证方预先建立的信任根匹配。SLSA Build L3 能力同样建立在信任构建平台的前提上,不等于平台内部任何角色都自动可信。

几个经常混淆的字段边界

  • 仓库地址不等于源码版本:地址和分支是请求输入,具体提交摘要才是这次构建解析到的版本线索。
  • builder 不等于编译器:builder.id 表示产生并负责记录 provenance 的构建平台边界;编译器可以作为依赖、版本信息或其他适合的字段记录。
  • provenance 不等于 SBOM:provenance 重点描述产物如何被构建;SBOM 更关注产物中包含哪些组件,两者可以通过资源引用关联,但职责不同。
  • 存在字段不等于完成验证:还要检查签名、产物摘要、构建类型、外部参数和信任根之间的关系。

最后用一张速查表判断是否写对

准备生成 SLSA provenance 时,可以按以下顺序自查:第一,subject 是否真实绑定输出摘要;第二,buildType 是否能解释构建接口;第三,externalParameters 是否只放外部输入;第四,源码引用解析出的提交是否进入 resolvedDependencies;第五,builder.id 是否能对应实际信任边界;第六,验证方是否有明确的允许值和拒绝条件。

只要这六个问题能够回答,SLSA provenance 就不再是“填一份 JSON”,而是一份能把产物、实际输入和执行平台连起来的构建来源说明。

相关问题

  • externalParameters 里要不要直接放 Git 提交摘要?通常先记录构建接口收到的参数,解析后的资源摘要放入 resolvedDependencies,除非构建接口本身就直接接收摘要。
  • 所有构建中间文件都要写进 provenance 吗?不需要;优先记录会影响输出或验证判断的依赖,排障资料可按需作为 byproducts 引用。
  • 只检查 provenance 的签名就够了吗?不够,还要确认签名者、builder.id、产物 subject 和构建参数都符合验证方预期。
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>