Введение
Защита цепочки поставок контейнеров стала базовым требованием для производственных кластеров Kubernetes. По мере роста кластеров и увеличения числа команд, развёртывающих рабочие нагрузки, контроль на этапе допуска (admission-time controls) выполняет роль барьера, не позволяющего недоверенным образам попасть в среду выполнения.
В этой статье рассказывается, как Cosign, Kyverno и Harbor могут совместно обеспечить проверку подписей образов в Kubernetes. Подход хорошо подходит для корпоративных сред с приватными реестрами, собственными TLS-сертификатами и ограниченным доступом к публичным журналам прозрачности (transparency logs).
Зачем подписывать образы
Подпись образов даёт три фундаментальные гарантии безопасности:
-
Целостность (Integrity)
Подтверждает, что контейнерный образ не был изменён после сборки и подписания. -
Подлинность (Authenticity)
Удостоверяет, что образ получен из доверенного источника, а не из неизвестного или скомпрометированного реестра. -
Принудительная проверка при развёртывании (Deployment-Time Enforcement)
Предотвращает попадание неподписанных или неправильно подписанных образов в кластер Kubernetes.
В рассматриваемой конфигурации:
-
Cosign отвечает за подписание и проверку образов.
-
Harbor хранит контейнерные образы и соответствующие им подписи.
-
Kyverno выступает контроллером допуска (admission controller) Kubernetes и применяет правила проверки подписей.
Если образ не соответствует заданным критериям проверки, развёртывание блокируется ещё до того, как рабочая нагрузка достигает среды выполнения.
Архитектура на высоком уровне
Процесс подписания и проверки образов выглядит следующим образом:
-
Контейнерный образ собирается и отправляется в Harbor.
-
Образ подписывается с помощью Cosign.
-
Подпись сохраняется как OCI-артефакт в том же репозитории Harbor.
-
В Kubernetes отправляется запрос на развёртывание.
-
Kyverno перехватывает запрос на этапе допуска.
-
Kyverno проверяет подпись образа с помощью публичного ключа Cosign.
-
В кластере разрешается запускать только проверенные образы.
Таким образом, обеспечение безопасности цепочки поставок переносится непосредственно в плоскость управления (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.