Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи?

Написать
Войти
Дайджесты
Цифровая подпись контейнерного образа перед запуском в Kubernetes

Контроль целостности образов в Kubernetes с помощью Cosign и Kyverno

Внедрение цифровой подписи контейнеров гарантирует запуск в продакшене только проверенного и неизмененного кода. Карточка подробно описывает практическую настройку цепочки доверия с использованием утилит Cosign и Kyverno для автоматической блокировки любых неподписанных образов на этапе admission-контроля.

Контроль целостности образов в Kubernetes с помощью Cosign и Kyverno

Обеспечение безопасности цепочки поставок ПО (software supply chain security) — одна из критичных задач при эксплуатации кластеров Kubernetes. По умолчанию API-сервер никак не проверяет происхождение запускаемых образов. Если манифест указывает на образ ghcr.io/my-org/backend:latest, кластер доверяет ссылке и запускает его.

Теги контейнеров являются изменяемыми (mutable). Злоумышленник, получивший доступ к реестру (registry), может подменить содержимое за тегом :latest или версией :v1.2.0, не меняя имени. Кроме того, скомпрометированный сервер сборки (CI/CD) может отправить в реестр вредоносный код под видом обновления. Для защиты от этих сценариев необходимо внедрить проверку подлинности образов на этапе их допуска в кластер (admission control) путем создания цифровых подписей с помощью утилиты Cosign и проверки в кластере через политики Kyverno или Sigstore Policy Controller.

Почему теги небезопасны и как работает цифровая подпись?

Единственным надежным идентификатором контейнера является его уникальный криптографический хэш (digest) — например, image@sha256:7d8a.... Этот хэш рассчитывается на основе содержимого слоев контейнера и его манифеста. Любое изменение слоев приведет к генерации другого хэша.

Инструмент Cosign, созданный в рамках проекта Sigstore, позволяет подписывать именно этот хэш образа. Процесс выглядит следующим образом:

  1. Система сборки отправляет образ в реестр.
  2. Система определяет точный хэш (digest) опубликованного образа.
  3. С помощью приватного ключа Cosign подписывает этот хэш.
  4. Цифровая подпись загружается в тот же реестр в виде специального OCI-артефакта, привязанного к исходному образу.

Когда Kubernetes пытается запустить контейнер, контролирующий компонент (admission controller) перехватывает запрос, находит подпись в реестре, проверяет ее с помощью публичного ключа и сверяет хэш. Если подпись неверна или хэш не совпадает, запуск блокируется на уровне API-сервера.

Выбор режима подписи: Ключи против Keyless

Cosign поддерживает два основных режима создания подписей:

  1. Режим с парой ключей (Keypair-mode): Вы генерируете собственную пару ключей. Приватный ключ шифруется паролем и хранится в секретах вашей системы сборки. Публичный ключ импортируется в кластер Kubernetes и прописывается в политиках проверки. Этот режим прост для изолированных приватных кластеров, но требует надежного хранения и ротации приватного ключа.
  2. Беспарольный режим (Keyless-mode): Исключает необходимость хранения приватных ключей. Вместо этого Cosign использует протокол OIDC для подтверждения личности того, кто подписывает образ. Например, в GitHub Actions система проверяет токен сборщика, обращается к удостоверяющему центру Fulcio и получает временный сертификат. Запись о подписи публикуется в публичном логе прозрачности Rekor (transparency log). Этот режим безопасен, но требует доступа в интернет и публикует метаданные в открытый лог.

При работе с приватными реестрами в режиме с ключами важно учитывать конфиденциальность метаданных. По умолчанию Cosign отправляет запись в публичный лог Rekor. Для приватных проектов это поведение следует отключать с помощью флага --tlog-upload=false, а при проверке подписи указывать параметр --insecure-ignore-tlog=true.

Настройка admission-контроля с помощью Kyverno

Kyverno — движок политик (policy engine) для Kubernetes. Он позволяет описывать правила проверки ресурсов на декларативном языке YAML. Для проверки подписей Kyverno предоставляет встроенное правило verifyImages. При его активации Kyverno перехватывает запросы на создание подов, связывается с реестром контейнеров, находит подпись, проверяет ее по публичному ключу и разрешает запуск.

Пример политики Kyverno ClusterPolicy для проверки образов:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  webhookConfiguration:
    failurePolicy: Fail
    timeoutSeconds: 30
  background: false
  rules:
    - name: verify-ghcr-signatures
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/my-org/backend*"
          failureAction: Enforce
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
                      -----END PUBLIC KEY-----

Если ваш реестр является приватным, Kyverno потребуется доступ для чтения подписей. Для этого в пространстве имен, где запущен Kyverno (обычно kyverno), необходимо создать секрет с учетными данными для реестра (тип kubernetes.io/dockerconfigjson) и настроить его использование.

В качестве альтернативы Kyverno можно использовать Sigstore Policy Controller. Он выполняет только одну задачу: проверяет подписи и аттестации (attestations) образов перед их запуском. Policy Controller работает по принципу явного включения (opt-in) на уровне пространств имен с помощью метки policy.sigstore.dev/include=true. После этого контейнеры будут проверяться на соответствие глобальным политикам ClusterImagePolicy. Это предотвращает атаки класса TOCTOU (Time-of-Check to Time-of-Use), когда в момент проверки политика видит один образ, а в момент скачивания узлом загружается уже другой.

Руководство по внедрению

Последовательность действий для настройки приведена ниже. Дополнительную информацию можно найти в репозитории Cosign и документации Kyverno Sigstore verifyImages.

1. Установка утилиты Cosign и генерация ключей

Установите Cosign на локальную машину или сервер сборщика. Сгенерируйте пару ключей (приватный ключ cosign.key шифруется паролем, публичный ключ сохраняется в cosign.pub):

cosign generate-key-pair

Внимание: Надежно сохраните приватный ключ и пароль.

2. Публикация и подпись тестового образа

Отправьте образ в реестр. Получите его хэш и подпишите его (для приватного реестра используйте флаг отключения публикации в лог Rekor):

crane digest ghcr.io/my-org/backend:v1.0.0
cosign sign --key cosign.key --tlog-upload=false ghcr.io/my-org/backend@sha256:1234567890abcdef...

Проверьте подпись локально:

cosign verify --key cosign.pub --insecure-ignore-tlog=true ghcr.io/my-org/backend@sha256:1234567890abcdef...
3. Применение политики Kyverno

Создайте секрет с правами на чтение реестра в пространстве имен kyverno, примените описанный выше манифест политики ClusterPolicy (подставив содержимое cosign.pub в секцию publicKeys):

kubectl apply -f policy.yaml
4. Тестирование работы

Разверните под с подписанным образом (он должен успешно запуститься):

kubectl apply -f signed-pod.yaml

Попробуйте развернуть неподписанный контейнер. API-сервер отклонит запрос с ошибкой верификации:

kubectl apply -f unsigned-pod.yaml

Риски, ограничения и лучшие практики

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

Основной риск — блокировка системных компонентов. Если написать общую политику (проверяющую абсолютно все образы во всех пространствах имен), кластер не сможет запустить служебные поды Kubernetes (такие как kube-proxy, coredns), так как они подписаны ключами вендоров. Всегда ограничивайте область действия политики с помощью точных масок в imageReferences и исключайте системные пространства имен (kube-system).

Второй фактор — выбор параметра failurePolicy. Режим Fail блокирует создание подов, если сам движок политик Kyverno недоступен. Это безопасно, но создает риск отказа в обслуживании при сбое Kyverno. Режим Ignore пропускает поды в случае сбоя валидатора, обеспечивая непрерывность работы ценой временного снижения уровня контроля. Для продакшена рекомендуется использовать режим Fail, но обеспечивать высокую доступность самого Kyverno (запуск нескольких реплик, настройка PodDisruptionBudget).

Также уделите внимание жизненному циклу ключей. При компрометации приватного ключа вам потребуется сгенерировать новую пару, переподписать все легитимные образы в реестре и обновить публичный ключ в манифесте ClusterPolicy. Разработайте процедуру ротации ключей на тестовом контуре.