• Русский
  • Проверка базового образа и 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: Скрипт для генерации Containerfile для сборки образа.
          name: generate-containerfile
          type: string
        - default: <registry>/test/chains/demo-5:latest
          description: Адрес целевого образа для сборки
          name: image
          type: string
      results:
        - description: первый артефакт образа
          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: Рабочее пространство для исходного кода.
        - name: registryconfig
          description: Рабочее пространство для конфигурации реестра дистрибуции.
        - name: signkey
          description: Рабочее пространство для приватных ключей и паролей, используемых для подписей образов.
    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>
    
    # создать запуск пайплайна в 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. Регулярно обновлять ключи подписи и политики безопасности

    Ссылки