Подпись образов в Kubernetes: Cosign, Harbor, Kyverno

Введение

Защита цепочки поставок контейнеров стала базовым требованием для производственных кластеров Kubernetes. По мере роста кластеров и увеличения числа команд, развёртывающих рабочие нагрузки, контроль на этапе допуска (admission-time controls) выполняет роль барьера, не позволяющего недоверенным образам попасть в среду выполнения.

В этой статье рассказывается, как Cosign, Kyverno и Harbor могут совместно обеспечить проверку подписей образов в Kubernetes. Подход хорошо подходит для корпоративных сред с приватными реестрами, собственными TLS-сертификатами и ограниченным доступом к публичным журналам прозрачности (transparency logs).

Зачем подписывать образы

Подпись образов даёт три фундаментальные гарантии безопасности:

  1. Целостность (Integrity)
    Подтверждает, что контейнерный образ не был изменён после сборки и подписания.

  2. Подлинность (Authenticity)
    Удостоверяет, что образ получен из доверенного источника, а не из неизвестного или скомпрометированного реестра.

  3. Принудительная проверка при развёртывании (Deployment-Time Enforcement)
    Предотвращает попадание неподписанных или неправильно подписанных образов в кластер Kubernetes.

В рассматриваемой конфигурации:

  • Cosign отвечает за подписание и проверку образов.

  • Harbor хранит контейнерные образы и соответствующие им подписи.

  • Kyverno выступает контроллером допуска (admission controller) Kubernetes и применяет правила проверки подписей.

Если образ не соответствует заданным критериям проверки, развёртывание блокируется ещё до того, как рабочая нагрузка достигает среды выполнения.

Архитектура на высоком уровне

Общая схема архитектуры решения

Процесс подписания и проверки образов выглядит следующим образом:

  1. Контейнерный образ собирается и отправляется в Harbor.

  2. Образ подписывается с помощью Cosign.

  3. Подпись сохраняется как OCI-артефакт в том же репозитории Harbor.

  4. В Kubernetes отправляется запрос на развёртывание.

  5. Kyverno перехватывает запрос на этапе допуска.

  6. Kyverno проверяет подпись образа с помощью публичного ключа Cosign.

  7. В кластере разрешается запускать только проверенные образы.

Таким образом, обеспечение безопасности цепочки поставок переносится непосредственно в плоскость управления (control plane) Kubernetes.

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

Необходимы следующие компоненты:

  • Работающий кластер Kubernetes

  • Приватный реестр Harbor с собственным удостоверяющим центром (CA)

  • Установленный Helm

  • Доступ к пространству имён kyverno

  • Настроенный kubectl с доступом к кластеру

Шаг 1: Генерация ключевой пары Cosign

Создайте ключевую пару Cosign:

cosign generate-key-pair

В результате будут созданы два файла:

  • cosign.key → приватный ключ (используется для подписания образов)

  • cosign.pub → публичный ключ (используется Kyverno для проверки)

Приватный ключ необходимо хранить в защищённом месте. Kubernetes/Kyverno использует только публичный ключ.

Шаг 2: Подписание образа с помощью Cosign

Прежде чем образ можно будет верифицировать, его необходимо подписать.

cosign sign --yes \
  --key cosign.key \
  --allow-insecure-registry \
  --registry-username <REGISTRY_USERNAME> \
  --registry-password <REGISTRY_PASSWORD> \
  --tlog-upload=false \
  <REGISTRY_HOST>/<PROJECT>/<IMAGE_NAME>:<IMAGE_TAG>

Важные замечания

  • --tlog-upload=false — отключает обращение к публичному Rekor, что полезно в изолированных сетях.

  • --allow-insecure-registry — часто необходим при использовании внутренних удостоверяющих центров или когда Harbor работает с нестандартной цепочкой сертификатов CA.

  • Cosign помещает подпись как OCI-артефакт рядом с образом в реестре.

Шаг 3: Экспорт корневого сертификата CA Harbor

Чтобы Kyverno мог проверять подписанные образы, он должен доверять TLS-сертификату Harbor.

Корневой CA Harbor можно извлечь следующей командой:

openssl s_client -showcerts \
  -connect <HARBOR_REGISTRY_HOST>:<PORT> </dev/null 2>/dev/null \
  | openssl x509 -outform PEM > <HARBOR_CA_FILE>.pem

Пример:

openssl s_client -showcerts \
  -connect registry.example.com:443 </dev/null 2>/dev/null \
  | openssl x509 -outform PEM > harbor-root-ca.pem

Команда генерирует сертификат CA в формате PEM, который впоследствии будет встроен в Kyverno.

Шаг 4: Установка Kyverno

Версия Helm-чарта и версия приложения — важное различие

Распространённая ошибка при установке Kyverno — попытка использовать версию приложения вместо версии Helm-чарта.

Например, следующая команда завершится ошибкой:

helm install kyverno kyverno/kyverno \
  --namespace kyverno --create-namespace \
  --version v1.16.0

Она не сработает, потому что Helm устанавливает версии чарта, а не версии приложения.

У Kyverno существуют два отдельных номера версии:

  • Chart Version — используется Helm

  • App Version — версия контроллера Kyverno, упакованного внутри чарта

Как найти правильную версию чарта

Выведите список всех доступных версий чарта Kyverno:

helm search repo kyverno -l

Вывод будет выглядеть примерно так:

CHART VERSION    APP VERSION
3.6.0            v1.16.0

Здесь:

  • 3.6.0 — версия Helm-чарта

  • v1.16.0 — версия приложения Kyverno внутри чарта

Правильная команда установки

Установите Kyverno, указав версию чарта:

helm install kyverno kyverno/kyverno \
  --namespace kyverno --create-namespace \
  --version 3.6.0

После этого Kyverno будет успешно установлен с версией приложения v1.16.0.

Проверка установки

kubectl get pods -n kyverno

Перед тем как продолжать, убедитесь, что все компоненты Kyverno находятся в состоянии Running.

Шаг 5: Настройка доверия к CA для Kyverno

Kyverno необходимо настроить доверие к:

  • Собственному CA Harbor

  • Публичному ключу Cosign, используемому для проверки подписей

Создание ConfigMap с набором CA-сертификатов

kubectl -n kyverno create configmap kyverno-ca-bundle \
  --from-file=ca-certificates.crt=harbor-ca.pem

Создание секретов для CA Harbor и публичного ключа Cosign

kubectl create secret generic harbor-ca \
  --from-file=ca.crt=harbor-ca.pem \
  -n kyverno
kubectl create secret generic cosign-pub \
  --from-file=cosign.pub=cosign.pub \
  -n kyverno

Шаг 6: Подключение CA через Helm

Рекомендуемый в продуктивной среде подход — внедрение доверенных сертификатов через Helm-значения (Helm values).

Создайте файл переопределения значений values-ca-bundle.yaml:

config:
  caBundle:
    enabled: true
    configMapName: kyverno-ca-bundle
    mountPath: /etc/ssl/certs

Обновите установку Kyverno:

helm upgrade kyverno kyverno/kyverno \
  -n kyverno \
  --version 3.6.0 \
  -f values-ca-bundle.yaml

Шаг 7: Ручное добавление CA Harbor в Kyverno (только для тестирования)

kubectl patch deployment kyverno-admission-controller -n kyverno \
  --type='json' \
  -p='[
    {
      "op": "add",
      "path": "/spec/template/spec/volumes/-",
      "value": {
        "name": "harbor-ca",
        "secret": {
          "secretName": "harbor-ca"
        }
      }
    },
    {
      "op": "add",
      "path": "/spec/template/spec/containers/0/volumeMounts/-",
      "value": {
        "name": "harbor-ca",
        "mountPath": "/etc/ssl/certs/harbor-ca.crt",
        "subPath": "ca.crt"
      }
    }
  ]'

Перезапустите Kyverno:

kubectl rollout restart deployment kyverno-admission-controller -n kyverno

Если Kyverno будет обновлён через Helm или Helm-чарт будет применён повторно, Helm перезапишет спецификацию Deployment тем, что определено в values.yaml.

В результате:

  • Ручной патч может исчезнуть без предупреждения

  • Смонтированный через патч CA может стать недоступен

  • После обновления проверка подписей образов может начать завершаться ошибками

Шаг 8: Принудительная проверка подписей Cosign с помощью Kyverno

Создайте Kyverno ClusterPolicy:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cosign-signatures  #<POLICY_NAME>
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-uat-images  #<RULE_NAME>
      match:
        resources:
          kinds:
            - Pod
          namespaces:
            - <TARGET_NAMESPACE>
      verifyImages:
        - imageReferences:
            - "<REGISTRY_HOST>/<PROJECT>/*"
          attestors:
            - entries:
                - keys:
                    secret:
                      name: cosign-pub
                      namespace: kyverno
          mutateDigest: false
          verifyDigest: false
          useCache: true

Важное замечание о validationFailureAction

Поле validationFailureAction определяет реакцию Kyverno при нарушении политики.

validationFailureAction: Enforce

Enforce — запрос блокируется, если образ не прошёл проверку.

Помимо этого, Kyverno поддерживает режим:

validationFailureAction: Audit

Audit — запрос не блокируется, но генерируются предупреждения.

Примените политику:

kubectl apply -f require-cosign-signatures.yaml

Перезапустите Kyverno:

kubectl rollout restart deployment kyverno-admission-controller -n kyverno

Ожидаемое поведение после включения политики

  • Неподписанные образы → развёртывание заблокировано

  • Образы, подписанные ненадёжным ключом → развёртывание заблокировано

  • Образы вне разрешённого диапазона реестров → развёртывание заблокировано

  • Корректно подписанные образы → развёртывание разрешено

Проверка происходит до создания пода, что исключает риски на этапе выполнения.

Рекомендации для продуктивной среды

1. Интеграция с CI/CD (Cosign в пайплайне)

  • Подписывайте образ в конце сборки — после прохождения тестов и сканирования.

  • Пример шага в GitHub Actions:

- name: Cosign Sign
  env:
    COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
  run: |
    cosign sign --yes \
      --key cosign.key \
      --registry-username "${{ secrets.REGISTRY_USER }}" \
      --registry-password "${{ secrets.REGISTRY_PASS }}" \
      --tlog-upload=false \
      $REGISTRY/$PROJECT/$IMAGE:$TAG

2. Пространства имён и мультиарендность (Multi-Tenancy)

  • Для изоляции арендаторов ограничивайте действие политик конкретными пространствами имён и путями реестров.

  • Если разные команды используют разные ключи, создайте несколько записей attestors или отдельные правила для каждой команды.

Заключение

Совместное использование Cosign, Harbor и Kyverno формирует надёжную и практичную модель безопасности цепочки поставок для Kubernetes. Такая конфигурация гарантирует:

  • Криптографическое подписание контейнерных образов

  • Допуск в кластер только доверенных образов

  • Полную поддержку приватных реестров с собственными CA

  • Автоматическое применение политик безопасности на этапе допуска

Этот подход существенно снижает риск запуска несанкционированных или скомпрометированных рабочих нагрузок в чувствительных средах Kubernetes.

© 2026 meganuke