Прямой ответ
Два артефакта закрывают supply chain-вопросы «что внутри образа» и «кто его собрал»: SBOM (Software Bill of Materials — инвентарь содержимого: пакеты ОС+языковые зависимости с версиями — CycloneDX/SPDX форматы — «список ингредиентов») и подпись (cosign — криптографическое подтверждение «образ собран нашим пайплайном, после сборки не изменён»); зачем SBOM: реакция на CVE (вышел log4shell-класс — вопрос «какие из 40 сервисов содержат уязвимую библиотеку» — с SBOM — grep по инвентарю за минуты; без — пересборка/скан всего — дни паники — Trivy-скан — SBOM дополняет: сканер ищет CVE, SBOM отвечает «что вообще внутри» — аудит/комплаенс/лицензии — «какие OSS-лицензии в продукте» — тот же инвентарь); генерация: syft registry/app:tag -o cyclonedx-json=sbom.json (или trivy image --format cyclonedx — оба генерят — Trivy — один инструмент на скан+SBOM); хранение — рядом с образом в реестре (OCI-артефакт — cosign attach sbom — «SBOM живёт там же, где образ — не в отдельном Excel» — реестр-практика); подпись cosign: два режима — ключевой (приватный ключ у CI — KMS/Vault — секреты — «ключ CI скомпрометирован = подписи врут» — ротация/хранение — серьёзность) и keyless (Sigstore-механика: CI-джоба аутентифицируется через OIDC-идентити (GitHub Actions — идентифити воркфлоу), Fulcio-CA выдаёт короткий сертификат, подпись + запись в Rekor (публичный журнал прозрачности — «подпись была» — неизменяемо) — ключей нет, вращать нечего — для GitHub Actions-пайплайнов — дефолт-выбор 2026); cosign sign registry/app@sha256:... (подписываем digest, не тег — тег переназначаем (теги-против-digest — «подписали :latest, latest сдвинули» — подпись не о том образе — digest — единственно корректно); подпись — OCI-артефакт рядом в реестре (cosign-тег по digest — ничего в конфиг образа не меняет); верификация: cosign verify --certificate-identity-regexp '' --certificate-oidc-issuer '' registry/app@sha256:... (проверяем не «подпись валидна», а «подписал наш пайплайн» — identity+issuer — без них — любой с GitHub Actions подпишет — частая дыра «verify без identity»); enforcement в кластере: admission-политика (Kyverno verifyImages — «под без подписи нашего CI — не деплоится» — политика YAML — mutate-режим подставит digest по тегу — «:latest в манифесте» перестаёт быть дырой — харднинг); полный цикл: build → SBOM+подпись в CI → реестр → admission-верификация → скан на CVE с привязкой к SBOM → реакция на новые CVE по инвентарю — цепочка, где каждый артефакт проверяем — 12-factor для артефактов: сборка один раз, верификация везде.
Практика: CI-пайплайн с подписью и политикой
GitHub Actions-каркас (keyless): джоба build — права id-token: write (OIDC-токен для Sigstore — без секретов-ключей вообще — CI-секреты), сборка+push по digest, затем: cosign sign registry/app@${DIGEST} (keyless — сертификат из OIDC-идентити джобы — запись в Rekor) + syft/trivy SBOM → cosign attach sbom (SBOM-артефакт в реестр) + cosign sign на SBOM (аттестация инвентаря — «SBOM не подменили»); аттестации сборки (SLSA-происхождение — cosign attest --type slsaprovenance — «кто/где/из какого коммита собрал» — github-actions-контекст — полный след); политика кластера (Kyverno verifyImages): match-правило на deployment'ы — verify подписи (identity-regex по org/repo — issuer — GitHub OIDC), fail-режим после испытательного срока (audit/warn → enforce — флаги-постепенность — «включили enforce в первый день» — легаси-деплои легли — классика); mutate (тег → digest при admission — «манифесты с :latest» — политика переводит в digest автоматически — репро-деплой); реакция на CVE-инцидент (процедура): новая критичная CVE → grep SBOM'ов всех образов (cosign download sbom/инвентарь в хранилище — MinIO-паттерн — SBOM-озеро) → список затронутых сервисов за минуты → фикс-волна (Dependabot — пересборка — новая подпись — деплой) → верификация (образы без новой сборки не задеплоены — политика гарантирует — «починили» проверяемо); локальная проверка разработчиком (cos verify-плагины/cosign verify в pre-commit — «деплою локальный билд» — политика кластера всё равно не пустит — ранний фидбек экономит цикл — структура).
Практический сценарий: «новый log4j» за 40 минут вместо недели
Контекст: 40+ сервисов в кластере, supply chain внедрён полгода назад (SBOM на каждый build, cosign keyless, Kyverno verifyImages в enforce); утро: публикация критичной RCE в популярной Java-библиотеке (CVSS 9.8, эксплойт публичный — «вчерашний Trivy-скан был чист, сегодня всё красное» — Trivy-ритм); реакция по шагам: (1) 10 минут — инвентарь: SBOM-озеро (каждый build выгружает CycloneDX — MinIO) — grep по пакету/диапазону версий — 7 сервисов затронуто (из 40 — остальные Java-библиотеку не содержат — это и есть ценность SBOM — без него: пересборка/рескан всех 40 — часы); (2) 20 минут — фикс: Dependabot-PR в 7 репо (патч-версия вышла — Dependabot-поток), CI зелёный (тесты — быстрая прогонка критичных путей — CI), сборка+подпись+SBOM — стандартный пайплайн; (3) 10 минут — деплой: canary на 2 сервиса → полный rollout 7 (HPA-стабильность — деплой под нагрузкой штатный — метрик-контроль); (4) верификация: ночной скан чист по 7 (новые SBOM не содержат уязвимую версию — артефактная проверка, не «надеемся»); политика не дала бы задеплоить неподписанное — «hotfix с ноутбука» исключён процессом (Kyverno — OWASP-позиция — supply chain-атаки любят аварийные окна); итог: 40 минут от публикации до закрытия (без SBOM-инвентаря — оценка «день-два» — предыдущий подобный инцидент до внедрения — 3 дня раскопок «где эта библиотека»); постмортем-находки: SBOM 2 легаси-сервисов генерился без Java-слоя (syft-конфиг — поправили — «инвентарь неполон = инвентаря нет» — тесты-инвентаря — проверка полноты SBOM в CI); вывод: supply chain — не «галочка комплаенса», а инструмент инцидент-менеджмента — окупается в первый же CVE-шторм (incident-практика).
FAQ
CycloneDX или SPDX — какой формат SBOM выбрать? CycloneDX: OWASP-происхождение, security-first (уязвимости/VEX в формате родные — Trivy/syft-поддержка отличная, экосистема Java/npm сильная) — дефолт для security-задач; SPDX: Linux Foundation/ISO-стандарт (ISO/IEC 5962 — лицензионно-комплаенсный фокус — «SBOM для юристов/госзаказчика» — формат); оба — JSON/XML — конвертируемы (syft генерит оба — «выбор формата» — не необратимое решение); практика: CycloneDX для внутренних security-процессов (скан/реакция на CVE — Trivy), SPDX — если требует контракт/комплаенс; важнее формата — полнота и хранение (SBOM рядом с образом, на каждый build, с версией тулзы — «SBOM раз в квартал вручную» — бесполезен — CVE-реакция упирается в свежесть инвентаря — реестр-ритм).
Что такое keyless-подпись и чем она лучше ключей? Механика: вместо постоянного приватного ключа — короткоживущий сертификат (Fulcio-CA выдаёт по OIDC-идентити CI-джобы — «я — джоба build из репо myorg/app» — GitHub Actions OIDC — токен-механика по сути), подпись фиксируется в Rekor (прозрачный журнал — подделка задним числом видна); выигрыш: нечего хранить/ротировать/утекать (ключ CI — главная цель атаки — «CI-секрет с signing-ключом» — отсутствует как класс — секреты); цена/условия: привязка к OIDC-провайдеру (GitHub/GitLab/ workload identity — self-hosted без OIDC — ключевой режим остаётся), публичность Rekor (запись видна — приватные имена артефактов — учитывать — приватные реестры + private Fulcio — enterprise-вариант); критично: verify с identity (certificate-identity-regexp по org/repo — иначе «любая GitHub-джоба» — дыра — сценарий выше — политика Kyverno с issuer+identity — не «подпись есть», а «подпись наша»); вывод: публичные CI (GitHub Actions) — keyless дефолт, изолированные контура — ключи в KMS (vault).
Как проверять подписи при деплое, чтобы не сломать аварийные релизы? Постепенность (audit → warn → enforce — Kyverno-режимы — флаги-культура — месяц warn показывает «что легаси-деплоится без подписи» — чинится до enforce); escape-hatch продуманный заранее (break-glass-процедура: отдельная подписанная «аварийная» джоба (собирает из того же репо — подпись та же политика — «аварийный релиз мимо CI» не нужен — аварийность — в скорости джобы, не в обходе проверки — incident-runbook); namespace-исключения — только quarantine-неймспейс для разбора — не прод); типичные поломки enforce: локальные dev-деплои (исключение dev-неймспейсов — локальный стек — кластерные политики не про dev-песочницы), сторонние образы (postgres/redis из Docker Hub — отдельное правило — verify по официальным подписям или allowlist digest'ов — «политика на всё подряд» — ломает инфраструктурные деплои); проверка самой политики (тесты Kyverno-yaml в CI — «политика, которую не тестировали, упадёт в прод» — тестирование-инфраструктуры — политики — тоже код — ревью).
Нужен ли SBOM, если есть Trivy-скан образов? Разные вопросы: Trivy — «есть ли известные CVE сейчас» (моментальный снимок уязвимостей — скан), SBOM — «что внутри» (инвентарь — на его основе сканируют потом, когда CVE опубликуют — «вчерашний чистый образ сегодня уязвим» — SBOM+свежая CVE-база — перескан без пересборки — скорость реакции из сценария); плюс SBOM покрывает не-CVE-задачи: лицензионный аудит (GPL-зависимости в проприетарном продукте — юридический риск — инвентарь лицензий), комплаенс-требования (госзаказ/enterprise-закупки — «предоставьте SBOM» — 2026 — стандартное требование — контекст), инвентаризацию стека («кто вообще использует эту библиотеку» — архитектурные решения — ADR-культура); стоимость — копейки (генерация секунды в build, хранение — килобайты на образ — реестр); вывод: скан — тактика, SBOM — инфраструктура знаний о составе — нужны оба, генерятся в одном пайплайне.
Источники: Sigstore/cosign docs, CycloneDX spec, syft — ссылки в шапке.