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 与之兼容 | 委托签名模式 |
| L4 | — | SLSA 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: writeid-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_TOKEN、CI_JOB_JWT_V2、CI_JOB_JWT,最后通过resolveGitHubActionsIDToken从ACTIONS_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:指定无密钥验证模式(支持key、kms、keyless,可自动探测);-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 bundle | false |
fulcio_url | keyless 签名使用的 Fulcio CA 地址 | https://fulcio.sigstore.dev |
rekor_url | upload_tlog启用时的 Rekor 透明日志地址 | https://rekor.sigstore.dev |
trusted_root_path | 用于固定 keyless 验证的 Sigstore trusted-root JSON;不设置则拉取公共 Sigstore 根 | — |
build_type | 溯源谓词中记录的 SLSAbuildTypeURI | https://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_identity与keyless_oidc_issuer非空、不允许设置verifier覆盖(验证严格绑定身份与颁发者策略),详见signingBackendConfig(post-processor.go)。
输出文件与签名流程
writeAttestation(post-processor.go)展示了完整流程:
- 未签名模式(
none)直接输出格式化 JSON 声明文件(*.provenance.json); - 签名模式先构造签名后端,对 in-toto 负载进行规范化(
MarshalPayload)后签名; - keyless 模式走
buildSigstoreBundleForSigner,产出 DSSE envelope 与 Sigstore bundle(*.sigstore.json),upload_tlog=true时 bundle 携带 Rekor 证据; - 签名后立即用验证方做
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)。
从零接入的完整步骤
将上述参考工作流落到自己的项目,可以按以下步骤操作:
- 复制工作流:把 github-actions-l2-keyless.yml(或 github-actions-l3-delegated.yml)复制到你项目的
.github/workflows/目录; - 准备模板:在 Packer 模板中声明
keyless_identity、keyless_oidc_issuer两个变量,并添加post-processor "provenance"块(L2 模式);L3 模式无需签名后处理器,但需要调整sha256sum中的产物路径; - 打 tag 触发:两个工作流都监听
v*格式的 tag 推送; - 检查权限:L2 模式必须保留
id-token: write;L3 模式的委托 job 需要actions: read、id-token: write、contents: write; - 消费产物:从 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),仅供参考