Криптографическая подпись Docker-образов: связка Cosign, AWS KMS и политик Kyverno для защиты Kubernetes
В стандартных процессах поставки контейнеров в Kubernetes существует уязвимость доверия: кластер запускает любой образ, указанный в манифесте, если узел имеет сетевой доступ к реестру. Использование изменяемых тегов (например, latest или версионных меток) создает риск подмены содержимого контейнера в реестре, компрометации сборочных раннеров или атак типа Man-in-the-Middle. Для построения защищенной цепочки поставки (Software Supply Chain Security) промышленным стандартом становится сквозная криптографическая подпись неизменяемых OCI-образов с принудительной проверкой политиками допуска (Admission Control).
В этом руководстве разобран практический стек: утилита Cosign (проект Sigstore) для формирования цифровой подписи, сервис аппаратных ключей AWS Key Management Service (KMS) для исключения утечки приватных ключей, реестр Amazon ECR и движок политик Kyverno для блокировки недоверенных контейнеров на входе в кластер.
Архитектурная модель и границы доверия
Безопасность цепочки поставки строится на разделении ответственности между инфраструктурными компонентами:
- Среда сборки (CI/CD): собирает контейнер, отправляет его в реестр и получает неизменяемый криптографический хэш содержимого — OCI digest (вида
sha256:...). Никакие операции не должны полагаться на мутабельный тег. - Аппаратный модуль AWS KMS (Hardware Security Module, HSM): хранит асимметричный ключ подписи. Приватный ключ никогда не экспортируется на диск раннера CI; утилита Cosign отправляет в AWS API только хэш артефакта, а операция шифрования
kms:Signвыполняется внутри изолированного облачного модуля. - Реестр Amazon ECR: хранит как сам Docker-образ, так и артефакт подписи Cosign, оформленный в виде отдельного OCI-объекта с привязкой к дайджесту.
- Контроллер допуска Kyverno в Kubernetes: перехватывает запросы на создание подов к Kubernetes API Server, извлекает публичный ключ из AWS KMS и разрешает запуск только тех контейнеров, чья подпись успешно верифицирована.
[CI/CD Runner] ---> Push Image ---> [Amazon ECR]
| ^
kms:Sign |
v |
[AWS KMS] ---> Cosign Signature -------+
|
[K8s API] <--- Create Pod (digest) -------+
|
[Kyverno Webhook] ---> Verify Signature via AWS KMS Public Key
|
+---> Valid Signature ---> [Pod Scheduled on Worker Node]
+---> Missing / Invalid ---> [Admission Rejected (403 Forbidden)]
Такая связка гарантирует, что даже при компрометации учетной записи реестра или подмене тега злоумышленник не сможет запустить произвольный код в продакшен-кластере.
Шаг 1. Создание асимметричного ключа в AWS KMS
Для работы Cosign требуется асимметричная пара ключей. Генерация выполняется через консоль AWS или утилиту AWS CLI на рабочей станции администратора.
Создание ключа с алгоритмом эллиптической криптографии ECC_NIST_P256:
aws kms create-key --key-spec ECC_NIST_P256 --key-usage SIGN_VERIFY --description "Ключ подписи контейнеров для Cosign и Kyverno"
Команда возвращает JSON с идентификатором KeyId и полным ресурсом Arn вида arn:aws:kms:eu-central-1:123456789012:key/abcd-1234-....
Для удобства создается системный псевдоним (alias):
aws kms create-alias --alias-name "alias/container-signing-key" --target-key-id "abcd-1234-..."
Безопасность IAM: сборочному пайплайну CI/CD назначается минимальная IAM-роль, содержащая исключительно право kms:Sign для созданного Key ARN. Доступ к администрированию или удалению ключа должен быть строго ограничен.
Шаг 2. Сборка, публикация и подпись OCI-дайджеста
На сборочном сервере CI/CD выполняется сборка контейнера и его отправка в реестр Amazon ECR. Критически важно извлечь именно неизменяемый дайджест:
# Сборка и отправка образа с версионным тегом
docker build -t 123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-app:v1.0.0 .
docker push 123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-app:v1.0.0
# Получение точного дайджеста манифеста
IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' 123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-app:v1.0.0)
echo "Неизменяемый дайджест: ${IMAGE_DIGEST}"
Далее утилита Cosign подписывает дайджест, обращаясь к ключу AWS KMS по протоколу awskms://:
cosign sign --yes --key "awskms://arn:aws:kms:eu-central-1:123456789012:key/abcd-1234-..." "${IMAGE_DIGEST}"
Подробные параметры описаны в официальной документации Sigstore Cosign. Cosign сформирует подпись и автоматически загрузит ее в ECR как дополнительный OCI-артефакт с тегом формата sha256-<hash>.sig.
Перед передачей образа в деплой выполняется локальная проверка в пайплайне:
cosign verify --key "awskms://arn:aws:kms:eu-central-1:123456789012:key/abcd-1234-..." "${IMAGE_DIGEST}"
Если подпись отсутствует или повреждена, команда завершится с ненулевым кодом ошибки, останавливая выкатку до обращения к кластеру.
Шаг 3. Настройка контроллера Kyverno в Kubernetes
Чтобы проверка стала обязательной для всего кластера, используется движок Kyverno. Для интеграции с AWS KMS сервисному аккаунту Kyverno в кластере (EKS) через механизм IAM Roles for Service Accounts (IRSA) выдается право на чтение публичного ключа kms:GetPublicKey.
В кластере создается ресурс ClusterPolicy, регламентирующий проверку образов для рабочего пространства (namespace):
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-signature
spec:
validationFailureAction: Enforce
webhookTimeoutSeconds: 15
rules:
- name: verify-signature-from-kms
match:
any:
- resources:
kinds:
- Pod
namespaces:
- production
verifyImages:
- imageReferences:
- "123456789012.dkr.ecr.eu-central-1.amazonaws.com/*"
attestors:
- entries:
- kms: "awskms://arn:aws:kms:eu-central-1:123456789012:key/abcd-1234-..."
Специфика синтаксиса приведена в руководстве Kyverno по валидации Sigstore. Директива validationFailureAction: Enforce указывает контроллеру допуска немедленно отклонять запросы при сбое валидации (в отличие от режима Audit, который лишь фиксирует предупреждения в логах).
Шаг 4. Практическая верификация и обработка отказов
Для проверки корректности настройки проводятся два контролируемых сценария:
- Позитивный сценарий: развертывание пода с подписанным дайджестом:
apiVersion: v1
kind: Pod
metadata:
name: test-app-signed
namespace: production
spec:
containers:
- name: app
image: 123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-app@sha256:4f8a...
Манифест успешно применяется, под переходит в статус Running.
- Негативный сценарий: попытка запустить неподписанный образ или тот же контейнер по голому тегу:
kubectl run test-untrusted --namespace=production --image=123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-app:v1.0.0
API-сервер Kubernetes возвращает немедленный отказ:
Error from server: admission webhook "check-image-signature.kyverno.svc" denied the request:
image 123456789012.dkr.ecr.eu-central-1.amazonaws.com/my-app:v1.0.0 failed verification:
no valid signatures found for the specified image
Эксплуатационные риски и границы применимости
Внедрение криптографической верификации требует строгого соблюдения эксплуатационных правил:
- Ограничение области действия (Scope): правило
imageReferencesдолжно точно соответствовать доверенному внутреннему репозиторию организации. Нельзя применять универсальные маски*ко всем образам кластера без исключений, иначе блокировка затронет системные поды CNI-плагинов, CoreDNS или ingress-контроллеров. - Ротация ключей KMS: при плановой смене ключа подписи в конфигурации Kyverno должен быть организован переходный период с доверием к обоим ключам (старому и новому). В противном случае образы ранее развернутых версий приложений потеряют валидность при аварийном перезапуске подов.
- Ограничения гарантий: наличие цифровой подписи удостоверяет только то, что конкретный бинарный дайджест был выпущен авторизованным сборочным контуром. Подпись не заменяет статический анализ кода, сканирование уязвимостей в пакетах (CVE) и проверку спецификации SBOM (Software Bill of Materials).
