Packer SLSA 溯源 CI 参考工作流:provenance 后处理器与 GitHub Actions 无密钥签名实战
2026/9/21 18:36:24 网站建设 项目流程

Packer SLSA 溯源 CI 参考工作流:provenance 后处理器与 GitHub Actions 无密钥签名实战

【免费下载链接】packerPacker is a tool for creating identical machine images for multiple platforms from a single source configuration.项目地址: https://gitcode.com/gh_mirrors/pa/packer

本文围绕 Packer 仓库 examples/ci/README.md 中提供的两份可直接复制使用的 CI 参考工作流展开,系统讲解如何用 Packer 的provenance后处理器在 GitHub Actions 中生成 SLSA Provenance v1 溯源声明,并分别以「L2 无密钥(keyless)签名」和「L3 兼容的委托签名」两种模式落地。读完本文,你将掌握:SLSA 构建等级中 Packer 的职责边界、provenance后处理器的完整配置参数、packer verify-attestation的策略校验用法,以及两份可直接迁移到自建项目.github/workflows/的完整 CI 工作流。

文档定位:参考工作流而非仓库自用 CI

examples/ci/目录下的工作流是复制粘贴型参考模板,用于配合 Packer 的provenance后处理器产出 SLSA 溯源声明。需要特别强调的是:这些工作流并未接入当前仓库自身的 CI,而是面向所有使用 Packer 构建镜像的第三方项目。使用时需要将它们复制到你自己项目的.github/workflows/目录,并适配模板路径、构建产物路径等具体内容。

目录包含三份文件:

  • examples/ci/README.md:本文讲解的主文档,说明 SLSA 等级定位与两份工作流的分工;
  • examples/ci/github-actions-l2-keyless.yml:L2 模式,Packer 使用工作流的 OIDC 身份无密钥签名溯源声明、上传 Rekor 透明日志,并用packer verify-attestation验证签名声明;
  • examples/ci/github-actions-l3-delegated.yml:L3 兼容模式,构建 job 只负责构建并发布摘要(digest),溯源生成与签名委托给隔离的可复用工作流,使构建步骤无法触达签名材料。

SLSA 构建等级与 Packer 的职责边界

主文档给出了一张关键表格,明确了 SLSA Build 等级大多属于构建平台的属性,而非构建工具的属性。Packer 是工具,因此它的作用范围是:

SLSA 构建等级要求Packer 的角色Packer 提供的能力
L1溯源存在且被分发完全由 Packer 承担溯源生成
L2溯源由托管平台签名Packer 通过 CI OIDC 身份签名CI 中的无密钥签名
L3加固平台;构建步骤无法触达签名密钥平台属性;Packer 与之兼容委托签名模式
L4SLSA v1.0 中未定义

结论很清晰:Packer 生成 SLSA Provenance v1,自身即可达到构建 L1;当运行在托管 CI 且启用无密钥签名时达到 L2。L3 是构建平台的属性,要求签名密钥对构建步骤不可达。Packer 单独无法赋予 L3,但文档提供的委托签名模式与 L3 平台兼容。

这一点在源码中也能印证:provenance 后处理器输出的是 SLSA v1 谓词(predicate),其谓词类型常量SLSAProvenanceV1PredicateType = "https://slsa.dev/provenance/v1"定义在 internal/provenance/predicate.go,默认构建类型为https://packer.io/buildtypes/hcl2/v1,默认本地 builder ID 为https://packer.io/local-build

工作流一:L2 无密钥签名(github-actions-l2-keyless.yml)

这份工作流对应 SLSA 构建 L2:Packer 以 GitHub Actions 工作流自身的 OIDC 身份(Fulcio)对溯源声明进行无密钥签名,并把签名记录进 Rekor 透明日志,从而产出由托管平台生成的、已签名且可透明审计的溯源

值得注意:文档明确警告,该模式本身不赋予 L3——构建 job 仍可触达(临时的)签名材料;L3 兼容模式请看第二份工作流。

工作流骨架与权限声明

name: build-and-sign-provenance on: push: tags: - "v*" permissions: contents: read # Required so Packer's keyless signing can request an OIDC token from GitHub # and exchange it with Fulcio for a short-lived signing certificate. id-token: write

id-token: write是启用无密钥签名的关键:它允许 Packer 向 GitHub 请求 OIDC token,再与 Fulcio 交换短时签名证书。如果省略该权限,Packer 将无法获取环境 OIDC 身份。

关键环境变量:验证时用于固定身份

env: # Must match the workflow's OIDC identity so verification can pin it. KEYLESS_IDENTITY: "https://github.com/${{ github.repository }}/.github/workflows/build-and-sign-provenance.yml@${{ github.ref }}" KEYLESS_OIDC_ISSUER: "https://token.actions.githubusercontent.com"

KEYLESS_IDENTITY必须与工作流自身的 OIDC 身份精确匹配,验证时才能将签名证书锁定到该身份;KEYLESS_OIDC_ISSUER是 GitHub Actions 的 OIDC 颁发者。

构建与签名步骤

steps: - name: Checkout uses: actions/checkout@v4 - name: Install Packer uses: hashicorp/setup-packer@main with: version: latest - name: Initialize plugins run: packer init . - name: Build and sign run: | packer build \ -var "keyless_identity=${KEYLESS_IDENTITY}" \ -var "keyless_oidc_issuer=${KEYLESS_OIDC_ISSUER}" \ .

工作流注释里特别解释了一个重要的 HCL 限制:Packer 的env()函数只能出现在变量的default中,绝不能内联写在 block 里,因此这里用-var把身份信息传入,而不是在模板中直接调用env()。对应的模板需要声明两个输入变量与一个无密钥 provenance 后处理器:

variable "keyless_identity" { type = string } variable "keyless_oidc_issuer" { type = string } post-processor "provenance" { signing_mode = "keyless" upload_tlog = true keyless_identity = var.keyless_identity keyless_oidc_issuer = var.keyless_oidc_issuer }

Packer 会自动从 GitHub Actions 的ACTIONS_ID_TOKEN_REQUEST_URL/ACTIONS_ID_TOKEN_REQUEST_TOKEN环境变量中拾取环境 OIDC token(前提是授予了上面的id-token: write权限)。这一点在 internal/attestation/sign_keyless.go 的resolveAmbientIDToken中有完整实现:它依次尝试SIGSTORE_ID_TOKENCI_JOB_JWT_V2CI_JOB_JWT,最后通过resolveGitHubActionsIDTokenACTIONS_ID_TOKEN_REQUEST_URL请求 token(audience 缺省为sigstore)。

验证签名声明

- name: Verify attestation run: | for att in *.provenance.json; do packer verify-attestation \ -signing-mode=keyless \ -bundle="${att%.json}.sigstore.json" \ -require-rekor \ -require-timestamp \ -keyless-identity="${KEYLESS_IDENTITY}" \ -keyless-oidc-issuer="${KEYLESS_OIDC_ISSUER}" \ -predicate-type="https://slsa.dev/provenance/v1" \ "${att}" done

这里循环遍历所有*.provenance.json声明文件,配合*.sigstore.json侧车文件(Sigstore bundle)做 Rekor 支撑的透明性校验。各参数含义与 command/verify_attestation.go 中packer verify-attestation的帮助文本一致:

  • -signing-mode=keyless:指定无密钥验证模式(支持keykmskeyless,可自动探测);
  • -bundle=*.sigstore.json:提供 Sigstore bundle,用于 Rekor 或时间戳验证;
  • -require-rekor:要求基于 bundle 完成 Rekor 透明日志验证;
  • -require-timestamp:要求可信观察者时间戳(Rekor 集成时间或 RFC3161 时间戳证据);
  • -keyless-identity/-keyless-oidc-issuer:期望的无密钥签名身份与 OIDC 颁发者;
  • -predicate-type:期望的谓词类型,这里固定为 SLSA Provenance v1。

上传溯源产物

- name: Upload provenance artifacts uses: actions/upload-artifact@v4 with: name: provenance path: | *.provenance.json *.sigstore.json

*.provenance.json是溯源声明本体,*.sigstore.json是签名 bundle 侧车。当upload_tlog = true时,bundle 中会携带 Rekor 透明性证据,供上述-require-rekor校验使用(见 post-processor/provenance/post-processor.go 中侧车路径的生成逻辑:以.json结尾的声明文件,其 bundle 路径为去掉.json后缀后追加.sigstore.json)。

工作流二:L3 兼容的委托签名(github-actions-l3-delegated.yml)

这份工作流对应 SLSA 构建 L3 兼容模式:构建 job 只负责构建产物并发布其摘要;溯源生成与签名被委托给一个隔离的、可复用的工作流(SLSA GitHub generator),在构建步骤无法影响或触达的独立 job 中运行。这种隔离正是 L3 的要求——签名材料对构建不可达。

文档再次强调:Packer 本身不赋予 L3,它只负责产出产物;L3 属性由平台(隔离签名方)提供。

name: build-and-delegate-provenance on: push: tags: - "v*" permissions: contents: read jobs: build: runs-on: ubuntu-latest outputs: digest: ${{ steps.hash.outputs.digest }} steps: - name: Checkout uses: actions/checkout@v4 - name: Install Packer uses: hashicorp/setup-packer@main with: version: latest - name: Initialize plugins run: packer init . # Build only. Do NOT sign here: signing in the build job would defeat the # isolation that the delegated signer provides. - name: Build artifact run: packer build . # Emit a base64-encoded subject digest for the delegated generator. - name: Compute artifact digest id: hash run: | # Adjust the artifact path to match your build output. echo "digest=$(sha256sum output/image.qcow2 | base64 -w0)" >> "${GITHUB_OUTPUT}"

构建 job 中明确不做签名——在构建 job 内签名会破坏委托签名方提供的隔离性。它只计算产物摘要并以 base64 编码输出(注意output/image.qcow2需要替换为你实际的构建产物路径)。

委托 job 则使用 SLSA GitHub generator:

provenance: needs: [build] permissions: actions: read id-token: write contents: write uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 with: base64-subjects: ${{ needs.build.outputs.digest }}

该 job 在构建步骤无法触达的独立 job 中生成并签名 SLSA 溯源——这是 L3 属性的来源。文档注释提醒:实际使用中应将可复用工作流固定到已发布 tag(示例中为@v2.0.0)。

provenance 后处理器源码级解读

两份工作流都依赖provenance后处理器,其实现位于 post-processor/provenance/post-processor.go,配套文档为 post-processor/provenance/README.md。它支持三类能力:

  • 富集了源码控制与 CI 元数据的 SLSA 溯源声明;
  • SBOM 侧车文件与 SBOM 溯源声明;
  • 可配置签名方与验证方的可选 DSSE 签名(默认signing_mode = "none",输出未签名 JSON 声明)。

核心配置参数

源码中Config结构体(post-processor.go)完整定义了以下参数,与工作流直接相关的有:

参数含义默认值
signing_mode签名模式:none(默认,未签名 JSON)、key(本地 PEM 密钥)、kms(KMS/Vault URI)、keyless(Sigstore Fulcio)none
keyless_identity期望的签名身份,如工作流 ref;keyless 模式必填
keyless_oidc_issuer期望的 OIDC 颁发者,如https://token.actions.githubusercontent.com;keyless 模式必填
upload_tlog是否将 keyless 签名上传 Rekor 透明日志并产出携带透明性证据的 Sigstore bundlefalse
fulcio_urlkeyless 签名使用的 Fulcio CA 地址https://fulcio.sigstore.dev
rekor_urlupload_tlog启用时的 Rekor 透明日志地址https://rekor.sigstore.dev
trusted_root_path用于固定 keyless 验证的 Sigstore trusted-root JSON;不设置则拉取公共 Sigstore 根
build_type溯源谓词中记录的 SLSAbuildTypeURIhttps://packer.io/buildtypes/hcl2/v1
template产出产物的 Packer 模板路径,记为外部参数
only_builds产物来源的构建列表,记为外部参数
user_variables额外的用户变量,记为外部参数;敏感变量会被脱敏
source_uri覆盖自动探测的源码仓库 URI自动探测
sbom/sbom_format/sbom_scan_path/sbom_scope/sbom_exclude是否同时生成 SBOM 及相应配置关 /cyclonedx/ 自动 /squashed/ 无

这些默认值在Configure方法中于解码之后统一应用(post-processor.go)。keyless 模式还要求keyless_identitykeyless_oidc_issuer非空、不允许设置verifier覆盖(验证严格绑定身份与颁发者策略),详见signingBackendConfig(post-processor.go)。

输出文件与签名流程

writeAttestation(post-processor.go)展示了完整流程:

  1. 未签名模式(none)直接输出格式化 JSON 声明文件(*.provenance.json);
  2. 签名模式先构造签名后端,对 in-toto 负载进行规范化(MarshalPayload)后签名;
  3. keyless 模式走buildSigstoreBundleForSigner,产出 DSSE envelope 与 Sigstore bundle(*.sigstore.json),upload_tlog=true时 bundle 携带 Rekor 证据;
  4. 签名后立即用验证方做VerifyEnvelope自检,再原子写入文件(atomicWriteFile先写临时文件再 rename,防止并行构建时输出交错或崩溃留下损坏声明)。

DSSE envelope 的结构(payloadType/payload/signatures,其中 keyless 签名在signatures[0].cert携带 Fulcio 证书)定义于 internal/attestation/dsse.go。

谓词内容

SLSA 谓词由 internal/provenance/predicate.go 的BuildSLSAPredicate构造:buildDefinition记录buildType、外部参数(模板、onlyBuilds、用户变量)、内部参数(Packer 版本、构建名、构建器类型)与已解析依赖(源码仓库 URI + 摘要);runDetails记录 builder ID(默认https://packer.io/local-build)与 Packer 版本、调用 ID 及起止时间。源码仓库由 Git 信息或 CI 环境变量自动探测,可用source_uri覆盖(post-processor.go)。

从零接入的完整步骤

将上述参考工作流落到自己的项目,可以按以下步骤操作:

  1. 复制工作流:把 github-actions-l2-keyless.yml(或 github-actions-l3-delegated.yml)复制到你项目的.github/workflows/目录;
  2. 准备模板:在 Packer 模板中声明keyless_identitykeyless_oidc_issuer两个变量,并添加post-processor "provenance"块(L2 模式);L3 模式无需签名后处理器,但需要调整sha256sum中的产物路径;
  3. 打 tag 触发:两个工作流都监听v*格式的 tag 推送;
  4. 检查权限:L2 模式必须保留id-token: write;L3 模式的委托 job 需要actions: readid-token: writecontents: write
  5. 消费产物:从 workflow 的provenanceartifact 中取回*.provenance.json*.sigstore.json,离线可用packer verify-attestation -signing-mode=keyless -bundle=... -require-rekor -require-timestamp -keyless-identity=... -keyless-oidc-issuer=... -predicate-type=https://slsa.dev/provenance/v1 <声明文件>重复验证。

常见问题与注意事项

  • env()的位置限制env()只能写在变量default中,不能内联在 block 里,因此工作流统一用-var传值;
  • L2 不等于 L3:L2 模式下构建 job 仍能触达临时签名材料,只有将签名委托给隔离平台(如 L3 工作流)才满足 L3 要求;
  • 固定可复用工作流版本:L3 委托的slsa-github-generator应固定到已发布 tag,而非长期跟随分支;
  • 身份字符串必须精确匹配keyless_identity与工作流 OIDC 身份不一致会导致验证失败;验证命令中的期望值与签名时的实际值必须一致;
  • 产物路径按需调整:两份工作流中的产物路径(如output/image.qcow2)都需要替换为真实构建输出;
  • 敏感变量脱敏:provenance 谓词中记录的用户变量会依据packer_sensitive_variables自动脱敏为[sensitive value redacted](见 post-processor.go),避免密钥泄露进溯源声明。

这两份参考工作流为 Packer 用户提供了一条从「生成溯源」到「无密钥签名、透明审计、平台级隔离」的渐进路径:先用 L2 模式快速获得已签名的可验证溯源,再在需要更高供应链保证时切换到 L3 委托模式,把签名材料彻底隔离在加固平台之内。

【免费下载链接】packerPacker is a tool for creating identical machine images for multiple platforms from a single source configuration.项目地址: https://gitcode.com/gh_mirrors/pa/packer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询