基础镜像和SBOM验证

如果我们只允许部署特定类型的基础镜像, 可以在获取镜像后将该信息保存到镜像的attestation中。

漏洞扫描与验证中,cosign-vuln格式的attestation已经包含了基础镜像信息。 但这里我们将采用不同的方法,使用syft为镜像生成SBOM。 SBOM信息同样包含基础镜像信息。

在ACP(Alauda Container Platform)中,可以使用Tekton Pipeline中的trivysyft任务为镜像生成SBOM。 这里我们使用syft任务生成SBOM。

目录

功能概述

该方法使用类似syft的工具为镜像生成SBOM,然后使用Kyverno验证SBOM:

  1. 使用syft Tekton任务为镜像生成SBOM并附加到镜像。
  2. 配置Kyverno规则验证SBOM。
  3. 使用该镜像创建Pod以验证SBOM。

使用场景

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

  • 在Kubernetes集群中使用Kyverno实现基础镜像验证
  • 强制安全策略仅允许特定基础镜像部署
  • 在CI/CD流水线中设置自动SBOM生成和验证
  • 确保生产环境中的基础镜像合规
  • 通过验证容器镜像的基础镜像信息,实现供应链安全控制

前提条件

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

流程概览

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

详细步骤说明

步骤1-3:基础环境搭建

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

步骤4:创建示例流水线

这是一个Pipeline资源,用于构建镜像并生成SBOM。

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: chains-demo-5
spec:
  params:
    - 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-5: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)
  tasks:
    - name: generate-dockerfile
      params:
        - name: script
          value: $(params.generate-dockerfile)
      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
    - name: syft-sbom
      params:
        - name: COMMAND
          value: |-
            set -x

            mkdir -p .git

            echo "Generate sbom.json"
            syft scan $(tasks.build-image.results.IMAGE_URL)@$(tasks.build-image.results.IMAGE_DIGEST) -o cyclonedx-json=.git/sbom.json > /dev/null

            echo -e "\n\n"
            cat .git/sbom.json
            echo -e "\n\n"

            echo "Generate and Attestation sbom"
            syft attest $(tasks.build-image.results.IMAGE_URL)@$(tasks.build-image.results.IMAGE_DIGEST) -o cyclonedx-json
      runAfter:
        - build-image
      taskRef:
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: syft
          - name: version
            value: "0.1"
        resolver: hub
      timeout: 30m0s
      workspaces:
        - name: source
          workspace: source
        - name: dockerconfig
          workspace: dockerconfig
        - name: signkey
          workspace: signkey
  workspaces:
    - name: source
      description: The workspace for source code.
    - name: dockerconfig
      description: The workspace for Docker configuration.
    - name: signkey
      description: The workspace for private keys and passwords used for image signatures.
TIP

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

  1. 使用git-clone任务从代码仓库拉取源代码
  2. 使用源代码中的Dockerfile构建镜像
  3. 该方式确保了版本控制的规范性,并保持代码与流水线配置的分离
YAML字段说明
  • 步骤4:创建示例流水线相同,但增加了:
    • workspaces:
      • signkey:用于镜像签名的私钥和密码的工作空间。
    • tasks:
      • syft-sbom:生成镜像SBOM并上传attestation的任务。 :::

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

$ export NAMESPACE=<default>

# 在命名空间中创建流水线
$ kubectl create -n $NAMESPACE -f chains-demo-5.yaml

pipeline.tekton.dev/chains-demo-5 created

步骤5:运行示例流水线

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

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

:::details {title="YAML字段说明"}

  • 步骤5:运行示例流水线相同,仅介绍差异。
  • workspaces
    • signkey:签名密钥的Secret名称。
      • secret.secretName:前面步骤获取签名密钥中准备的签名Secret,但需在流水线运行的命名空间中创建新的Secret。 :::

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

$ export NAMESPACE=<default>

# 在命名空间中创建流水线运行
$ kubectl create -n $NAMESPACE -f chains-demo-5.pipelinerun.yaml

等待PipelineRun完成。

$ kubectl get pipelinerun -n $NAMESPACE -w

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

步骤6:等待PipelineRun签名完成

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

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

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

    chains.tekton.dev/signed: "true"

当PipelineRun带有该注解时,表示镜像已被签名。

步骤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-5:latest@sha256:a6c727554be7f9496e413a789663060cd2e62b3be083954188470a94b66239c7

该镜像将用于验证SBOM。

步骤8:(可选)获取SBOM attestation

如果您对SBOM attestation内容感兴趣,可以继续阅读以下内容。

关于cyclonedx SBOM attestation的更多细节,请参考cyclonedx SBOM attestation

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

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

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

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

输出类似如下,包含镜像的组件信息。

:::details {title="cyclonedx SBOM attestation"}

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://cyclonedx.org/bom",
  "predicate": {
    "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
    "bomFormat": "CycloneDX",
    "components": [
      {
        "bom-ref": "os:ubuntu@24.04",
        "licenses": [
          {
            "license": {
              "name": "GPL"
            }
          }
        ],
        "description": "Ubuntu 24.04.2 LTS",
        "name": "ubuntu",
        "type": "operating-system",
        "version": "24.04"
      }
    ],
    "metadata": {
      "timestamp": "2025-06-07T09:56:05Z",
      "tools": {
        "components": [
          {
            "author": "anchore",
            "name": "syft",
            "type": "application",
            "version": "1.23.1"
          }
        ]
      }
    }
  }
}

:::details {title="字段说明"}

  • predicateType:谓词类型。
  • predicate
    • components:镜像组件。
      • bom-ref:组件的BOM引用。
      • licenses:组件的许可证。
        • license:许可证信息。
          • name:许可证名称。
          • id:许可证ID。
      • name:组件名称。
      • type:组件类型。
      • version:组件版本。
    • metadata:镜像元数据。
      • timestamp:时间戳。
      • tools
        • components:工具组件。
          • author:工具作者。
          • name:工具名称。
          • type:工具类型。
          • version:工具版本。 :::

步骤9:验证基础镜像信息

步骤9.1:创建Kyverno策略验证基础镜像信息

TIP

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

关于Kyverno ClusterPolicy的更多信息,请参考Kyverno ClusterPolicy

策略如下:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-base-image
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://cyclonedx.org/bom
              attestors:
                - entries:
                    - attestor:
                      keys:
                        publicKeys: |- # <- 签名者的公钥
                          -----BEGIN PUBLIC KEY-----
                          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFZNGfYwn7+b4uSdEYLKjxWi3xtP3
                          UkR8hQvGrG25r0Ikoq0hI3/tr0m7ecvfM75TKh5jGAlLKSZUJpmCGaTToQ==
                          -----END PUBLIC KEY-----

                        ctlog:
                          ignoreSCT: true

                        rekor:
                          ignoreTlog: true

              conditions:
                - any:
                    - key: "{{ components[?type=='operating-system'] | [?name=='ubuntu' && (version=='22.04' || version=='24.04')] | length(@) }}"
                      operator: GreaterThan
                      value: 0
                      message: "操作系统必须是Ubuntu 22.04或24.04,而非 {{ components[?type=='operating-system'].name[] }} {{ components[?type=='operating-system'].version[] }}"

                    - key: "{{ components[?type=='operating-system'] | [?name=='alpine' && (version=='3.18' || version=='3.20')] | length(@) }}"
                      operator: GreaterThan
                      value: 0
                      message: "操作系统必须是Alpine 3.18或3.20,而非 {{ components[?type=='operating-system'].name[] }} {{ components[?type=='operating-system'].version[] }}"
                       PkgIDs: {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `1.0`].PkgID[] }}.

:::details {title="YAML字段说明"}

  • 策略与镜像签名验证中的策略大致相同。
  • spec.rules[0].verifyImages[].attestations[0].conditions
    • type:cyclonedx SBOM attestation类型为https://cyclonedx.org/bom
    • attestors:同上。
    • conditions:需验证的条件。
      • any:满足任一条件即可。
        • key: "{{ components[?type=='operating-system'] | [?name=='ubuntu' && (version=='22.04' || version=='24.04')] | length(@) }}":操作系统必须是Ubuntu 22.04或24.04。
        • key: "{{ components[?type=='operating-system'] | [?name=='alpine' && (version=='3.18' || version=='3.20')] | length(@) }}":操作系统必须是Alpine 3.18或3.20。 :::

将策略保存为kyverno.verify-base-image.yaml文件并应用:

$ kubectl create -f kyverno.verify-base-image.yaml

clusterpolicy.kyverno.io/verify-base-image created

步骤9.2:验证策略

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

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

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

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

如果基础镜像是Ubuntu 22.04或24.04,Pod将成功创建。

ClusterPolicy中的条件改为只允许Alpine 3.18或3.20。

conditions:
  - any:
      - key: "{{ components[?type=='operating-system'] | [?name=='alpine' && (version=='3.18' || version=='3.20')] | length(@) }}"
        operator: GreaterThan
        value: 0
        message: "操作系统必须是Alpine 3.18或3.20,而非 {{ components[?type=='operating-system'].name[] }} {{ components[?type=='operating-system'].version[] }}"

然后创建Pod验证策略。

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

会收到如下输出:

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

resource Pod/policy/deny-base-image was blocked due to the following policies

verify-base-image:
  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-5:latest and predicate https://cyclonedx.org/bom:
    The operating system must be Alpine 3.18 or 3.20, not ["ubuntu"] ["24.04"]'

步骤10:清理资源

删除前面步骤创建的Pod。

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

删除策略。

$ kubectl delete clusterpolicy verify-base-image

预期结果

完成本指南后:

  • 您已成功搭建Tekton Chains用于SBOM生成,Kyverno用于基础镜像验证的环境
  • 容器镜像自动包含SBOM信息的attestation
  • 仅允许符合要求的基础镜像在指定命名空间中部署
  • 不合规基础镜像的镜像会被Kyverno策略自动阻止
  • 通过验证容器镜像的基础镜像信息,实现了基础的供应链安全控制

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

  1. 配置合理的命名空间隔离和访问控制
  2. 实施安全的签名密钥管理
  3. 设置策略违规的监控和告警
  4. 定期轮换签名密钥并更新安全策略

参考资料