已签名的 Provenance

本指南帮助新用户快速设置 Tekton Chains,通过为 Tekton PipelineRuns 生成并验证 SLSA Provenance 来保护其 CI/CD 流水线。

简介

使用场景

Tekton Chains 可通过自动为构建产物生成 SLSA Provenance,帮助你保护软件供应链。本指南演示如何设置 Tekton Chains、生成签名密钥、运行示例流水线,以及验证其 SLSA Provenance。

预计阅读时间

15-20 分钟

注意事项

  • 在使用 Alauda Devops Pipelines Operator 时,Tekton Chains 默认安装在 tekton-pipelines 命名空间中
  • 签名密钥应妥善管理;在生产环境中,建议使用密钥管理系统(KMS)
  • 本指南通过在流水线中内联生成 Containerfile 来简化工作流程
  • 在生产环境中,应使用规范的源代码管理和版本控制

前提条件

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

流程概览

步骤操作描述
1生成签名密钥使用 cosign 创建用于签名产物的密钥对
2设置身份验证配置用于镜像推送的 registry 凭据
3配置 Tekton Chains设置 Chains 使用 OCI 存储并配置签名
4创建示例流水线创建包含必要任务和 workspace 的流水线定义
5运行示例流水线创建并运行配置正确的 PipelineRun
6等待签名等待 Chains 对 PipelineRun 完成签名
7获取镜像信息从 PipelineRun 中提取镜像 URI 和 digest
8验证签名验证镜像签名和 SLSA provenance 证明

分步说明

步骤 1:生成签名密钥

TIP

更多详细信息,请参考 Signing Key Configuration

Tekton Chains 使用加密密钥对产物进行签名。默认情况下,它会在 Chains 命名空间中查找名为 signing-secrets 的 secret。

  1. 如果尚未安装,请先安装 cosign

  2. 生成密钥对并将其存储为 Kubernetes secret:

    $ COSIGN_PASSWORD={password} cosign generate-key-pair k8s://tekton-pipelines/signing-secrets
    
    Successfully created secret signing-secrets in namespace tekton-pipelines
    Public key written to cosign.pub
    TIP

    此密码将存储在 tekton-pipelines 命名空间中名为 signing-secrets 的 Kubernetes secret 中。

  3. 验证 secret 是否已创建:

    $ kubectl get secret signing-secrets -n tekton-pipelines
    
    NAME              TYPE     DATA   AGE
    signing-secrets   Opaque   3      3m

步骤 2:设置身份验证

TIP

更多详细信息,请参考 Authentication for Chains

配置用于镜像推送的 registry 凭据:

  1. 创建包含凭据的 secret:

    $ kubectl create secret docker-registry registry-credentials \
      --docker-server=<gcr.io> \
      --docker-username=<username> \
      --docker-email=<email> \
      --docker-password=<password> \
      -n $NAMESPACE
  2. 设置 config.json 键:

    $ DOCKER_CONFIG=$(kubectl get secret -n $NAMESPACE $REGISTRY_CREDENTIALS -o jsonpath='{.data.\.dockerconfigjson}')
    $ kubectl patch secret -n $NAMESPACE $REGISTRY_CREDENTIALS -p "{\"data\":{\"config.json\":\"$DOCKER_CONFIG\"}}"
  3. 修补 service account 以使用该 secret:

    $ kubectl patch serviceaccount $SERVICE_ACCOUNT_NAME \
      -p "{\"secrets\": [{\"name\": \"registry-credentials\"}]}" -n $NAMESPACE

步骤 3:配置 Tekton Chains

TIP

更多详细信息,请参考 Chains Configuration

配置 Tekton Chains 以 OCI 格式存储产物:

$ kubectl patch tektonconfigs.operator.tekton.dev config --type=merge -p='{
  "spec": {
    "chain": {
      "artifacts.oci.format": "simplesigning",
      "artifacts.oci.storage": "oci",
      "artifacts.pipelinerun.format": "in-toto",
      "artifacts.pipelinerun.storage": "oci",
      "artifacts.taskrun.format": "in-toto",
      "artifacts.taskrun.storage": "oci"
    }
  }
}'

步骤 4:创建示例流水线

这是一个 Pipeline 资源,用于构建镜像并生成 SLSA Provenance 证明。

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: chains-demo-1
spec:
  params:
    - default: |-
        echo "Generate a Containerfile for building an image."

        cat << 'EOF' > Containerfile
        FROM ubuntu:latest
        ENV TIME=1
        EOF

        echo -e "\nContainerfile contents:"
        echo "-------------------"
        cat Containerfile
        echo "-------------------"
        echo -e "\nContainerfile generated successfully!"
      description: A script to generate a Containerfile for building an image.
      name: generate-containerfile
      type: string
    - default: <registry>/test/chains/demo-1:latest
      description: The target image address built
      name: image
      type: string
  tasks:
    - name: generate-containerfile
      params:
        - name: script
          value: $(params.generate-containerfile)
      taskRef:
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: run-script
          - name: version
            value: "0.1"
        resolver: hub
      timeout: 30m0s
      workspaces:
        - name: source
          workspace: source
    - name: build-image
      params:
        - name: IMAGES
          value:
            - $(params.image)
        - name: TLS_VERIFY
          value: "false"
      runAfter:
        - generate-containerfile
      taskRef:
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: buildah
          - name: version
            value: "0.9"
        resolver: hub
      timeout: 30m0s
      workspaces:
        - name: source
          workspace: source
        - name: registryconfig
          workspace: registryconfig
  results:
    - description: first image artifact output
      name: first_image_ARTIFACT_OUTPUTS
      type: object
      value:
        digest: $(tasks.build-image.results.IMAGE_DIGEST)
        uri: $(tasks.build-image.results.IMAGE_URL)
  workspaces:
    - name: source
      description: The workspace for source code.
    - name: registryconfig
      description: The workspace for distribution registry configuration.
TIP

在生产环境中,你应该:

  1. 使用 git-clone 任务从你的仓库中获取源代码
  2. 使用源代码中已有的 Containerfile 构建镜像
  3. 此方法可确保正确的版本控制,并保持代码与流水线配置之间的分离
YAML 字段说明
  • params:流水线参数。
    • generate-containerfile:用于生成 Containerfile 以构建镜像的脚本。
    • image:要构建的目标镜像地址。
  • tasks:流水线任务。
    • generate-containerfile:用于生成 Containerfile 以构建镜像的任务。
    • build-image:用于构建并将镜像推送到 registry 的任务。
      • params.TLS_VERIFY:是否验证 registry 的 TLS 证书。
  • results:流水线结果。
    • first_image_ARTIFACT_OUTPUTS:第一个镜像产物输出的结果。
      • digest:镜像的 digest。
      • uri:镜像的 URI。
    • 此格式与 Tekton Chains 兼容,更多详细信息请参见上文中的 Tekton Chains Type Hinting
  • workspaces:流水线 workspace。
    • source:源代码 workspace。
    • registryconfig:分发 registry 配置 workspace。

需要调整的配置

  • params
    • generate-containerfile
      • default:调整 from 镜像地址。
    • image
      • default:要构建的目标镜像地址。

将其保存为名为 chains.demo-1.pipeline.yaml 的 yaml 文件,并使用以下命令应用:

$ export NAMESPACE=<default>
$ kubectl apply -n $NAMESPACE -f chains.demo-1.pipeline.yaml

步骤 5:运行示例流水线

这是一个 PipelineRun 资源,用于运行该流水线。

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: chains-demo-1-
spec:
  pipelineRef:
    name: chains-demo-1
  taskRunTemplate:
    serviceAccountName: <default>
  workspaces:
    - name: registryconfig
      secret:
        secretName: <registry-credentials>
    - name: source
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
          storageClassName: <nfs>
YAML 字段说明
  • pipelineRef:要运行的流水线。
    • name:流水线名称。
  • taskRunTemplate:TaskRun 模板。
    • serviceAccountName:流水线使用的 service account。
  • workspaces:流水线 workspace。
    • registryconfig:分发 registry 配置 workspace。
    • source:源代码 workspace。

需要调整的配置

  • taskRunTemplate
  • workspaces
    • registryconfig
    • source
      • volumeClaimTemplate.spec.storageClassName:volume claim template 的 storage class 名称。

将其保存为名为 chains.demo-1.pipelinerun.yaml 的 yaml 文件,并使用以下命令应用:

$ export NAMESPACE=<default>

# 在该命名空间中创建 pipeline run 资源
$ kubectl create -n $NAMESPACE -f chains.demo-1.pipelinerun.yaml

等待 PipelineRun 完成。

$ kubectl get pipelinerun -n $NAMESPACE -w

chains-demo-1-<xxxxx>   True        Succeeded   2m         2m

步骤 6:等待 PipelineRun 完成签名

等待 PipelineRun 具有 chains.tekton.dev/signed: "true" 注解。

$ export NAMESPACE=<default>
$ export PIPELINERUN_NAME=<chains-demo-1-xxxxx>

$ kubectl get pipelinerun -n $NAMESPACE $PIPELINERUN_NAME -o yaml | grep "chains.tekton.dev/signed"

    chains.tekton.dev/signed: "true"

当 PipelineRun 具有 chains.tekton.dev/signed: "true" 注解时,表示镜像已经完成签名。

步骤 7:从 PipelineRun 获取镜像

# 获取镜像 URI
$ export IMAGE_URI=$(kubectl get pipelinerun -n $NAMESPACE $PIPELINERUN_NAME -o jsonpath='{.status.results[?(@.name=="first_image_ARTIFACT_OUTPUTS")].value.uri}')

# 获取镜像 digest
$ export IMAGE_DIGEST=$(kubectl get pipelinerun -n $NAMESPACE $PIPELINERUN_NAME -o jsonpath='{.status.results[?(@.name=="first_image_ARTIFACT_OUTPUTS")].value.digest}')

# 组合镜像 URI 和 digest,形成完整的镜像引用
$ export IMAGE=$IMAGE_URI@$IMAGE_DIGEST

# 打印镜像引用
$ echo $IMAGE

<registry>/test/chains/demo-1:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046

该镜像将用于验证签名。

步骤 8:验证镜像和证明

  1. 获取签名公钥

    如果你没有权限,可以请管理员获取公钥。

    $ export NAMESPACE=<tekton-pipelines>
    $ kubectl get secret -n $NAMESPACE signing-secrets -o jsonpath='{.data.cosign\.pub}' | base64 -d > cosign.pub
  2. 验证签名

    $ cosign verify --key cosign.pub ${IMAGE}

    如果成功,你将看到以下输出:

    [{"critical":{"identity":{"docker-reference":"<registry>/test/chains/demo-1"},"image":{"docker-manifest-digest":"sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046"},"type":"cosign container image signature"},"optional":null}]
  3. 验证 SLSA provenance 证明

    $ cosign verify-attestation --key cosign.pub --type slsaprovenance ${IMAGE}

    如果成功,你将看到以下输出:

    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":[{"keyid":"SHA256:B8CzieJ9/kKhU9hiIPXY+dvvz9DLVOQ3XokyYLdladc","sig":"MEQCIEeS/gGpMV4Y22pYJw3hTyCg92KXACAYvnDwm3fzPxyLAiA3cDP6zjCfaVrf35wED1ylSUdsEJzwlXDCvj2i2A6BYg=="}]}
  4. 使用 jq 提取 payload:

    $ cosign verify-attestation --key cosign.pub --type slsaprovenance ${IMAGE} | jq '.payload | @base64d' | jq -r '.' | jq '.'
    证明 Payload
     {
       "_type": "https://in-toto.io/Statement/v0.1",
       "subject": [
         {
           "name": "<registry>/test/chains/demo-1",
           "digest": {
             "sha256": "213a270c14f88abdd283f4ceaf8a8a73f5f8b5c2303609295680a6e751ef6f0f"
           }
         }
       ],
       "predicateType": "https://slsa.dev/provenance/v0.2",
       "predicate": {
         "buildConfig": {
           "steps": [
             {
               "annotations": null,
               "arguments": [
                 "--images",
                 "<registry>/test/chains/demo-1:latest",
                 "--build-args",
                 ""
               ],
               "entryPoint": "",
               "environment": {
                 "container": "build-and-push",
                 "image": "oci://<registry>/devops/tektoncd/hub/buildah@sha256:8d5ea9ecd9b531e798fecd87ca3b64ee1c95e4f2621d09e893c58ed593bfd4c4"
               }
             }
           ]
         },
         "buildType": "tekton.dev/v1beta1/TaskRun",
         "builder": {
           "id": "https://alauda.io/builders/tekton/v1"
         },
         "invocation": {
           "configSource": {
             "digest": {
               "sha256": "3225653d04c223be85d173747372290058a738427768c5668ddc784bf24de976"
             },
             "uri": "http://tekton-hub-api.tekton-pipelines:8000/v1/resource/catalog/task/buildah/0.9/yaml"
           },
           "environment": {
             "annotations": {
               "cpaas.io/creator": "admin@cpaas.io",
               "cpaas.io/operator": "admin@cpaas.io",
               "cpaas.io/updated-at": "2025-06-16T06:04:04Z",
               "pipeline.tekton.dev/affinity-assistant": "affinity-assistant-691f2fff6f",
               "pipeline.tekton.dev/release": "002f74eea37b0f5dbcfed39c13d7cd762c8fcdc4",
               "tekton.dev/categories": "Image Build",
               "tekton.dev/displayName": "Buildah",
               "tekton.dev/icon": "",
               "tekton.dev/pipelines.minVersion": "0.56.0",
               "tekton.dev/platforms": "linux/amd64,linux/arm64",
               "tekton.dev/tags": "image-build"
             },
             "labels": {
               "app.kubernetes.io/managed-by": "tekton-pipelines",
               "app.kubernetes.io/version": "0.9",
               "tekton.dev/memberOf": "tasks",
               "tekton.dev/pipeline": "chains-demo-1",
               "tekton.dev/pipelineRun": "chains-demo-1-zp9tk",
               "tekton.dev/pipelineRunUID": "ad8234b8-19a6-4c5c-b2fd-5673444aeda8",
               "tekton.dev/pipelineTask": "build-image",
               "tekton.dev/task": "buildah"
             }
           },
           "parameters": {
             "BUILDER_IMAGE": "<registry>/devops/tektoncd/hub/buildah:v1.33.12-v4.0.0-beta.240.g2b61d46",
             "BUILD_ARGS": [
               ""
             ],
             "BUILD_EXTRA_ARGS": "",
             "CONTEXT": ".",
             "CONTAINERFILE": "./Containerfile",
             "FORMAT": "oci",
             "IMAGES": [
               "<registry>/test/chains/demo-1:latest"
             ],
             "PUSH_EXTRA_ARGS": "",
             "SKIP_PUSH": "false",
             "STORAGE_DRIVER": "vfs",
             "TLS_VERIFY": "false",
             "VERBOSE": "false"
           }
         },
         "materials": [
           {
             "digest": {
               "sha256": "8d5ea9ecd9b531e798fecd87ca3b64ee1c95e4f2621d09e893c58ed593bfd4c4"
             },
             "uri": "oci://<registry>/devops/tektoncd/hub/buildah"
           }
         ],
         "metadata": {
           "buildFinishedOn": "2025-06-16T06:04:41Z",
           "buildStartedOn": "2025-06-16T06:04:15Z",
           "completeness": {
             "environment": false,
             "materials": false,
             "parameters": false
           },
           "reproducible": false
         }
       }
     }

预期结果

完成本指南后:

  • 你已经拥有一个可正常工作的 Tekton Chains 环境,并配置了签名密钥
  • PipelineRun 在完成后会自动签名,并且镜像具有 SLSA Provenance 证明
  • 你可以验证签名和证明,以确保构建的完整性

本指南为在 CI/CD 流水线中实施供应链安全提供了基础。在生产环境中,你应该:

  1. 使用规范的源代码管理和版本控制
  2. 使用云 KMS 实现安全的密钥管理
  3. 在部署流程中设置自动化验证
  4. 配置适当的访问控制和安全策略
  5. 定期轮换签名密钥并更新安全配置

参考资料