• Русский
  • Проверка базового образа и SBOM

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

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

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

    Обзор функции

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

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

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

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

    • Реализация проверки базового образа в Kubernetes-кластерах с помощью Kyverno
    • Применение политик безопасности, разрешающих развертывание только определённых базовых образов
    • Настройка автоматической генерации и проверки SBOM в CI/CD пайплайнах
    • Обеспечение соответствия базового образа требованиям в продуктивной среде
    • Внедрение мер безопасности цепочки поставок для контейнерных образов путём проверки информации о базовом образе

    Требования

    • Kubernetes-кластер с установленными Tekton Pipelines, Tekton Chains и Kyverno
    • Реестр с разрешённой загрузкой образов
    • Установленный и настроенный CLI kubectl для доступа к кластеру
    • Установленный CLI-инструмент cosign
    • Установленный CLI-инструмент jq

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

    ШагОперацияОписание
    1Генерация ключей подписиСоздание пары ключей для подписи артефактов с помощью cosign
    2Настройка аутентификацииКонфигурация учётных данных реестра для загрузки образов
    3Настройка Tekton ChainsНастройка Chains для использования OCI-хранилища и подписи, отключение TaskRun SLSA Provenance
    4Создание примерного пайплайнаСоздание определения пайплайна с задачей syft для генерации SBOM
    5Запуск примерного пайплайнаСоздание и запуск PipelineRun с правильной конфигурацией
    6Ожидание подписиОжидание подписи PipelineRun Chains
    7Получение информации об образеИзвлечение URI и дайджеста образа из PipelineRun
    8(Опционально) Получение аттестации SBOMПолучение и проверка аттестации SBOM
    9Проверка с KyvernoСоздание и применение политики Kyverno для проверки информации о базовом образе
    10ОчисткаУдаление тестовых ресурсов и политик

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

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

    Эти шаги идентичны руководству Быстрый старт: Подписанная Provenance. Пожалуйста, следуйте инструкциям в том руководстве для:

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

    Это ресурс Pipeline, который используется для сборки образа и генерации 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

    В этом руководстве демонстрируется упрощённый рабочий процесс, при котором Containerfile и вывод задачи git-clone генерируются непосредственно в пайплайне.
    В продуктивной среде обычно:

    1. Используется задача git-clone для получения исходного кода из репозитория
    2. Сборка образа происходит с использованием Containerfile из исходного кода
    3. Такой подход обеспечивает правильное управление версиями и разделение кода и конфигурации пайплайна
    Объяснение полей YAML
    • Аналогично Шагу 4: Создание примерного пайплайна, но добавлено:
      • workspaces:
        • signkey: рабочее пространство для приватных ключей и паролей, используемых для подписей образов.
      • tasks:
        • syft-sbom: задача для генерации SBOM для образа и загрузки аттестации. :::

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

    $ export NAMESPACE=<default>
    
    # создать пайплайн в namespace
    $ 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: registryconfig
          secret:
            secretName: <registry-credentials>
        - name: source
          volumeClaimTemplate:
            spec:
              accessModes:
                - ReadWriteOnce
              resources:
                requests:
                  storage: 1Gi
              storageClassName: <nfs>

    :::details {title="Объяснение полей YAML"}

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

    $ export NAMESPACE=<default>
    
    # создать PipelineRun в 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", это означает, что образ подписан.

    Шаг 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

    Если вас интересует содержимое аттестации 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

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

    :::details {title="Аттестация 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"
              }
            ]
          }
        }
      }
    }

    :::details {title="Описание полей"}

    • predicateType: Тип предиката.
    • predicate:
      • components: Компоненты образа.
        • bom-ref: Ссылка BOM компонента.
        • licenses: Лицензии компонента.
          • license: Лицензия компонента.
            • name: Название лицензии.
            • 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:
                  # Учетные данные должны существовать в namespace, где развернут 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 — 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: Проверка политики

    В namespace 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: Очистка ресурсов

    Удалите Pods, созданные на предыдущих шагах.

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

    Удалите политику.

    $ kubectl delete clusterpolicy verify-base-image

    Ожидаемые результаты

    После выполнения этого руководства:

    • У вас настроена работа с Tekton Chains для генерации SBOM и Kyverno для проверки базового образа
    • Ваши контейнерные образы автоматически включают информацию SBOM в свои аттестации
    • В указанном namespace разрешено развертывание только образов с допустимыми базовыми образами
    • Образы с несоответствующими базовыми образами автоматически блокируются политиками Kyverno
    • Вы реализовали базовый контроль безопасности цепочки поставок, проверяя информацию о базовом образе контейнерных образов

    Это руководство служит основой для внедрения безопасности цепочки поставок в ваших CI/CD пайплайнах. В продуктивной среде рекомендуется:

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

    Ссылки