Cosign в 2026: keyless-подпись контейнеров через GitHub Actions и проверка в Kubernetes
Как подписывать контейнеры без приватных ключей: Cosign v2.4 keyless в GitHub Actions, проверка через Kyverno в Kubernetes, SLSA-provenance и SBOM-attestations. Практический пример workflow и типичные ошибки.
Cosign keyless — это способ подписать OCI-образ контейнера без хранения приватных ключей. Конвейер получает короткоживущий сертификат от Fulcio на основе OIDC-токена GitHub Actions, факт подписи фиксируется в прозрачном логе Rekor, а Kubernetes через Kyverno проверяет цепочку до допуска пода. В 2026 году это стандартный шаблон supply-chain-защиты для CNCF-стека, и Cosign v2.4 работает keyless по умолчанию. Ниже разберём рабочий конвейер от docker build до admission-политики, без размахивания приватными cosign.key.
Cosign v2 подписывает keyless через OIDC-токен GitHub Actions, Fulcio выдаёт сертификат на 10 минут, приватный ключ никуда не сохраняется.
Каждая подпись публикуется в Rekor (прозрачном append-only логе), что даёт верифицируемую историю без доверия к моей CI-машине.
Проверка в Kubernetes реализуется через Kyverno verifyImages, где политика фиксирует ожидаемого OIDC-issuer и subject workflow.
Cosign умеет прикреплять SBOM и SLSA-attestations как отдельные OCI-артефакты, привязанные к digest, а не к тегу.
Подписи по тегу небезопасны: всегда проверяю образ по sha256:-digest, чтобы исключить mutable-tag атаки.
Notation от CNCF решает похожую задачу, но требует X.509-цепочку и удобнее там, где уже развёрнута корпоративная PKI.
Что такое Cosign и Sigstore
Cosign — это CLI-утилита из проекта Sigstore, которая подписывает и проверяет OCI-артефакты: контейнерные образы, Helm-чарты, SBOM, WASM-модули и произвольные blob'ы. Сам Sigstore при этом не одна утилита, а связка из трёх компонентов. Fulcio выдаёт короткоживущие сертификаты X.509 по OIDC-идентити, Rekor публикует прозрачный append-only лог всех подписей, а Cosign склеивает всё это в один cosign sign. Такой подход убирает главную боль классических PKI, то есть управление приватными ключами в CI.
В 2026 году Sigstore это graduated-проект CNCF, а keyless-подпись поддерживают GitHub Actions, GitLab CI, Buildkite и любые OIDC-совместимые провайдеры. У меня в командах Cosign чаще всего живёт рядом с Trivy для сканирования уязвимостей и генерации SBOM. Схема простая: сначала сканируем, потом подписываем, потом прикрепляем SBOM как attestation. На выходе получаем образ с проверяемой историей от коммита до реестра.
Ключевая метафора: Cosign — это ssh-keygen для контейнеров, только без необходимости беречь id_ed25519. Всё, что вам нужно, это доказать провайдеру идентити (GitHub, Google, что угодно с OIDC), а Fulcio сам выпишет сертификат на пару минут.
Как работает keyless-подпись через OIDC
Механика keyless-подписи выглядит так. Ваш CI-раннер запрашивает у OIDC-провайдера JWT-токен с claim'ами вроде iss=https://token.actions.githubusercontent.com и sub=repo:acme/api:ref:refs/heads/main. Cosign генерирует одноразовую пару ключей ephemeral в памяти, отправляет публичную часть плюс OIDC-токен в Fulcio, а Fulcio возвращает X.509-сертификат, где расширения содержат issuer и subject из токена. Сертификат живёт около 10 минут. Как раз достаточно, чтобы подписать артефакт.
Подпись, публичный ключ и сертификат пушатся в OCI-реестр рядом с образом (обычно как объекты вида sha256-...sig), а хеш подписи регистрируется в Rekor, публичном логе, работающем через Merkle tree. Это даёт то, ради чего всё затевалось: даже если завтра кто-то скомпрометирует раннер, вы сможете доказать, что подпись уже была в Rekor на конкретный момент времени, потому что лог неизменяем.
Приватный ключ уничтожается сразу после подписи, и это принципиально. Нет ключа, нет KMS, нет секрета в CI, нет ротации. Есть только identity вашего workflow. Именно поэтому проверяющая сторона (Kyverno, admission controller, локальный cosign verify) должна валидировать не «принадлежит ли подпись такому-то ключу», а «выписан ли сертификат ожидаемому issuer'у на ожидаемый subject».
Cosign распространяется как статический бинарь для Linux, macOS и Windows. На Ubuntu 24.04, Debian 12 и Rocky 9 я обычно ставлю фиксированную версию из GitHub Releases, потому что подгружать «latest» в CI, честно говоря, плохая идея. Любой релиз завтра может сломать пайплайн. Актуальная стабильная линия на середину 2026 это v2.4.x, где keyless включён по умолчанию и убраны предупреждения --yes для non-interactive окружений.
# Установка Cosign v2.4.1 на Linux x86_64
COSIGN_VERSION="v2.4.1"
curl -sSLo /usr/local/bin/cosign \
"https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64"
chmod +x /usr/local/bin/cosign
# Проверяем подпись самого cosign. Рекурсия, но по делу
curl -sSLo cosign.sig \
"https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64.sig"
curl -sSLo cosign.pub \
"https://raw.githubusercontent.com/sigstore/cosign/main/release/release-cosign.pub"
cosign verify-blob --key cosign.pub --signature cosign.sig /usr/local/bin/cosign
cosign version --json | jq '.GitVersion'
# ожидаем: "v2.4.1"
Для локальной разработки этого достаточно. Для запуска в контейнере CI используйте официальный образ ghcr.io/sigstore/cosign/cosign:v2.4.1. Он весит около 40 МБ и не тащит с собой Docker-daemon. Я держу его закреплённым по digest, а не по тегу, ровно по тем же причинам, по которым проверяю production-образы по digest.
Подпись образов в GitHub Actions без ключей
Это самый частый сценарий и та часть, ради которой обычно и приходят к Sigstore. Ниже приведён полный workflow, который собирает образ, пушит в GHCR, подписывает keyless и прикрепляет SBOM. Обратите внимание на блок permissions. Без id-token: write раннер не сможет запросить OIDC-токен, и cosign sign упадёт с непонятной ошибкой. Я на этом залипал часа полтора в первый раз.
Пара вещей, которые я на своей шкуре понял. Первое: всегда подписывайте образ по digest, а не по тегу. Тег в OCI мутируемый, кто-то с правами push может переклеить его на другой образ, и ваша подпись «поедет» вместе с тегом на новый digest, где вас не спрашивали. Второе: --yes нужен именно потому, что в CI никто не подтвердит интерактивный prompt. Третье: attestations прикрепляются к digest, поэтому они автоматически «следуют» за правильным образом.
Как проверить подпись keyless-образа
Проверка это половина работы. Подпись без проверки бесполезна, потому что злоумышленник может залить свой образ и «подписать» его от своего репозитория. Если вы не проверяете issuer и subject, вы примете такую подпись за легитимную. Правильная команда cosign verify явно фиксирует ожидаемый OIDC-issuer и regexp по subject.
Из практики: если вы промахнулись с regexp в certificate-identity-regexp, Cosign вернёт скупое «no matching signatures». Это не значит, что подписи нет. Просто ни одна из существующих не удовлетворяет вашей политике. Полезный дебаг-приём: сначала запустить cosign download signature IMAGE, распарсить сертификат через openssl x509 -in cert.pem -text и посмотреть, что реально стоит в SAN. Честно говоря, у меня половина инцидентов «не работает подпись» решается сравнением реального subject с regexp'ом.
# Быстрый CI-шаг: fail-fast если образ не подписан из ожидаемого репозитория
- name: Verify image before deploy
run: |
cosign verify \
--certificate-identity-regexp "^https://github.com/${{ github.repository }}/.+" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"${REGISTRY}/${IMAGE_NAME}@${DIGEST}" \
|| { echo "::error::image is not signed by trusted workflow"; exit 1; }
Проверка в Kubernetes через Kyverno
Проверять на этапе CI полезно, но недостаточно. Рано или поздно кто-то запустит образ мимо вашего пайплайна. Правильный ответ, это admission-контроллер, который отбрасывает поды без валидной подписи. В 2026 году канонический выбор это Kyverno с политикой verifyImages. Она поддерживает Cosign keyless из коробки, работает с Rekor, а правила пишутся в чистом YAML без Rego.
Флаг mutateDigest: true, честно говоря, моя любимая деталь. Kyverno при допуске переписывает image: ghcr.io/acme/api:v1.2.3 в image: ghcr.io/acme/api@sha256:..., фиксируя ровно тот digest, который был проверен. Это ловит классическую атаку «подменили тег между admission и pull». Без mutateDigest kubelet мог бы скачать уже другой образ.
Тест политики без риска для production это validationFailureAction: Audit. Kyverno будет писать нарушения в события, но не блокировать. Я оставляю Audit на 1–2 недели, вычищаю все не подписанные образы (обычно это утилиты в kube-system и пара забытых side-car'ов), потом переключаю в Enforce.
Cosign против Notation: сравнительная таблица
Cosign не единственный игрок. CNCF также развивает Notation (бывший Notary v2), который решает похожую задачу, но с другими компромиссами. Ниже сравнение по параметрам, которые обычно интересуют DevSecOps-команду при выборе.
Критерий
Cosign (Sigstore)
Notation (CNCF)
Модель ключей
Keyless через OIDC + Fulcio, либо статический ключ
Только X.509-цепочка, требует управляемой PKI
Прозрачный лог
Rekor (публичный или self-hosted)
Опционально, через OCI 1.1 referrers
Attestations (SBOM, SLSA)
Первоклассная поддержка, cosign attest
Через отдельные проекты (например, ORAS)
Проверка в Kubernetes
Kyverno, Sigstore Policy Controller, Connaisseur
Ratify (CNCF), Notation Verifier
Кривая обучения
Низкая для keyless, высокая для offline-KMS
Средняя, но требует PKI-экспертизы
Соответствие compliance
FIPS-режим с v2.2+, но keyless сложнее аудировать
Лучше для регулируемых сред с внутренней PKI
Экосистема 2026
Доминирует в open-source, поддержка в GitHub, GitLab, ArgoCD
Активно в enterprise, Azure Container Registry, Docker Scout
Прагматичная рекомендация. Если вы в CNCF-стеке, используете GitHub Actions или GitLab, и у вас нет корпоративной PKI, берите Cosign. Если у вас Enterprise-инфраструктура с HSM, внутренним CA и требованиями регуляторов, где keyless-модель звучит подозрительно, смотрите Notation. Смешивать в одном реестре можно, но админить два discovery-механизма, честно, так себе удовольствие.
SLSA-provenance и attestations
SLSA (Supply-chain Levels for Software Artifacts) это уровневая модель supply-chain-зрелости от OpenSSF. Cosign выступает в ней как транспорт для двух вещей: SBOM-attestation (что внутри образа) и provenance-attestation (как именно образ был собран, какой коммит, какой билдер, какие входы). SLSA v1.0 требует, чтобы provenance-документ был подписан билдером, и Sigstore keyless как раз это и даёт бесплатно.
После этого рядом с образом лежат три вещи: *.sig (подпись), *.att с типом spdxjson (SBOM) и *.att с типом slsaprovenance (кто собрал). Kyverno-политика может требовать одновременно и подпись, и SBOM, и SLSA v1.0 provenance. Тогда в production не попадёт ни один образ, о происхождении которого мы не знаем всё.
За несколько десятков внедрений Cosign я собрал коллекцию грабель, на которых стабильно спотыкается новая команда. Разложу по частотности.
1. Забытый id-token: write в workflow
Симптом: cosign sign падает с Error: getting signer: getting key from Fulcio: no OIDC token. Причина: GitHub не выдаёт OIDC-токен джобу, где не разрешён id-token. Лечится добавлением permissions на уровне job или workflow.
2. Подпись по тегу вместо digest
Симптом: сегодня cosign verify ghcr.io/acme/api:latest работает, завтра нет. Причина: тег переписан на новый push. Решение: всегда подписывайте и проверяйте по image@sha256:.... Buildx возвращает digest в steps.build.outputs.digest, оттуда и берите.
3. Слишком широкий regexp по subject
Регулярка .* в certificate-identity-regexp это фактически «принять подпись от кого угодно, кто прошёл через Fulcio». А через Fulcio может пройти кто угодно с валидным GitHub-аккаунтом. Всегда фиксируйте организацию и репозиторий, минимум до ^https://github.com/acme/.+.
4. Игнорирование шума от сканеров
Trivy и Grype любят подсвечивать CVE-шум в базовом образе, где реально ничего не эксплуатируется. Не смешивайте это с политикой подписей: подпись говорит «это наш образ», а не «в нём нет уязвимостей». Держите две ортогональные политики, одну для сканирования, вторую для подписи, иначе PR-ы будут блокироваться по несвязанным причинам.
5. Отсутствие break-glass процедуры
Kyverno в Enforce может заблокировать hotfix, если Fulcio или Rekor лежат (такое бывает раз в год). Держите заранее подписанные emergency-образы или namespace-исключение, документированное в runbook.
# pre-commit хук: не даём коммитить workflow без id-token: write в jobs, использующих cosign
# .git/hooks/pre-commit (упрощённо)
#!/usr/bin/env bash
if git diff --cached --name-only | grep -q '\.github/workflows/.*\.yml$'; then
for f in $(git diff --cached --name-only | grep '\.github/workflows/.*\.yml$'); do
if grep -q 'cosign sign' "$f" && ! grep -q 'id-token: write' "$f"; then
echo "ERROR: $f uses cosign sign but lacks 'id-token: write' permission" >&2
exit 1
fi
done
fi
Часто задаваемые вопросы
Насколько безопасен keyless-подпись без приватных ключей?
Безопаснее, чем хранить приватный ключ в CI-секретах, при условии, что вы жёстко проверяете issuer и subject сертификата. Модель угроз смещается с «не потерять ключ» на «не пустить чужую identity в свою политику», что аудитить проще. Ephemeral-сертификат живёт 10 минут, и подделать OIDC-токен GitHub на нужный sub без компрометации репозитория невозможно.
Можно ли использовать Cosign без публичной Sigstore-инфраструктуры?
Да. Sigstore-стек (Fulcio, Rekor, TUF-root) можно развернуть self-hosted. Для air-gapped-сред это обязательно. Cosign поддерживает флаги --fulcio-url, --rekor-url и SIGSTORE_ROOT_FILE, чтобы работать с приватной копией.
В чём разница между cosign sign и cosign attest?
cosign sign подписывает digest образа как таковой, это «я разрешаю запуск этого образа». cosign attest подписывает произвольное утверждение о образе (SBOM, результат сканирования, SLSA-provenance), это «я утверждаю, что об этом образе известно то-то». Проверяются они разными командами: verify и verify-attestation.
Как проверить подписанный образ в Kubernetes без Kyverno?
Альтернативы Kyverno: Sigstore Policy Controller (родной от Sigstore, работает через ClusterImagePolicy) и Connaisseur (популярен в Docker-мире). Оба поддерживают Cosign keyless. Если у вас уже есть OPA Gatekeeper, для Cosign там нужен внешний data-provider, путь неудобный, лучше поставить рядом Kyverno.
Работает ли Cosign с приватными реестрами вроде Harbor или ECR?
Да, Cosign работает с любым OCI-совместимым реестром, включая Harbor, ECR, Artifact Registry и JFrog. Единственное требование: реестр должен поддерживать OCI 1.1 referrers или fallback-схему с тегами sha256-...sig. Все крупные вендоры это поддерживают с 2024 года.
Пошаговая настройка CrowdSec 1.6 на Linux: агент, LAPI, bouncer для nftables, AppSec WAF перед nginx, защита SSH от bruteforce и подключение к коллективному Blocklist API с примерами команд и типичными ошибками.
Практическое руководство по настройке auditd на RHEL 9 и Ubuntu 24.04 в 2026: правила sudo, execve и /etc/passwd, поиск через ausearch и пересылка событий в Wazuh или Splunk через audisp-плагины.
Trivy 0.65+ для DevSecOps: сканирование контейнеров, Terraform и Kubernetes YAML, генерация SBOM (CycloneDX, SPDX), поиск секретов и готовые пайплайны для GitHub Actions, GitLab CI и Trivy Operator.