构建系统 Provenance 验证

SLSA provenance 中,有一个 builder.id 字段,用于标识镜像的构建环境。

在本文档中,我们将使用这个 builder.id 字段来验证镜像。

TIP

由于 Tekton Chains 已经在准备阶段同时处理了镜像签名和 SLSA provenance 生成,因此我们可以直接复用 Quick Start: Signed Provenance 中的流程和镜像。

本文档将重点介绍如何验证 SLSA provenance。

功能概述

此方法使用 Chains 为已构建的镜像自动生成 SLSA Provenance,然后使用 Kyverno 验证该 provenance:

  1. 配置 Tekton Chains,为已构建的镜像自动生成 SLSA Provenance。
  2. 使用 buildah Tekton Task 构建镜像。
  3. (可选)使用 cosign cli 验证 attestation。
  4. 配置 Kyverno 规则以验证 attestation。
  5. 使用该镜像创建 Pod,以验证 attestation。

使用场景

以下场景需要参考本文档中的指导:

  • 使用 SLSA provenance 验证容器镜像的构建环境
  • 使用 CUE 或 Rego 策略实现构建系统 provenance 验证
  • 强制执行安全策略,仅允许在特定构建环境中构建的镜像
  • 在 CI/CD 流水线中设置自动化构建系统 provenance 验证
  • 确保生产环境中构建系统 provenance 的完整性和真实性

前提条件

  • 已安装 Tekton Pipelines、Tekton Chains 和 Kyverno 的 Kubernetes 集群
  • 已启用镜像推送的镜像仓库
  • 已安装并配置好用于访问集群的 kubectl CLI
  • 已安装 cosign CLI 工具
  • 已安装 jq CLI 工具

流程概览

步骤操作描述
1生成签名密钥使用 cosign 创建用于签名工件的密钥对
2设置认证配置镜像推送所需的镜像仓库凭证
3配置 Tekton Chains设置 Chains 使用 OCI 存储并配置签名
4创建示例流水线创建包含必要任务和 workspace 的流水线定义
5运行示例流水线使用正确配置创建并运行一个 PipelineRun
6等待签名等待 Chains 对 PipelineRun 进行签名
7获取镜像信息从 PipelineRun 中提取镜像 URI 和 digest
8(可选)使用 cosign 验证签名使用 cosign CLI 验证镜像 attestation
9使用 Kyverno 验证签名使用 Kyverno 策略配置并验证镜像 attestation
10清理资源删除测试 Pod 和策略

分步说明

步骤 1-7:(可选)基础设置

NOTE

如果你更改了 builder.id 字段,需要重新运行流水线来生成镜像。 因为旧镜像没有使用新的 builder.id 进行签名,所以会被策略阻止。

否则,你可以跳过此步骤,使用旧镜像来验证策略。

这些步骤与 Quick Start: Signed Provenance 指南完全相同。请按照该指南完成以下内容:

步骤 8:(可选)使用 cosign 验证 builder 信息

TIP

此步骤为可选步骤,仅当你需要使用 cosign 验证镜像构建器的真实性时执行。

如果你想了解如何使用 CUERego 验证 builder 信息,可以继续阅读以下内容。

根据 获取签名公钥 小节获取签名公钥。

Cosign 提供两种方式来 验证 attestation

下面将展示这两种方式的验证方法。

方式 1:使用 CUE 验证

生成用于验证 builder 信息的 CUE 文件。

// The predicate must match the following constraints.
predicate: {
    builder: {
        id: "https://alauda.io/builders/tekton/v1"
    }
}

将 CUE 文件保存为 builder.cue

使用 cosign 验证 builder 信息。

# Disable tlog upload and enable private infrastructure
$ export COSIGN_TLOG_UPLOAD=false
$ export COSIGN_PRIVATE_INFRASTRUCTURE=true

$ export IMAGE=<<registry>/test/chains/demo-1:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046>

$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.cue $IMAGE

如果收到如下输出,表示 builder 信息验证成功。

will be validating against CUE policies: [builder.cue]
will be validating against CUE policies: [builder.cue]

Verification for <registry>/test/chains/demo-1:latest@sha256:8ac1af8dd89652bf32abbbd0c5f667ae9fe6d92c91972617e70b5398303c8e27 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
{"payloadType":"application/vnd.in-toto+json","payload":"","signatures":[]}

builder.cue 文件中的 builder id 修改为另一个值 https://alauda.io/builders/tekton/v2,然后再次验证。

$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.cue $IMAGE

如果收到如下输出,表示 builder 信息验证失败。

will be validating against CUE policies: [builder.cue]
will be validating against CUE policies: [builder.cue]
There are 2 number of errors occurred during the validation:

- predicate.builder.id: conflicting values "https://alauda.io/builders/tekton/v1" and "https://alauda.io/builders/tekton/v2"
- predicate.builder.id: conflicting values "https://alauda.io/builders/tekton/v1" and "https://alauda.io/builders/tekton/v2"
Error: 2 validation errors occurred
error during command execution: 2 validation errors occurred

方式 2:使用 Rego 验证

生成用于验证 builder 信息的 Rego 文件。

package signature

default allow = false

# Define the allowed builder.id
allowed_builder_id = "https://alauda.io/builders/tekton/v1"

# Verify the builder.id
allow {
    # Check if the builder.id in the predicate is equal to the allowed value
    input.predicate.builder.id == allowed_builder_id
}

# Return error message when not match
deny[msg] {
    input.predicate.builder.id != allowed_builder_id
    msg := sprintf("unexpected builder.id: %v, expected: %v", [input.predicate.builder.id, allowed_builder_id])
}

将 Rego 文件保存为 builder.rego

使用 cosign 验证 builder 信息。

# Disable tlog upload and enable private infrastructure
$ export COSIGN_TLOG_UPLOAD=false
$ export COSIGN_PRIVATE_INFRASTRUCTURE=true

$ export IMAGE=<<registry>/test/chains/demo-1:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046>

$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.rego $IMAGE

如果收到如下输出,表示 builder 信息验证成功。

will be validating against Rego policies: [builder.rego]
will be validating against Rego policies: [builder.rego]

Verification for <registry>/test/chains/demo-1:latest --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
{"payloadType":"application/vnd.in-toto+json","payload":"","signatures":[]}

builder.rego 文件中的 builder id 修改为另一个值 https://alauda.io/builders/tekton/v2,然后再次验证。

$ cosign verify-attestation --key cosign.pub --type slsaprovenance --policy builder.rego $IMAGE

如果收到如下输出,表示 builder 信息验证失败。

will be validating against Rego policies: [builder.rego]
will be validating against Rego policies: [builder.rego]
There are 2 number of errors occurred during the validation:

- expression value, false, is not true
- expression value, false, is not true
Error: 2 validation errors occurred
error during command execution: 2 validation errors occurred

步骤 9:使用 Kyverno 验证签名

TIP

此步骤需要集群管理员权限。

provenance 的内容大致如下,我们将使用 builder.id 字段来验证构建环境。

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://slsa.dev/provenance/v0.2",
  "predicate": {
    "buildType": "tekton.dev/v1beta1/TaskRun",
    "builder": {
      "id": "https://alauda.io/builders/tekton/v1"
    },
    "materials": [
      {
        "digest": {
          "sha256": "8d5ea9ecd9b531e798fecd87ca3b64ee1c95e4f2621d09e893c58ed593bfd4c4"
        },
        "uri": "oci://<registry>/devops/tektoncd/hub/buildah"
      }
    ],
    "metadata": {
      "buildFinishedOn": "2025-06-06T10:21:27Z",
      "buildStartedOn": "2025-06-06T10:20:55Z"
    }
  }
}

步骤 9.1:创建一个 Kyverno 策略,仅允许在特定构建环境中构建的镜像被部署

TIP

关于 Kyverno ClusterPolicy 的更多细节,请参考 Kyverno ClusterPolicy

策略如下:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-tekton-built-images
spec:
  webhookConfiguration:
    failurePolicy: Fail
    timeoutSeconds: 30
  background: false
  rules:
    - name: check-image
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - policy
      verifyImages:
        - imageReferences:
            - "*"
            # - "<registry>/test/*"
          skipImageReferences:
            - "ghcr.io/trusted/*"
          failureAction: Enforce
          verifyDigest: false
          required: false
          useCache: false
          imageRegistryCredentials:
            allowInsecureRegistry: true
            secrets:
              # The credential needs to exist in the namespace where kyverno is deployed
              - registry-credentials

          attestations:
            - type: https://slsa.dev/provenance/v0.2
              attestors:
                - entries:
                    - keys:
                        publicKeys: |- # <- The public key of the signer
                          -----BEGIN PUBLIC KEY-----
                          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFZNGfYwn7+b4uSdEYLKjxWi3xtP3
                          UkR8hQvGrG25r0Ikoq0hI3/tr0m7ecvfM75TKh5jGAlLKSZUJpmCGaTToQ==
                          -----END PUBLIC KEY-----

                        ctlog:
                          ignoreSCT: true

                        rekor:
                          ignoreTlog: true
              conditions:
                - all:
                    - key: "{{ builder.id }}"
                      operator: Equals
                      value: "https://alauda.io/builders/tekton/v1"
                      message: "The builder.id must be equal to https://alauda.io/builders/tekton/v1, not {{ builder.id }}"
YAML 字段说明
  • 该策略与 镜像签名验证 中的策略基本一致。下面仅介绍差异部分。
  • spec.rules[0].verifyImages[].attestations[0].conditions
    • type:slsa provenance 类型为 https://slsa.dev/provenance/v0.2https://slsa.dev/provenance/v1
    • attestors:同上。
    • conditions:需要验证的条件。
      • all:必须满足所有条件。
        • key: "{{ builder.id }}":检查 attestation 中的 builder.id 字段是否等于 https://alauda.io/builders/tekton/v1

需要调整的配置

  • spec.rules[].attestors[].entries[].keys.publicKeys:签名者的公钥。
    • 该公钥与 signing-secrets secret 中的公钥 cosign.pub 相同。
    • 可以通过 获取签名公钥 小节获取该公钥。

将策略保存为名为 kyverno.verify-tekton-built-images.yaml 的 yaml 文件,并执行:

$ kubectl apply -f kyverno.verify-tekton-built-images.yaml

clusterpolicy.kyverno.io/verify-tekton-built-images configured

步骤 9.2:验证策略

在定义了该策略的 policy 命名空间中,创建一个 Pod 来验证该策略。

使用已构建的镜像创建 Pod。

$ export NAMESPACE=<policy>
$ export IMAGE=<<registry>/test/chains/demo-1:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046>

$ kubectl run -n $NAMESPACE built --image=${IMAGE} -- sleep 3600

pod/built created

Pod 会成功创建。

$ kubectl get pod -n $NAMESPACE built

NAME      READY   STATUS    RESTARTS   AGE
built   1/1     Running   0          10s

ClusterPolicy 中的 builder id 修改为另一个值 https://alauda.io/builders/tekton/v2,然后再次验证。

conditions:
  - all:
      - key: "{{ builder.id }}"
        operator: Equals
        value: "https://alauda.io/builders/tekton/v2"
        message: "The builder.id must be equal to https://alauda.io/builders/tekton/v2, not {{ builder.id }}"
$ kubectl run -n $NAMESPACE unbuilt --image=${IMAGE} -- sleep 3600

如果收到如下输出,表示 Pod 被策略阻止。

Error from server: admission webhook "mutate.kyverno.svc-fail" denied the request:

resource Pod/policy/unbuilt was blocked due to the following policies

verify-tekton-built-images:
  check-image: 'image attestations verification failed, verifiedCount: 0, requiredCount:
    1, error: .attestations[0].attestors[0].entries[0].keys: attestation checks failed
    for <registry>/test/chains/demo-1@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046
    and predicate https://slsa.dev/provenance/v0.2: The builder.id must be equal to
    https://alauda.io/builders/tekton/v2, not https://alauda.io/builders/tekton/v1'

步骤 10:清理资源

删除前面步骤中创建的 Pod。

$ export NAMESPACE=<policy>
$ kubectl delete pod -n $NAMESPACE built

删除策略。

$ kubectl delete clusterpolicy verify-tekton-built-images

预期结果

完成本指南后:

  • 你已经拥有一个可正常工作的 Tekton Chains 配置,用于生成 SLSA provenance
  • 你的容器镜像会在构建过程中自动附带构建系统 provenance 签名
  • 你可以使用 CUE 或 Rego 策略验证镜像的构建环境
  • 只有在指定构建环境中构建的镜像才能在指定命名空间中部署
  • 在未授权构建环境中构建的镜像会被 Kyverno 策略自动阻止
  • 你已经为容器镜像实现了基础的构建系统 provenance 验证控制

本指南为在 CI/CD 流水线中实现构建系统 provenance 验证奠定了基础。在生产环境中,你应当:

  1. 配置适当的命名空间隔离和访问控制
  2. 为签名密钥实现安全的密钥管理
  3. 为策略违规配置监控和告警
  4. 定期轮换签名密钥并更新安全策略
  5. 考虑实现额外的安全控制,例如漏洞扫描

参考资料