После SolarWinds, Codecov и log4shell стало ясно, что проверка артефакта на выходе перестала быть достаточной защитой. SBOM показывает, что находится внутри образа. Cosign-подпись доказывает, что у подписавшего был валидный OIDC-токен. Между ними — пустота, в которую как раз и встают атаки на цепочку поставки: подменённый builder, изменённый workflow, неверный commit. SLSA закрывает эту пустоту через provenance — машинно-проверяемое описание того, как именно был собран артефакт.
Что обещает каждый уровень
SLSA v1.0 (поддерживается OpenSSF) определяет четыре уровня. L0 — никаких гарантий, состояние по умолчанию. L1 требует документированного build-процесса и сгенерированного provenance, даже без подписи: этого хватает, чтобы поймать честные ошибки и быстрые подмены. L2 поднимает планку до hosted isolated builder с подписанным provenance — закрывает tampering между build и публикацией. L3 — hardened ephemeral builder, strong provenance: модель угроз включает скомпрометированный builder и инсайдера с доступом к CI.
L3 доступен почти из коробки на GitHub Actions через slsa-framework/slsa-github-generator (reusable workflow). GitLab пока даёт partial L2. Self-hosted builder с пометкой «у нас L2» без реальной изоляции — самая распространённая ложь индустрии, и SLSA отказывается её валидировать.
Что внутри provenance
Provenance — это in-toto Statement с предикатом slsa.dev/provenance/v1. Внутри: subject — image и его digest; buildDefinition.externalParameters — repository, ref, путь к workflow; buildDefinition.resolvedDependencies — git commit; runDetails.builder.id — идентификатор builder; runDetails.metadata — invocation ID и временные метки.
Эти поля отвечают на четыре вопроса: что собрали, откуда, чем, когда. Подпись без provenance подтверждает только «кто-то с валидным токеном что-то подписал». Provenance без подписи — JSON без доказательств. Работают они только вместе.
Sigstore: подпись без долгоживущих ключей
Cosign в keyless-режиме (GA с 2.0+) убирает проблему key management. Identity берётся из OIDC: GitHub Actions, Google, ваш Keycloak в air-gapped. Fulcio выдаёт сертификат на 10 минут, Cosign подписывает им артефакт, запись уезжает в Rekor — публичный append-only transparency log.
Один синтаксис покрывает все артефакты OCI:
cosign sign --yes ghcr.io/org/payments-api:v1.2.3
cosign attest --predicate provenance.json --type slsaprovenance ghcr.io/org/payments-api:v1.2.3
cosign attest --predicate sbom.cdx.json --type cyclonedx ghcr.io/org/payments-api:v1.2.3
С приходом Helm 4 и OCI как первичного канала distribution тот же cosign sign работает на chart-артефакты. Один transparency log, одна модель verification, одно admission-правило.
Закрытие петли в admission
Подписать и не проверять — security theater, упомянутый в каждом SLSA-документе и регулярно встречающийся в проде. Verification должна жить в admission controller, иначе любой образ заедет в кластер мимо контроля.
Kyverno ClusterPolicy с verifyImages принимает keyless-attestor: regex на subject (workflow path), OIDC issuer, Rekor URL. Pod с образом, который не прошёл проверку, не создаётся. На стороне CLI работает slsa-verifier:
slsa-verifier verify-image ghcr.io/org/payments-api:v1.2.3 \
--source-uri github.com/org/payments-api \
--source-tag v1.2.3 \
--builder-id 'https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_container_slsa3.yml@refs/tags/v2.0.0'
Без --builder-id цепочка обрывается: подпись валидна, но кто и где собрал — неизвестно.
Anti-patterns, которые встречаются чаще, чем хочется
Один key на dev/staging/prod аннулирует blast radius isolation. Hardcoded email подписанта в policy не масштабируется на команду. latest в verifyImages.imageReferences указывает на mutable target — подпись бессмысленна. Provenance с пустыми internalParameters теряет контекст вызова. Отключённый Rekor (--rekor-url="") убирает detection: украденный OIDC-токен пройдёт незаметно.
K8s-native альтернатива
Tekton Chains — observer для TaskRun: автоматически генерирует SLSA provenance, подписывает через Sigstore keyless, пушит attestation рядом с image в OCI и запись в Rekor. Получается SLSA L3 без выхода из Kubernetes. Цена — Tekton как pipeline engine с собственным набором CRDs (Tasks, Pipelines, Triggers) и нетривиальный operational overhead. Подходит платформенным командам, которые уже зафиксировали K8s-первый CI.
Три ответа на три вопроса
SBOM отвечает на «что внутри артефакта». Provenance — на «как собран». Cosign-подпись — на «кто гарантирует». Атака на supply chain выбивает один из этих трёх ответов; SLSA проектируется так, чтобы admission controller успел задать все три, прежде чем создан Pod.