漏洞扫描与验证

在 ACP(Alauda Container Platform)中,您可以使用 Tekton Pipeline 来构建镜像并扫描其漏洞。

具体来说,使用 trivy task 生成漏洞扫描结果,然后使用 cosign 上传漏洞扫描结果的 attestation,最后使用 kyverno 验证漏洞扫描结果的 attestation。

功能概述

此方法使用类似 trivy 的工具扫描镜像中的漏洞,然后使用 Kyverno 验证漏洞扫描结果:

  1. 使用 trivy Tekton Task 扫描镜像漏洞。
  2. 使用 cosign Tekton Task 将漏洞扫描结果上传到镜像。
  3. 配置 Kyverno 规则以验证漏洞扫描结果。
  4. 使用该镜像创建 Pod 以验证漏洞扫描结果。

使用场景

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

  • 在 Kubernetes 集群中使用 Kyverno 实现漏洞扫描与验证
  • 强制执行安全策略,仅允许部署漏洞等级可接受的镜像
  • 在 CI/CD 流水线中设置自动化漏洞扫描与验证
  • 确保生产环境中的镜像安全性和漏洞合规性
  • 通过验证容器镜像的漏洞状态,为其实现供应链安全控制

前提条件

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

流程概览

步骤操作描述
1生成签名密钥使用 cosign 创建用于签名工件的密钥对
2设置认证配置用于镜像推送的 registry 凭证
3配置 Tekton Chains设置 Chains 使用 OCI 存储并配置签名,禁用 TaskRun SLSA Provenance
4创建示例流水线创建包含 trivy 扫描器和 cosign 上传任务的流水线定义
5运行示例流水线创建并运行一个配置正确的 PipelineRun
6等待签名等待 Chains 对 PipelineRun 进行签名
7获取镜像信息从 PipelineRun 中提取镜像 URI 和 digest
8(可选)获取漏洞 attestation获取并验证漏洞扫描 attestation
9使用 Kyverno 验证创建并应用 Kyverno 策略以验证漏洞扫描结果
10(可选)要求最近扫描添加条件,要求漏洞扫描结果在 168 小时内
11清理删除测试资源和策略

分步说明

步骤 1-3:基础设置

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

步骤 4:创建示例流水线

这是一个 Pipeline 资源,用于构建镜像并生成 cosign 漏洞 attestation。

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: chains-demo-4
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-4: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-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
    - name: trivy-scanner
      params:
        - name: COMMAND
          value: |-
            set -x

            mkdir -p .git

            # support for insecure registry
            export TRIVY_INSECURE=true

            echo "generate cyclonedx sbom"
            trivy image --skip-db-update --skip-java-db-update --scanners vuln --format cyclonedx --output .git/sbom-cyclonedx.json $(tasks.build-image.results.IMAGE_URL)@$(tasks.build-image.results.IMAGE_DIGEST)
            cat .git/sbom-cyclonedx.json

            echo "trivy scan vulnerabilities based on cyclonedx sbom"
            trivy sbom --skip-db-update --skip-java-db-update --format cosign-vuln --output .git/trivy-scan-result.json .git/sbom-cyclonedx.json
            cat .git/trivy-scan-result.json

            echo "trivy scan vulnerabilities based on cyclonedx sbom and output in table format"
            trivy sbom --skip-db-update --skip-java-db-update --format table .git/sbom-cyclonedx.json
      runAfter:
        - build-image
      taskRef:
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: trivy-scanner
          - name: version
            value: "0.4"
        resolver: hub
      timeout: 30m0s
      workspaces:
        - name: source
          workspace: source
        - name: registryconfig
          workspace: registryconfig
    - name: cosign-uploads
      params:
        - name: COMMAND
          value: |-
            set -x

            export COSIGN_ALLOW_INSECURE_REGISTRY=true
            export COSIGN_TLOG_UPLOAD=false
            export COSIGN_KEY=$(workspaces.signkey.path)/cosign.key

            echo "Signing image vuln"
            cosign attest --type vuln --predicate .git/trivy-scan-result.json $(tasks.build-image.results.IMAGE_URL)@$(tasks.build-image.results.IMAGE_DIGEST)

            echo "Signing image sbom"
            cosign attest --type cyclonedx --predicate .git/sbom-cyclonedx.json $(tasks.build-image.results.IMAGE_URL)@$(tasks.build-image.results.IMAGE_DIGEST)
      runAfter:
        - trivy-scanner
      taskRef:
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: cosign
          - name: version
            value: "0.1"
        resolver: hub
      timeout: 30m0s
      workspaces:
        - name: source
          workspace: source
        - name: registryconfig
          workspace: registryconfig
        - name: signkey
          workspace: signkey
  workspaces:
    - name: source
      description: The workspace for source code.
    - name: registryconfig
      description: The workspace for distribution registry configuration.
    - name: signkey
      description: The workspace for private keys and passwords used for image signatures.
TIP

本教程通过在 pipeline 中内联生成 Containerfilegit-clone task 输出,演示了一个简化的工作流。 在生产环境中,通常会:

  1. 使用 git-clone task 从您的仓库获取源代码
  2. 使用源代码中已有的 Containerfile 构建镜像
  3. 这种方式可确保正确的版本控制,并保持代码与 pipeline 配置之间的分离
YAML 字段说明
  • Step 4: Create a Sample Pipeline 中的内容相同,但增加了以下内容:
    • workspaces:
      • signkey: 用于镜像签名的私钥和密码的 workspace。
    • tasks:
      • trivy-scanner: 用于扫描镜像漏洞的 task。
      • cosign-uploads: 用于上传漏洞扫描结果 attestation 的 task。 :::

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

$ export NAMESPACE=<default>

# create the pipeline in the namespace
$ kubectl create -n $NAMESPACE -f chains-demo-4.yaml

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

步骤 5:运行示例流水线

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

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

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

  • Step 5: Run a Sample Pipeline 中的内容相同。下面仅介绍差异部分。
  • workspaces
    • signkey: 签名密钥的 secret 名称。
      • secret.secretName: 前一步 Get the signing secret 中准备的签名 secret。但您需要在与 pipeline run 相同的 namespace 中创建一个新的 secret。 :::

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

$ export NAMESPACE=<default>

# create the pipeline run in the namespace
$ kubectl create -n $NAMESPACE -f chains-demo-4.pipelinerun.yaml

等待 PipelineRun 完成。

$ kubectl get pipelinerun -n $NAMESPACE -w

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

步骤 6:等待 PipelineRun 完成签名

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

$ export NAMESPACE=<default>
$ export PIPELINERUN_NAME=<chains-demo-4-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 获取镜像

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

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

# Combine the image URI and digest to form the full image reference
$ export IMAGE=$IMAGE_URI@$IMAGE_DIGEST

# Print the image reference
$ echo $IMAGE

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

该镜像将用于验证漏洞扫描结果。

步骤 8:(可选)获取 cosign vuln attestation

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

关于 cosign vuln attestation 的更多细节,请参考 cosign vuln attestation

根据 Get the signing public key 章节获取签名公钥。

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

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

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

输出将类似如下内容,其中包含漏洞扫描结果。

:::details {title="cosign vuln attestation"}

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1",
  "predicate": {
    "scanner": {
      "uri": "pkg:github/aquasecurity/trivy@dev",
      "version": "dev",
      "result": {
        "CreatedAt": "2025-06-07T07:05:30.098889688Z",
        "Metadata": {
          "OS": {
            "Family": "ubuntu",
            "Name": "24.04"
          }
        },
        "Results": [
          {
            "Class": "os-pkgs",
            "Packages": [
              {
                "Arch": "amd64",
                "ID": "coreutils@9.4-3ubuntu6",
                "Identifier": {
                  "BOMRef": "pkg:deb/ubuntu/coreutils@9.4-3ubuntu6?arch=amd64&distro=ubuntu-24.04",
                  "PURL": "pkg:deb/ubuntu/coreutils@9.4-3ubuntu6?arch=amd64&distro=ubuntu-24.04",
                  "UID": "82bb3c93286700bc"
                },
                "Licenses": [
                  "GPL-3.0-or-later",
                  "BSD-4-Clause-UC",
                  "GPL-3.0-only",
                  "ISC",
                  "FSFULLR",
                  "GFDL-1.3-no-invariants-only",
                  "GFDL-1.3-only"
                ],
                "Name": "coreutils"
              }
            ],
            "Vulnerabilities": [
              {
                "CVSS": {
                  "nvd": {
                    "V2Score": 2.1,
                    "V2Vector": "AV:L/AC:L/Au:N/C:N/I:P/A:N",
                    "V3Score": 6.5,
                    "V3Vector": "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N"
                  },
                  "redhat": {
                    "V2Score": 6.2,
                    "V2Vector": "AV:L/AC:H/Au:N/C:C/I:C/A:C",
                    "V3Score": 8.6,
                    "V3Vector": "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H"
                  }
                },
                "InstalledVersion": "9.4-3ubuntu6",
                "LastModifiedDate": "2025-04-20T01:37:25.86Z",
                "PkgID": "coreutils@9.4-3ubuntu6",
                "PkgName": "coreutils",
                "PublishedDate": "2017-02-07T15:59:00.333Z",
                "References": [
                  "http://seclists.org/oss-sec/2016/q1/452",
                  "http://www.openwall.com/lists/oss-security/2016/02/28/2",
                  "http://www.openwall.com/lists/oss-security/2016/02/28/3",
                  "https://access.redhat.com/security/cve/CVE-2016-2781",
                  "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772%40%3Cdev.mina.apache.org%3E",
                  "https://lore.kernel.org/patchwork/patch/793178/",
                  "https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.28/v2.28-ReleaseNotes",
                  "https://nvd.nist.gov/vuln/detail/CVE-2016-2781",
                  "https://www.cve.org/CVERecord?id=CVE-2016-2781"
                ],
                "Severity": "LOW",
                "SeveritySource": "ubuntu",
                "Status": "affected",
                "VendorSeverity": {
                  "azure": 2,
                  "cbl-mariner": 2,
                  "nvd": 2,
                  "redhat": 2,
                  "ubuntu": 1
                },
                "VulnerabilityID": "CVE-2016-2781"
              }
            ]
          }
        ],
        "SchemaVersion": 2
      }
    },
    "metadata": {
      "scanStartedOn": "2025-06-07T07:05:30.104726629Z",
      "scanFinishedOn": "2025-06-07T07:05:30.104726629Z"
    }
  }
}

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

  • predicateType: predicate 的类型。
  • predicate.scanner:
    • uri: 扫描器的 URI。
    • version: 扫描器的版本。
    • result: 漏洞扫描结果。
      • CreatedAt: 漏洞扫描完成的时间。
      • Metadata:
        • OS:
          • Family: OS 的家族。
          • Name: OS 的名称。
      • Results: 漏洞扫描结果。
        • Class:
          • os-pkgs: OS 软件包。
          • lang-pkgs: 语言软件包。
        • Packages: 镜像的软件包。
        • Vulnerabilities: 镜像中的漏洞。
          • Severity: 漏洞的严重级别。
          • PkgID: 漏洞对应的软件包 ID。
          • PkgName: 漏洞对应的软件包名称。
          • CVSS: 漏洞的 CVSS。
            • nvd: 漏洞的 NVD 评分。
            • redhat: 漏洞的 Red Hat 评分。 :::

步骤 9:使用 Kyverno 验证漏洞扫描结果

步骤 9.1:创建一个 Kyverno 策略,拒绝高风险漏洞镜像

TIP

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

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

策略如下:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: reject-high-risk-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:
              # The credential needs to exist in the namespace where kyverno is deployed
              - registry-credentials

          attestations:
            - type: https://cosign.sigstore.dev/attestation/vuln/v1
              attestors:
                - entries:
                    - attestor:
                      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: "{{ scanner.result.Results[].Vulnerabilities[].Severity }}"
                      operator: AllNotIn
                      # supported values: UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL
                      value: ["HIGH", "CRITICAL"]
                      message: |
                        The image contains high-risk vulnerabilities, please fix them before proceeding.
                        Severity levels: {{ scanner.result.Results[].Vulnerabilities[].Severity }}

                    - key: "{{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `1.0`][] | length(@) }}"
                      operator: Equals
                      value: 0
                      message: |
                        The image contains high-risk vulnerabilities, please fix them before proceeding.
                        High-risk vulnerabilities (CVSS > 1.0): {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `1.0`].CVSS.redhat.V3Score[] }}.
                        Severity levels: {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `1.0`].Severity[] }}.
                        PkgIDs: {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `1.0`].PkgID[] }}.

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

  • 该策略与 Image Signature Verification 中的策略大体一致
  • spec.rules[0].verifyImages[].attestations[0].conditions
    • type: cosign vuln attestation 的类型为 https://cosign.sigstore.dev/attestation/vuln/v1
    • attestors: 与上文相同。
    • conditions: 需要验证的条件。
      • all: 所有条件都必须满足。
        • key: "{{ scanner.result.Results[].Vulnerabilities[].Severity }}": 漏洞严重级别不能为 HIGHCRITICAL
        • key: "{{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > 1.0][] | length(@) }}": CVSS 分数大于 1.0 的漏洞数量必须为 0。 :::

将策略保存为名为 kyverno.reject-high-risk-image.yaml 的 yaml 文件,并使用以下命令应用:

$ kubectl apply -f kyverno.reject-high-risk-image.yaml

clusterpolicy.kyverno.io/reject-high-risk-image configured

步骤 9.2:验证策略

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

使用前面构建的镜像创建 Pod。

$ export NAMESPACE=<policy>
$ export IMAGE=<<registry>/test/chains/demo-4:latest@sha256:0f123204c44969876ed12f40066ccccbfd68361f68c91eb313ac764d59428bef>

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

如果您的镜像存在高风险漏洞,Pod 会被该策略阻止。 您将收到如下输出:

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

resource Pod/policy/high-risk was blocked due to the following policies

reject-high-risk-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-4:latest and predicate https://cosign.sigstore.dev/attestation/vuln/v1: The image contains high-risk vulnerabilities, please fix them before proceeding.
    High-risk vulnerabilities (CVSS > 1.0): [8.6,2.7,6.2,5.9,7.5,4.7,7.4,4.7,7.4,4.7,7.4,4.7,7.4,5.9,3.6,3.6,7.3,4.4,6.5,5.4].
    Severity levels: ["LOW","MEDIUM","LOW","LOW","MEDIUM","MEDIUM","MEDIUM","MEDIUM","MEDIUM","MEDIUM","MEDIUM","MEDIUM","MEDIUM","LOW","LOW","LOW","MEDIUM","MEDIUM","MEDIUM","MEDIUM"].
    PkgIDs: ["coreutils@9.4-3ubuntu6","gpgv@2.4.4-2ubuntu17","gpgv@2.4.4-2ubuntu17","libgcrypt20@1.10.3-2build1","liblzma5@5.6.1+really5.4.5-1build0.1","libpam-modules@1.5.3-5ubuntu5.1","libpam-modules@1.5.3-5ubuntu5.1","libpam-modules-bin@1.5.3-5ubuntu5.1","libpam-modules-bin@1.5.3-5ubuntu5.1","libpam-runtime@1.5.3-5ubuntu5.1","libpam-runtime@1.5.3-5ubuntu5.1","libpam0g@1.5.3-5ubuntu5.1","libpam0g@1.5.3-5ubuntu5.1","libssl3t64@3.0.13-0ubuntu3.5","login@1:4.13+dfsg1-4ubuntu3.2","passwd@1:4.13+dfsg1-4ubuntu3.2","perl-base@5.38.2-3.2build2.1","golang.org/x/net@v0.23.0","golang.org/x/net@v0.23.0","stdlib@v1.22.12"].

修改 ClusterPolicy 中的条件,以允许存在高风险漏洞但 CVSS 分数小于 10.0 的镜像。

conditions:
  - all:
      - key: "{{ scanner.result.Results[].Vulnerabilities[].Severity }}"
        operator: AllNotIn
        value: ["CRITICAL"]
        message: |
          The image contains high-risk vulnerabilities, please fix them before proceeding.
          Severity levels: {{ scanner.result.Results[].Vulnerabilities[].Severity }}

      - key: "{{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `10.0`][] | length(@) }}"
        operator: Equals
        value: 0
        message: |
          The image contains high-risk vulnerabilities, please fix them before proceeding.
          High-risk vulnerabilities (CVSS > 10.0): {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `10.0`].CVSS.redhat.V3Score[] }}.
          Severity levels: {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `10.0`].Severity[] }}.
          PkgIDs: {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `10.0`].PkgID[] }}.

然后再次创建 Pod 以验证该策略。

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

Pod 将会成功创建。

步骤 10:(可选)要求漏洞扫描结果在 168 小时内

TIP

如果您还想为策略添加更多条件,可以继续阅读以下内容。

由于 Cosign Vulnerability Scan Record Attestation 包含 scanFinishedOn 字段, 并且 trivy 符合该规范,因此我们可以使用该字段来判断漏洞扫描结果是否在 168 小时内。

我们只需要在 ClusterPolicy 中添加一个条件,用于检查 scanFinishedOn 字段是否在 168 小时内。

conditions:
  - all:
      - key: "{{ time_since('','{{metadata.scanFinishedOn}}','') }}"
        operator: LessThanOrEquals
        value: "168h"
        message: "The vulnerability scan results must be within 168 hours, not {{ metadata.scanFinishedOn }}"

这里不再演示,有兴趣的读者可以自行尝试。

步骤 11:清理资源

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

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

删除该策略。

$ kubectl delete clusterpolicy reject-high-risk-image

预期结果

完成本指南后:

  • 您已经完成了一个可工作的 Tekton Chains 漏洞扫描和 Kyverno 漏洞验证环境
  • 您的容器镜像会自动在其 attestation 中包含漏洞扫描结果
  • 只有漏洞等级可接受的镜像才能在指定 namespace 中部署
  • 具有高风险漏洞的镜像会被 Kyverno 策略自动阻止
  • 您已经通过验证容器镜像的漏洞状态,实现了基础的供应链安全控制

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

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

参考资料