Диагностика cosign в Kaspersky Container Security#
Подпись, которую никто не проверяет, ничем не лучше её отсутствия.
Подпись cosign подтверждает, что в кластер попадает ровно тот образ, который собрали и подписали в CI. Первое включение такой проверки в продуктиве часто выглядит одинаково. Образ подписан, cosign verify на рабочей станции проходит успешно, а политика среды выполнения блокирует под с сообщением «ограничено из-за ошибки при проверке подписи».
На ум приходит простое объяснение: раз cosign подпись принимает, ошибка в решении. Но здесь кроется ловушка.
Почему «на хосте работает» ничего не доказывает#
cosign на рабочей станции и агент защиты кластера (kube-agent) проверяют одну и ту же подпись в разных условиях.
| Что использует проверка | cosign на рабочей станции | kube-agent в кластере |
|---|---|---|
| Учётные данные реестра | ~/.docker/config.json пользователя | Секрет из интеграции и imagePullSecrets пода |
| Доверенные сертификаты | Системное хранилище, где уже есть корпоративный УЦ | Только сертификат из поля «Сертификат» интеграции |
| Сеть | Прямой доступ, VPN, прокси рабочей станции | Сетевые политики и прокси пространства имён агента |
| Адрес образа | Тот, что набран в команде | Тот, что записан в манифесте пода |
| Формат подписи | Любой, который понимает установленная версия cosign | Классический тег sha256-<дайджест>.sig; поддержка формата bundle из cosign 3.x в документации не описана |
| Способ подписи | Ключ, keyless (OIDC), KMS | Только открытый ключ ECDSA или RSA |
Каждое из этих различий может изменить результат проверки. Какой из этого следует вывод? Искать причину нужно там, где проверка выполняется, то есть внутри кластера, а не повторять cosign verify на своей машине.
В статье рассмотрим диагностику проверки подписей cosign в Kaspersky Container Security 2.5: где записана настоящая причина отказа, как повторить проверку в условиях агента и какие ошибки встречаются чаще всего.
Как устроена проверка подписи#
Проверка настраивается в двух местах Консоли управления:
- Администрирование → Интеграции → Модули проверки подписей образов. Здесь создаётся интеграция типа Cosign: секрет для доступа к реестру, сертификат, доверенные корневые ключи (до 20 пар из имени и значения ключа), порог подписания и обязательные владельцы подписи.
- Политики → Среда выполнения В политике включается блок «Защита содержания образа»: шаблон URL реестра, действие «Проверять» и модуль проверки подписей.
При развёртывании пода проверка проходит так:
- Оркестратор через динамический контроллер доступа (admission webhook) передаёт запрос на создание пода агенту kube-agent.
- kube-agent находит политику среды выполнения, в которой шаблон URL реестра совпадает с адресом образа.
- kube-agent определяет дайджест образа, используя
imagePullSecretsпода. - kube-agent читает секрет из интеграции, загружает из реестра подпись cosign и проверяет её доверенными ключами.
- Если действительных подписей не меньше порога и среди них есть все обязательные владельцы, под запускается. Иначе в режиме «Блокирование» запуск запрещается.
[схема: API-сервер → admission webhook → kube-agent → реестр (дайджест, подпись) → «разрешить / заблокировать»]
Решение проверяет только подписи, созданные ключом: открытая часть ECDSA или RSA из cosign.pub. Подписи без ключа (keyless, через OIDC и Fulcio) и ссылки на ключи во внешних KMS (awskms://, hashivault://, k8s://) в интеграции не настраиваются.
В примерах ниже используются обозначения:
| Обозначение | Значение |
|---|---|
kcs-agents | Пространство имён агентов, как в примере установки из документации. Замените на своё |
registry.example.com/app/web:1.0 | Проверяемый образ |
cosign-secret | Секрет с учётными данными реестра, указанный в интеграции |
<app-ns> | Пространство имён приложения |
Этап 1. Найти точную причину в журнале kube-agent#
Сообщение в Консоли управления говорит только о срабатывании контроля. Подробная причина, включая код выхода cosign, записывается в журнал kube-agent.
Воспроизведите развёртывание и сразу соберите журнал со всех подов агента:
for p in $(kubectl -n kcs-agents get pods -o name | grep kube-agent); do
echo "== $p"
kubectl -n kcs-agents logs "$p" --since=15m \
| grep -E 'cosign-validator|validate-sign-check|get-image-digest|validation finished'
doneУспешная проверка выглядит так:
[INFO][kube-agent][policy][validate-sign-check-policies] registry.example.com pattern matches image: registry.example.com/app/web:1.0
[INFO][kube-agent][cosign-validator][new-cosign-validator] get secret cosign-secret from namespace kcs-agents
[INFO][kube-agent][policy][get-image-digest][secret-name:regcred] secret added to keychain
[INFO][kube-agent][policy] validation finished. valid? trueНеуспешная:
[ERROR][kube-agent][cosign-validator][verify-step][error:exit status 10][step-name:required][verify-count:1] get validated digest failed
[ERROR][kube-agent][policy][validate-sign-check-policies][error:cosign: image not compliant with validation policy (threshold of required signers not reached):
exit status 10] sign validator errors: ...Первые три строки успешного примера служат контрольными точками: pattern matches image, get secret и get-image-digest. Если одной из них нет или в ней не то значение, место отказа найдено.
| Строка журнала | Что она говорит |
|---|---|
pattern matches image: <образ> | Под какой шаблон попал образ и какой адрес реально проверяется |
get secret <имя> from namespace <ns> | Какой секрет интеграции и из какого пространства имён читает агент |
get-image-digest][secret-name:<имя>] | Удалось ли получить дайджест; <имя> здесь секрет из imagePullSecrets пода |
threshold of required signers not reached | Подпись не найдена или не подошла ни к одному ключу |
x509, certificate signed by unknown authority | Агент не доверяет TLS-сертификату реестра |
unauthorized, 401, 403, DENIED | Неверные учётные данные или секрет |
timeout, connection refused, no such host | Агент не может связаться с реестром |
Какая политика заблокировала под, показывает сообщение контроллера доступа. Для пода, созданного напрямую, его выводит kubectl apply:
Error from server: admission webhook "kube-agent.kcs-agents.svc.cluster.local" denied the request: Pod init blocked by KCS runtime policy: [ <имя политики> ]Для Deployment и StatefulSet поды не создаются, и сообщение нужно искать в событиях:
kubectl -n <app-ns> describe rs | grep -A3 'admission webhook'
kubectl -n <app-ns> get events --sort-by=.lastTimestamp | grep -i webhookНа время диагностики стоит перевести политику в режим «Аудит». Поды запускаются, а журнал и события продолжают записываться. После исправления можно вернуть режим «Блокирование».
Этап 2. Проверить настройки интеграции и политики#
Интеграция (Администрирование → Интеграции → Модули проверки подписей образов):

- Тип: Cosign.
- Секрет для аутентификации на сервере подписей: существующий секрет типа
kubernetes.io/dockerconfigjson. Документация требует разместить его в пространстве имён Kaspersky Container Security. Из какого пространства имён агент читает секрет, показывает строку журналаget secret … from namespace …. Если секрета там нет, создайте его в этом пространстве имён.Ключ вkubectl -n kcs-agents get secret cosign-secret -o jsonpath='{.type}{"\n"}' kubectl -n kcs-agents get secret cosign-secret \ -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d; echoauthsдолжен совпадать с хостом и портом из имени образа. Дляregistry.example.com:5000/app/webэтоregistry.example.com:5000, для Docker Hubhttps://index.docker.io/v1/. - Сертификат: сертификат УЦ, выпустившего TLS-сертификат реестра, в формате PEM. При промежуточных УЦ укажите всю цепочку. Для реестра по HTTP оставьте поле пустым.
- Доверенные корневые ключи: полное содержимое
cosign.pubсо строкамиBEGINиEND, без символов\r. Соответствие закрытому ключу можно проверить так:cosign public-key --key cosign.key | diff - cosign.pub && echo "ключи совпадают" - Порог подписания: не больше числа ключей, которыми образ подписан на самом деле. Для одного ключа укажите
1. - Обязательные владельцы подписи: только имена ключей, которыми образ действительно подписан.
Политика (Политики → Среда выполнения → Политики):
- Политика включена, область применения охватывает нужный кластер и пространство имён.
- Блок «Защита содержания образа» включён, выбрано «Проверять» и нужный модуль проверки подписей.
- Шаблон URL реестра образов совпадает с адресом образа в том виде, в каком он записан в манифесте.
nginxв манифесте означаетdocker.io/library/nginx. - Образ не попадает одновременно под другое правило проверки подписей с другим модулем.
В манифесте пода должны быть imagePullSecrets с доступом к реестру, иначе агент не получит дайджест образа.
Этап 3. Проверить образ и подпись на рабочей станции#
IMG=registry.example.com/app/web:1.0
cosign version
cosign triangulate "$IMG" # адрес, по которому cosign ищет подпись
docker buildx imagetools inspect "$IMG" | grep -m1 Digest
cosign tree "$IMG" # какие артефакты подписи привязаны к образу
cosign verify --key cosign.pub --insecure-ignore-tlog=true "$IMG"Сверьте следующее:
- Способ подписи. Если
cosign verifyу вас работает с--certificate-identity,--certificate-oidc-issuerили ключом видаawskms://…, образ подписан не файловым ключом, и решение такую подпись не проверит. - Формат подписи.
cosign treeпоказывает, где лежит подпись: в тегеsha256-<дайджест>.sigили в связанном артефакте (OCI referrer, Sigstore bundle). - Дайджест. Дайджест из
imagetools inspectдолжен совпадать с подписанным и с дайджестом в журнале kube-agent. - Переменная
COSIGN_REPOSITORY. Если её задали при подписании, подписи лежат в другом репозитории. Проверить можно командойenv | grep COSIGN_.
Этап 4. Повторить проверку изнутри кластера#
Запускаем cosign в пространстве имён агента с тем же секретом, сертификатом и ключом, что указаны в интеграции. Результат делит проблемы на две группы: окружение (сеть, учётные данные, TLS, зеркала) или сама подпись и настройки решения.
Если у кластера нет доступа в интернет, заранее скопируйте образ ghcr.io/sigstore/cosign/cosign:v2.4.1 во внутренний реестр. В манифесте ниже указана именно такая копия. Ключ и сертификат положите в ConfigMap:
kubectl -n kcs-agents create configmap cosign-debug \
--from-file=cosign.pub \
--from-file=ca.crt=registry-ca.pem # не нужно для реестра по HTTPСохраните манифест в cosign-debug.yaml:
apiVersion: v1
kind: Pod
metadata:
name: cosign-debug
namespace: kcs-agents
spec:
restartPolicy: Never
containers:
- name: cosign
image: registry.example.com/sigstore/cosign:v2.4.1
args:
- verify
- --key=/cfg/cosign.pub
- --insecure-ignore-tlog=true
# для реестра по HTTP раскомментируйте:
# - --allow-http-registry=true
# - --allow-insecure-registry=true
- registry.example.com/app/web:1.0
env:
- name: DOCKER_CONFIG
value: /docker
- name: SSL_CERT_FILE # для реестра по HTTP удалите
value: /cfg/ca.crt
volumeMounts:
- { name: auth, mountPath: /docker, readOnly: true }
- { name: cfg, mountPath: /cfg, readOnly: true }
volumes:
- name: auth
secret:
secretName: cosign-secret
items:
- { key: .dockerconfigjson, path: config.json }
- name: cfg
configMap:
name: cosign-debugЗапустите проверку, посмотрите вывод и удалите временные объекты:
kubectl apply -f cosign-debug.yaml
kubectl -n kcs-agents logs -f cosign-debug
kubectl -n kcs-agents delete pod cosign-debug
kubectl -n kcs-agents delete configmap cosign-debugЕсли политика среды выполнения распространяется на kcs-agents, она может заблокировать и этот под. На время проверки переведите её в режим «Аудит».
| Результат в кластере | Вывод | Причины ниже |
|---|---|---|
| Ошибка доступа, TLS или сети | Проблема в окружении кластера, а не в подписи | 3, 5, 6, 9 |
no signatures found | По этому адресу подписи нет | 1, 3, 4 |
| Проверка успешна, а решение блокирует под | Окружение в порядке, расхождение в формате подписи или настройках интеграции | 1, 7, 8 |
Типовые причины и решения#
Сводка, чтобы сразу перейти к нужному разделу:
| # | Причина | Признак | Где видно |
|---|---|---|---|
| 1 | Формат подписи cosign 3.x | Нет тега .sig, подпись в связанном артефакте | Этап 3, cosign tree |
| 2 | Keyless или KMS | --certificate-identity, awskms:// в командах | Этап 3 |
| 3 | Зеркало или прокси реестра | В pattern matches image исходный адрес, а узлы тянут с зеркала | Этап 1 |
| 4 | Подписан другой дайджест | Дайджесты в журнале и в подписи различаются | Этапы 1 и 3 |
| 5 | Учётные данные реестра | 401, 403, unauthorized | Этапы 1 и 4 |
| 6 | TLS-сертификат | x509: certificate signed by unknown authority | Этапы 1 и 4 |
| 7 | Открытый ключ | threshold … not reached, а под cosign-debug проходит | Этапы 1 и 4 |
| 8 | Порог и обязательные владельцы | threshold … not reached, настройки интеграции не совпадают с подписями | Этапы 1 и 2 |
| 9 | Сетевой доступ агента | timeout, no such host | Этапы 1 и 4 |
Причина 1. Формат подписи cosign 3.x#
Признаки. Используется cosign 3.x, cosign tree показывает подпись как связанный артефакт, тега sha256-<дайджест>.sig в репозитории нет. На рабочей станции проверка проходит.
cosign 3.0 по умолчанию сохраняет подпись в новом формате Sigstore bundle как связанный артефакт OCI 1.1 (referrer), а не в отдельном теге. Поддержку этого формата модулем проверки в документации Kaspersky Container Security 2.5 мы не нашли, поэтому эту причину стоит исключить первой.
Решение. Подпишите образ в классическом формате:
cosign sign --key cosign.key \
--new-bundle-format=false \
--use-signing-config=false \
registry.example.com/app/web@sha256:<дайджест>Либо подпишите образ утилитой cosign 2.x. Если после этого под запустился, причина найдена.
Причина 2. Подпись без ключа или ключ во внешнем KMS#
Признаки. Для проверки используются --certificate-identity, --certificate-oidc-issuer или ключ вида awskms://, gcpkms://, azurekms://, hashivault://, k8s://.
Решение. Подпишите образ файловым ключом:
cosign generate-key-pair
cosign sign --key cosign.key registry.example.com/app/web@sha256:<дайджест>Если ключ должен остаться в KMS, выгрузите его открытую часть командой cosign public-key --key <kms-uri> и укажите её в интеграции.
Причина 3. Зеркало или прокси реестра#
Признаки. Узлы получают образы через зеркало (hosts.toml в containerd, registries.conf, Nexus или Harbor в режиме proxy), а в манифесте указан исходный адрес вроде docker.io/…. Строка pattern matches image: в журнале содержит исходный адрес.
Зеркало подставляет containerd на узле. kube-agent о нём не знает и ищет подпись по адресу из манифеста. Кроме того, прокси-реестр может не копировать теги .sig.
Решение. Указывайте в манифесте адрес внутреннего реестра, в котором образ подписан. При репликации между реестрами переносите подписи вместе с образом: cosign copy <источник> <назначение>.
Причина 4. Подписан другой дайджест#
Признаки. Дайджест в журнале kube-agent отличается от подписанного. Обычно это одна из двух ситуаций:
- образ пересобрали и опубликовали с тем же тегом уже после подписания;
- подписан мультиархитектурный индекс, а в манифесте дайджест образа конкретной платформы (или наоборот).
Решение. Подписывайте образ по дайджесту после финальной публикации и развёртывайте по той же ссылке:
DIGEST=$(docker buildx imagetools inspect registry.example.com/app/web:1.0 \
--format '{{json .Manifest.Digest}}' | tr -d '"')
cosign sign --key cosign.key "registry.example.com/app/web@${DIGEST}"Причина 5. Учётные данные реестра#
Признаки. В журнале unauthorized, 401, 403, DENIED или ошибка рядом со строкой get secret.
Решение. Пересоздайте секрет в пространстве имён из строки get secret … from namespace …. Значение --docker-server должно точно совпадать с хостом и портом реестра:
kubectl -n kcs-agents create secret docker-registry cosign-secret \
--docker-server=registry.example.com:5000 \
--docker-username=<пользователь> \
--docker-password=<пароль>Учётной записи нужно право чтения репозитория, включая теги .sig. У пода приложения должны быть imagePullSecrets для того же реестра.
Причина 6. TLS-сертификат реестра#
Признаки. В журнале x509: certificate signed by unknown authority. На рабочей станции всё работает, потому что корпоративный УЦ установлен в систему.
Решение. Укажите в поле «Сертификат» интеграции сертификат УЦ в формате PEM, при промежуточных УЦ всю цепочку. Цепочку, которую отдаёт реестр, можно получить так:
openssl s_client -connect registry.example.com:443 -showcerts </dev/null 2>/dev/null \
| sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p'Причина 7. Открытый ключ#
Признаки. В журнале threshold of required signers not reached, при этом подпись в реестре есть и cosign verify с тем же cosign.pub проходит, в том числе из пода cosign-debug.
Решение. Вставьте cosign.pub целиком, вместе со строками BEGIN и END. Если файл редактировали в Windows, уберите символы \r: tr -d '\r' < cosign.pub > cosign-unix.pub. Убедитесь, что ключ сгенерирован по алгоритму ECDSA или RSA.
Причина 8. Порог подписания и обязательные владельцы#
Признаки. Та же строка threshold of required signers not reached. Порог больше числа ключей, которыми подписан образ, или в обязательных владельцах указан ключ, которым образ не подписан.
Решение. Для одного ключа установите порог 1 и укажите имя этого ключа в обязательных владельцах. Если подписантов несколько, подпишите образ каждым ключом:
cosign sign --key team-a.key registry.example.com/app/web@sha256:<дайджест>
cosign sign --key team-b.key registry.example.com/app/web@sha256:<дайджест>Причина 9. Сетевой доступ агента#
Признаки. В журнале timeout, connection refused или no such host. Под cosign-debug завершается с той же ошибкой.
Решение.
- Проверьте NetworkPolicy в пространстве имён агента: нужен исходящий трафик к реестру и к DNS.
- Если выход в сеть идёт через прокси, проверьте переменные
HTTP_PROXY,HTTPS_PROXYиNO_PROXYу kube-agent. - Проверьте, что имя реестра разрешается из кластера:
kubectl -n kcs-agents run dns-test --rm -it --restart=Never --image=busybox -- nslookup registry.example.com
Ограничения#
- В Консоли управления видно только срабатывание контроля. Текст ошибки cosign есть лишь в журнале kube-agent, поэтому без доступа к журналам агента диагностика сводится к перебору гипотез.
- Решение проверяет подписи ключом ECDSA или RSA. Keyless-подписи и ключи во внешних KMS напрямую не проверяются.
- Совместимость модуля проверки с форматом Sigstore bundle из cosign 3.x документацией не описана. Совет подписывать образы в классическом формате основан на диагностике, а не на официальной матрице совместимости.
- Поведение проверки для подписей с записью в журнал прозрачности (Rekor) и без неё документацией тоже не описано. Для закрытых контуров проверьте его на стенде.
- Документация размещает секрет интеграции в пространстве имён Kaspersky Container Security. В инсталляциях, где агенты развёрнуты в отдельном пространстве имён, ориентируйтесь на строку журнала
get secret … from namespace ….
Что отправить в техническую поддержку#
Если причина не нашлась, соберите:
- Версию Kaspersky Container Security, тип и версию оркестратора.
- Журнал kube-agent за период воспроизведения.
- Сообщение admission webhook из
kubectl applyилиkubectl describe rs. - Вывод
cosign versionи точные командыcosign signиcosign verify. - Вывод
cosign treeиcosign triangulateдля образа. - Вывод пода
cosign-debug. - Снимки экрана настроек интеграции и блока «Защита содержания образа» в политике.
- Сведения о зеркалах или прокси реестров, если они используются.
Перед отправкой удалите из файлов пароли, токены и содержимое секретов. Закрытый ключ cosign.key не передавайте.
Заключение#
Проверка подписей в кластере ломается не там, где её обычно перепроверяют. cosign на рабочей станции видит свои учётные данные, свои сертификаты и свою сеть, а kube-agent видит только то, что настроено в интеграции и доступно из его пространства имён. Поэтому порядок диагностики всегда один: журнал kube-agent, настройки интеграции, образ и подпись, повтор проверки изнутри кластера.
Такой порядок превращает «ограничено из-за ошибки при проверке подписи» в конкретную причину, а режим «Аудит» позволяет разобраться без остановки приложений. Главное не отключать проверку «на время»: подпись без проверки защищает только от тех, кто и не собирался подменять образ.