• Русский
  • Проверка Base Image и SBOM

    Если мы хотим разрешить к развертыванию только определенные типы base image, мы можем сохранить эту информацию в image attestation после ее получения.

    В разделе Сканирование и проверка уязвимостей attestation в формате cosign-vuln уже включают информацию о base image. Но здесь мы воспользуемся другим подходом: используем syft для генерации SBOM для image. Информация SBOM также включает сведения о base image.

    В ACP (Alauda Container Platform) можно использовать задачу trivy или syft в Tekton Pipeline для генерации SBOM для image. Здесь мы используем задачу syft для генерации SBOM.

    Обзор функциональности

    Этот метод использует инструменты, аналогичные syft, для генерации SBOM для image, а затем использует Kyverno для проверки SBOM:

    1. Использовать Tekton Task syft для генерации SBOM для image и прикрепить его к image.
    2. Настроить правила Kyverno для проверки SBOM.
    3. Использовать image для создания Pod, чтобы проверить SBOM.

    Сценарии использования

    Следующие сценарии требуют обращения к рекомендациям в этом документе:

    • Реализация проверки base image в кластерах Kubernetes с помощью Kyverno
    • Принудительное применение политик безопасности, разрешающих к развертыванию только определенные base image
    • Настройка автоматической генерации и проверки SBOM в CI/CD pipelines
    • Обеспечение соответствия base image в production-средах
    • Реализация controls безопасности supply chain для container images путем проверки информации об их base image

    Предварительные требования

    • Кластер Kubernetes с установленными Tekton Pipelines, Tekton Chains и Kyverno
    • Registry с включенной возможностью push image
    • Установленный и настроенный kubectl CLI для доступа к кластеру
    • Установленный cosign CLI tool
    • Установленный jq CLI tool

    Обзор процесса

    ШагОперацияОписание
    1Generate signing keysСоздать пару ключей для подписания artifacts с помощью cosign
    2Set up authenticationНастроить учетные данные registry для push image
    3Configure Tekton ChainsНастроить Chains на использование OCI storage и signing, отключить TaskRun SLSA Provenance
    4Create a sample pipelineСоздать определение pipeline с task syft для генерации SBOM
    5Run a sample pipelineСоздать и запустить PipelineRun с корректной конфигурацией
    6Wait for signingДождаться, пока PipelineRun будет подписан Chains
    7Get image informationИзвлечь URI image и digest из PipelineRun
    8(Optional) Get SBOM attestationПолучить и проверить SBOM attestation
    9Verify with KyvernoСоздать и применить политику Kyverno для проверки информации о base image
    10Clean upУдалить test resources и policies

    Пошаговые инструкции

    Шаги 1–3: Базовая настройка

    Эти шаги идентичны руководству Quick Start: Signed Provenance. Следуйте инструкциям в этом руководстве для:

    Шаг 4: Создание примера Pipeline

    Это ресурс Pipeline, который используется для сборки image и генерации SBOM.

    apiVersion: tekton.dev/v1
    kind: Pipeline
    metadata:
      name: chains-demo-5
    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-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-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: 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: 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

    В этом руководстве демонстрируется упрощенный workflow, в котором Containerfile и output задачи git-clone генерируются inline внутри pipeline. В production-средах обычно:

    1. Используют задачу git-clone для получения исходного кода из репозитория
    2. Собирают image с использованием Containerfile, который уже находится в исходном коде
    3. Такой подход обеспечивает корректный version control и сохраняет разделение между кодом и конфигурацией pipeline
    Explanation of YAML fields
    • То же самое, что и в Шаг 4: Create a Sample Pipeline, но добавляет следующий контент:
      • workspaces:
        • signkey: workspace для private keys и passwords, используемых для подписей image.
      • tasks:
        • syft-sbom: задача для генерации SBOM для image и загрузки attestation. :::

    Сохраните это в yaml-файл с именем chains-demo-5.yaml и примените его:

    $ export NAMESPACE=<default>
    
    # create the pipeline in the namespace
    $ kubectl create -n $NAMESPACE -f chains-demo-5.yaml
    
    pipeline.tekton.dev/chains-demo-5 created

    Шаг 5: Запуск примера Pipeline

    Это ресурс PipelineRun, который используется для запуска pipeline.

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

    :::details {title="Explanation of YAML fields"}

    • То же самое, что и в Шаг 5: Run a Sample Pipeline. Ниже приведены только различия.
    • workspaces
      • signkey: имя secret с signing key.
        • secret.secretName: signing secret, подготовленный на предыдущем шаге Get the signing secret. Но вам нужно создать новый secret в том же namespace, что и pipeline run. :::

    Сохраните это в yaml-файл с именем chains-demo-5.pipelinerun.yaml и примените его:

    $ export NAMESPACE=<default>
    
    # create the pipeline run in the namespace
    $ 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", это означает, что image подписан.

    Шаг 7: Получить image из 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-5:latest@sha256:a6c727554be7f9496e413a789663060cd2e62b3be083954188470a94b66239c7

    Этот image будет использоваться для проверки SBOM.

    Шаг 8: (Необязательно) Получить SBOM attestation

    Если вам интересен содержимое SBOM attestation, вы можете продолжить чтение следующего раздела.

    Более подробную информацию о cyclonedx SBOM attestation см. в cyclonedx SBOM attestation

    Получите signing public key в соответствии с разделом Получение 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-5:latest@sha256:a6c727554be7f9496e413a789663060cd2e62b3be083954188470a94b66239c7>
    
    $ cosign verify-attestation --key cosign.pub --type cyclonedx $IMAGE | jq -r '.payload | @base64d' | jq -s

    Вывод будет похож на следующий, он содержит информацию о components image.

    :::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="Description of the fields"}

    • predicateType: тип predicate.
    • predicate:
      • components: components image.
        • bom-ref: BOM reference компонента.
        • licenses: licenses компонента.
          • license: license компонента.
            • name: имя license.
            • id: идентификатор license.
        • name: имя компонента.
        • type: тип компонента.
        • version: версия компонента.
      • metadata: метаданные image.
        • timestamp: временная метка image.
        • tools:
          • components: components инструмента.
            • author: автор инструмента.
            • name: имя инструмента.
            • type: тип инструмента.
            • version: версия инструмента. :::

    Шаг 9: Проверка информации о base image

    Шаг 9.1: Создать политику Kyverno для проверки информации о base image

    TIP

    Этот шаг требует прав cluster administrator.

    Более подробную информацию о 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:
                  # The credential needs to exist in the namespace where kyverno is deployed
                  - registry-credentials
    
              attestations:
                - type: https://cyclonedx.org/bom
                  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:
                    - any:
                        - key: "{{ components[?type=='operating-system'] | [?name=='ubuntu' && (version=='22.04' || version=='24.04')] | length(@) }}"
                          operator: GreaterThan
                          value: 0
                          message: "The operating system must be Ubuntu 22.04 or 24.04, not {{ 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: "The operating system must be Alpine 3.18 or 3.20, not {{ components[?type=='operating-system'].name[] }} {{ components[?type=='operating-system'].version[] }}"
                           PkgIDs: {{ scanner.result.Results[].Vulnerabilities[?CVSS.redhat.V3Score > `1.0`].PkgID[] }}.

    :::details {title="Explanation of YAML fields"}

    • Политика в значительной степени совпадает с политикой из Image Signature Verification
    • 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. :::

    Сохраните политику в yaml-файл с именем kyverno.verify-base-image.yaml и примените ее:

    $ kubectl create -f kyverno.verify-base-image.yaml
    
    clusterpolicy.kyverno.io/verify-base-image created

    Шаг 9.2: Проверка политики

    В namespace policy, где определена политика, создайте Pod для проверки политики.

    Используйте собранный image для создания Pod.

    $ export NAMESPACE=<policy>
    $ export IMAGE=<<registry>/test/chains/demo-5:latest@sha256:a6c727554be7f9496e413a789663060cd2e62b3be083954188470a94b66239c7>
    
    $ kubectl run -n $NAMESPACE base-image --image=${IMAGE} -- sleep 3600

    Если ваш base image — 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: "The operating system must be Alpine 3.18 or 3.20, not {{ 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 для проверки base image
    • Ваши container images будут автоматически включать информацию SBOM в свои attestations
    • В указанном namespace к развертыванию будут допускаться только image с допустимыми base image
    • Image с несоответствующими base image будут автоматически блокироваться политиками Kyverno
    • Вы реализуете базовый control безопасности supply chain путем проверки информации о base image ваших container images

    Это руководство создает основу для реализации безопасности supply chain в ваших CI/CD pipelines. В production-среде следует:

    1. Настроить корректную изоляцию namespace и controls доступа
    2. Реализовать безопасное управление ключами для signing keys
    3. Настроить monitoring и alerting на нарушения политик
    4. Регулярно ротировать signing keys и обновлять политики безопасности

    Ссылки