Source Code Repository Verification

在 Tekton Chains 中,可以收集来自 PipelineRun 的特定输入和输出,并将其记录在 SLSA Provenance 中。

TIP

更多详情请参见上文中的 Tekton Chains 类型提示

我们可以利用此功能将代码仓库信息包含在 SLSA Provenance 信息中,随后在 kyverno 中验证代码仓库。

目录

功能概述

该方法使用 Chains 自动为构建的镜像生成 SLSA Provenance,然后使用 Kyverno 验证该溯源信息:

  1. 配置 Tekton Chains 自动为构建的镜像生成 SLSA Provenance。
  2. 使用 git Tekton Task 获取源代码仓库。
  3. 使用 buildah Tekton Task 构建镜像。
  4. 在 Pipeline 的结果中声明 gitbuildah 的结果信息,方便记录镜像的源代码仓库和提交信息。
  5. 配置 Kyverno 规则以验证源代码仓库。
  6. 使用该镜像创建 Pod 以验证源代码仓库。

使用场景

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

  • 在 Kubernetes 集群中使用 Kyverno 实现源代码仓库验证
  • 强制安全策略,仅允许从特定源代码仓库构建的镜像部署
  • 在 CI/CD 流水线中设置自动化的源代码仓库验证
  • 确保生产环境中镜像的溯源和源代码真实性
  • 通过验证容器镜像的源代码来源,实现供应链安全控制

前提条件

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

流程概览

步骤操作说明
1生成签名密钥使用 cosign 创建用于签名工件的密钥对
2设置认证配置镜像推送的仓库凭证
3配置 Tekton Chains设置 Chains 使用 OCI 存储并配置签名,禁用 TaskRun 的 SLSA Provenance
4创建示例流水线创建包含 git clone 任务和 buildah 任务的流水线定义
5运行示例流水线创建并运行配置正确的 PipelineRun
6等待签名等待 PipelineRun 被 Chains 签名
7获取镜像信息从 PipelineRun 中提取镜像 URI 和摘要
8(可选)获取 SLSA Provenance获取并验证 SLSA Provenance 证明
9使用 Kyverno 验证创建并应用 Kyverno 策略验证镜像的源代码仓库
10清理删除测试资源和策略

逐步操作指南

步骤 1-3:基础配置

这些步骤与快速开始:签名溯源指南相同。请按照该指南完成:

  • 步骤 1:生成签名密钥

  • 步骤 2:设置认证

  • 步骤 3:配置 Tekton Chains

    为避免 Tekton Chains 同时为 TaskRun 和 PipelineRun 生成 SLSA Provenance,影响后续 kyverno 的验证,我们先禁用 TaskRun 的 SLSA Provenance。

    TIP

    此过程需要平台管理员权限进行配置。

    $ kubectl patch tektonconfigs.operator.tekton.dev config --type=merge -p='{
      "spec": {
        "chain": {
          "artifacts.taskrun.storage": ""
        }
      }
    }'

步骤 4:创建示例流水线

在之前的镜像构建流水线中,添加一个 git clone 任务,并将 git 任务的输出保存到 PipelineRunresults 中。

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: chains-demo-3
spec:
  params:
    - default: |-
        echo "Simulate cloning the code and write the repository URL and commit message into the results."

        # This commit sha must be a valid commit sha [0-9a-f]{40}.
        cat << 'EOF' > $(results.array-result.path)
        [
          "https://github.com/tektoncd/pipeline",
          "cccccaaaa0000000000000000000000000000000"
        ]
        EOF

        echo -e "\nResults:"
        echo "-------------------"
        cat $(results.array-result.path)
        echo "-------------------"
        echo -e "\nClone successfully!"
      description: A script to simulate cloning the code and write the repository URL and commit message into the results.
      name: generate-git-clone-results
      type: string
    - default: |-
        echo "Generate a Dockerfile for building an image."

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

        echo -e "\nDockerfile contents:"
        echo "-------------------"
        cat Dockerfile
        echo "-------------------"
        echo -e "\nDockerfile generated successfully!"
      description: A script to generate a Dockerfile for building an image.
      name: generate-dockerfile
      type: string
    - default: <registry>/test/chains/demo-3:latest
      description: The target image address built
      name: image
      type: string
  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)
    - description: first repo artifact input
      name: source_repo_ARTIFACT_INPUTS
      type: object
      value:
        digest: sha1:$(tasks.git-clone.results.array-result[1])
        uri: $(tasks.git-clone.results.array-result[0])
  tasks:
    - name: git-clone
      params:
        - name: script
          value: $(params.generate-git-clone-results)
      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: generate-dockerfile
      params:
        - name: script
          value: $(params.generate-dockerfile)
      runAfter:
        - git-clone
      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-dockerfile
      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: dockerconfig
          workspace: dockerconfig
  workspaces:
    - name: source
      description: The workspace for source code.
    - name: dockerconfig
      description: The workspace for Docker configuration.
TIP

本教程通过在流水线中内联生成 Dockerfilegit-clone 任务输出,演示了简化的工作流。 在生产环境中,通常应:

  1. 使用 git-clone 任务从仓库拉取源代码
  2. 使用源代码中的 Dockerfile 构建镜像
  3. 该方法确保了版本控制的正确性,并保持代码与流水线配置的分离
YAML 字段说明
  • 大部分字段与步骤 4:创建示例流水线相同,以下仅介绍差异。
  • params
    • generate-git-clone-results:模拟克隆代码并将仓库 URL 和提交信息写入结果的脚本。
  • results
    • source_repo_ARTIFACT_INPUTS:源代码仓库 URL 和提交信息。
      • digest:源代码仓库的提交 sha。
    • 此格式符合 Tekton Chains 规范,详情请参见上文的 Tekton Chains 类型提示

需调整配置

  • params:
    • generate-dockerfile
      • default:调整基础镜像地址。
    • image:
      • default:构建的目标镜像地址。

保存为 chains.demo-3.pipeline.yaml 文件并应用:

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

步骤 5:运行示例流水线

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

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: chains-demo-3-
spec:
  pipelineRef:
    name: chains-demo-3
  taskRunTemplate:
    serviceAccountName: <default>
  workspaces:
    - name: dockerconfig
      secret:
        secretName: <registry-credentials>
    - name: source
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
          storageClassName: <nfs>
YAML 字段说明

保存为 chains.demo-3.pipelinerun.yaml 文件并应用:

$ export NAMESPACE=<default>

# 在命名空间中创建 PipelineRun 资源
$ kubectl create -n $NAMESPACE -f chains.demo-3.pipelinerun.yaml

等待 PipelineRun 完成。

$ kubectl get pipelinerun -n $NAMESPACE -w

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

步骤 6:等待 PipelineRun 被签名

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

$ export NAMESPACE=<default>
$ export PIPELINERUN_NAME=<chains-demo-3-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}')

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

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

# 输出镜像引用
$ echo $IMAGE

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

该镜像将用于验证源代码仓库。

步骤 8:(可选)获取 SLSA Provenance 证明

TIP

如果您对 SLSA Provenance 证明内容感兴趣,可以继续阅读以下内容。

更多关于 SLSA Provenance 证明的详情,请参见 SLSA Provenance

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

# 禁用 tlog 上传,启用私有基础设施
$ export COSIGN_TLOG_UPLOAD=false
$ export COSIGN_PRIVATE_INFRASTRUCTURE=true

$ export IMAGE=<<registry>/test/chains/demo-3:latest@sha256:db2607375049e8defa75a8317a53fd71fd3b448aec3c507de7179ded0d4b0f20>

$ cosign verify-attestation --key cosign.pub --type slsaprovenance $IMAGE | jq -r '.payload | @base64d' | jq -s

输出类似如下,包含 SLSA Provenance 证明。

SLSA Provenance 证明
{
  "_type": "https://in-toto.io/Statement/v0.1",
  "subject": [
    {
      "name": "<registry>/test/chains/demo-3:latest",
      "digest": {
        "sha256": "db2607375049e8defa75a8317a53fd71fd3b448aec3c507de7179ded0d4b0f20"
      }
    }
  ],
  "predicateType": "https://slsa.dev/provenance/v0.2",
  "predicate": {
    "buildConfig": {
      "tasks": null
    },
    "buildType": "tekton.dev/v1beta1/PipelineRun",
    "builder": {
      "id": "https://alauda.io/builders/tekton/v1"
    },
    "invocation": {
      "parameters": {
        "image": "<registry>/test/chains/demo-3:latest"
      }
    },
    "materials": [
      {
        "digest": {
          "sha256": "bad5d84ded24307d12cacc9ef37fc38bce90ea5d00501f43b27d0c926be26f19"
        },
        "uri": "oci://<registry>/devops/tektoncd/hub/run-script"
      },
      {
        "digest": {
          "sha1": "cccccaaaa0000000000000000000000000000000"
        },
        "uri": "https://github.com/tektoncd/pipeline"
      }
    ],
    "metadata": {
      "buildFinishedOn": "2025-06-06T10:28:21Z",
      "buildStartedOn": "2025-06-06T10:27:34Z"
    }
  }
}
字段说明
  • predicateType:谓词类型。
  • predicate
    • buildConfig
      • tasks:构建任务。
    • buildType:构建类型,此处为 tekton.dev/v1beta1/PipelineRun
    • builder
      • id:构建者 ID,此处为 https://alauda.io/builders/tekton/v1
    • invocation
      • parameters:构建参数。
    • materials:构建材料。
      • uri
        • oci://<registry>/devops/tektoncd/hub/run-script:使用的任务镜像。
        • https://github.com/tektoncd/pipeline:任务的源代码仓库。
    • metadata:构建元数据。
      • buildFinishedOn:构建完成时间。
      • buildStartedOn:构建开始时间。

步骤 9:使用 Kyverno 验证镜像源代码仓库限制

步骤 9.1:创建 Kyverno 策略,仅允许从特定源代码仓库构建的镜像部署

TIP

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

更多关于 Kyverno ClusterPolicy,请参见 Kyverno ClusterPolicy

策略如下:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-code-repository-material
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:
              # 凭证需存在于 kyverno 所在命名空间
              - registry-credentials

          attestations:
            - type: https://slsa.dev/provenance/v0.2
              attestors:
                - entries:
                    - keys:
                        publicKeys: |- # <- 签名者的公钥
                          -----BEGIN PUBLIC KEY-----
                          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFZNGfYwn7+b4uSdEYLKjxWi3xtP3
                          UkR8hQvGrG25r0Ikoq0hI3/tr0m7ecvfM75TKh5jGAlLKSZUJpmCGaTToQ==
                          -----END PUBLIC KEY-----

                        ctlog:
                          ignoreSCT: true

                        rekor:
                          ignoreTlog: true
              conditions:
                - all:
                    - key: "{{ buildType }}"
                      operator: Equals
                      value: "tekton.dev/v1beta1/PipelineRun"
                      message: "The buildType must be equal to tekton.dev/v1beta1/PipelineRun, not {{ buildType }}"

                    - key: "{{ materials[?starts_with(uri, 'https://github.com/tektoncd/')] | length(@) }}"
                      operator: GreaterThan
                      value: 0
                      message: "The materials must have at least one entry starts with https://github.com/tektoncd/, {{ materials }}"
YAML 字段说明
  • 策略与镜像签名验证中的类似。
  • spec.rules[].verifyImages[].attestations[].conditions:验证条件。
    • all:所有条件必须满足。
      • key: "{{ buildType }}":构建类型必须等于 tekton.dev/v1beta1/PipelineRun
      • key: "{{ materials[?starts_with(uri, 'https://github.com/tektoncd/')] | length(@) }}":材料中必须至少有一条以 https://github.com/tektoncd/ 开头。

保存为 verify-code-repository-material.yaml 并应用:

$ kubectl create -f verify-code-repository-material.yaml

clusterpolicy.kyverno.io/verify-code-repository-material created

步骤 9.2:验证策略

在定义策略的 policy 命名空间中,创建 Pod 以验证策略。

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

$ export NAMESPACE=<policy>
$ export IMAGE=<<registry>/test/chains/demo-3:latest@sha256:db2607375049e8defa75a8317a53fd71fd3b448aec3c507de7179ded0d4b0f20>

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

pod/built-from-specific-repo created

Pod 会成功创建。

$ kubectl get pod -n $NAMESPACE built-from-specific-repo

NAME                      READY   STATUS    RESTARTS   AGE
built-from-specific-repo   1/1     Running   0          10s

ClusterPolicy 中的代码仓库改为其他值 https://gitlab.com/,再次验证。

conditions:
  - all:
      - key: "{{ buildType }}"
        operator: Equals
        value: "tekton.dev/v1beta1/PipelineRun"
        message: "The buildType must be equal to tekton.dev/v1beta1/PipelineRun, not {{ buildType }}"

      - key: "{{ materials[?starts_with(uri, 'https://gitlab.com/')] | length(@) }}"
        operator: GreaterThan
        value: 0
        message: "The materials must have at least one entry starts with https://gitlab.com/, {{ materials }}"
$ kubectl run -n $NAMESPACE unbuilt-from-specific-repo --image=${IMAGE} -- sleep 3600

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

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

resource Pod/policy/unbuilt-from-specific-repo was blocked due to the following policies

verify-code-repository-material:
  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-3:latest and predicate https://slsa.dev/provenance/v0.2:
    The materials must have at least one entry starts with https://gitlab.com/,
    [{"digest":{"sha256":"bad5d84ded24307d12cacc9ef37fc38bce90ea5d00501f43b27d0c926be26f19"},"uri":"oci://<registry>/devops/tektoncd/hub/run-script"},{"digest":{"sha256":"7a63e6c2d1b4c118e9a974e7850dd3e9321e07feec8302bcbcd16653c512ac59"},"uri":"http://tekton-hub-api.tekton-pipelines:8000/v1/resource/catalog/task/run-script/0.1/yaml"},{"digest":{"sha256":"8d5ea9ecd9b531e798fecd87ca3b64ee1c95e4f2621d09e893c58ed593bfd4c4"},"uri":"oci://<registry>/devops/tektoncd/hub/buildah"},{"digest":{"sha256":"3225653d04c223be85d173747372290058a738427768c5668ddc784bf24de976"},"uri":"http://tekton-hub-api.tekton-pipelines:8000/v1/resource/catalog/task/buildah/0.9/yaml"},{"digest":{"sha1":"cccccaaaa0000000000000000000000000000000"},"uri":"https://github.com/tektoncd/pipeline"}]'

步骤 10:清理资源

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

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

删除策略。

$ kubectl delete clusterpolicy verify-code-repository-material

预期结果

完成本指南后:

  • 您已成功配置 Tekton Chains 生成 SLSA Provenance,及 Kyverno 进行源代码仓库验证
  • 容器镜像自动包含源代码仓库信息于其 SLSA Provenance 中
  • 仅允许从指定源代码仓库构建的镜像在指定命名空间中部署
  • 来自未授权源代码仓库的镜像会被 Kyverno 策略自动阻止
  • 通过验证容器镜像的源代码来源,实现了基础的供应链安全控制

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

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

参考资料