基础镜像和SBOM验证

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

漏洞扫描与验证中,cosign-vuln格式的认证已经包含了基础镜像信息。 但这里我们将采用不同的方法,使用 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 认证获取并验证 SBOM 认证
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 并上传认证的任务。

保存为 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>
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 带有 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-5:latest@sha256:a6c727554be7f9496e413a789663060cd2e62b3be083954188470a94b66239c7

该镜像将用于验证 SBOM。

步骤 8:(可选)获取 SBOM 认证

TIP

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

关于 cyclonedx SBOM 认证的更多详情,请参考 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

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

cyclonedx SBOM 认证
{
  "_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"
          }
        ]
      }
    }
  }
}
字段说明
  • 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[] }}.
YAML 字段说明
  • 策略与镜像签名验证中的大致相同。
  • spec.rules[0].verifyImages[].attestations[0].conditions
    • type:cyclonedx SBOM 认证类型为 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 信息的认证
  • 仅允许符合要求的基础镜像在指定命名空间中部署
  • 不合规基础镜像的镜像被 Kyverno 策略自动阻止
  • 实现了通过验证容器镜像基础镜像信息的基础供应链安全控制

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

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

参考资料