Перейти к содержимому
Hogin Hogin
Назад

SLSA Level 2: что такое build provenance и зачем он не SBOM

8 мин чтения

SBOM отвечает на вопрос «что лежит внутри артефакта». SLSA отвечает на совсем другой — «как этот артефакт был собран». Это разные слои, и путать их дорого: можно иметь идеальный SBOM и при этом понятия не иметь, что бинарник подменили на скомпрометированном раннере. SLSA Level 2 — это минимальный уровень, который ловит атаки класса SolarWinds. Разберёмся, что он реально даёт и как включить его за пару строк.

Содержание

Открыть содержание

SBOM и SLSA — это про разное

SBOM (Software Bill of Materials) — это опись: список пакетов, библиотек и их версий внутри артефакта. Он бесценен, когда выходит очередной Log4Shell и нужно за минуту понять, затронуты вы или нет. Но SBOM ничего не говорит о том, откуда взялся сам артефакт. Его можно сгенерировать для любого бинарника, в том числе для подменённого.

SLSA (Supply-chain Levels for Software Artifacts) заходит с другой стороны. Это не опись содержимого, а provenance — заверенное описание процесса сборки: из какого коммита, каким пайплайном, на каком build runner-е получился этот конкретный digest. Грубо: SBOM — список ингредиентов на упаковке, SLSA — пломба с записью «произведено на этой фабрике, в эту смену, по этому рецепту».

SBOM против SLSA: что внутри против как собрано

Поэтому вопрос «SBOM или SLSA» поставлен неверно. Нужны оба: SBOM кормит сканеры уязвимостей, SLSA даёт доверие к самому факту, что артефакт пришёл из вашего пайплайна, а не из чужих рук.

Уровни L1–L3 на пальцах

В SLSA v1.0 уровни описывают, насколько провенанс трудно подделать.

L2 — реалистичная цель для подавляющего большинства команд. Он не требует своей инфраструктуры, ловит самый частый сценарий атаки (подмена на этапе сборки) и включается в GitLab CI одной переменной. L3 — это уже про крупные платформы, регулируемые отрасли и изолированные раннеры.

Почему L2 «перехватывает SolarWinds»

Атака на SolarWinds сработала, потому что злоумышленник внедрился в build-окружение и подменил артефакт после того, как код прошёл ревью. Исходник в git был чистым, подпись релиза была валидной — а в бинарнике сидел бэкдор. Ни код-ревью, ни SBOM такое не ловят: они смотрят на вход и на содержимое, но не на сам процесс сборки.

L2-провенанс закрывает именно этот разрыв. Он привязывает digest артефакта к конкретному коммиту и конкретному пайплайну на доверенном раннере. Если кто-то соберёт «свой» артефакт в обход пайплайна, у него не будет валидного провенанса от вашего builder identity — и проверка на стороне потребителя его отклонит.

Важно понимать, чего L2 при этом не обещает. Он не мешает злоумышленнику с доступом к репозиторию влить вредоносный коммит — это зона код-ревью и защиты веток. Он не гарантирует, что внутри артефакта нет уязвимостей — это зона сканеров. Что он действительно делает — обрубает целый класс атак, где код и релиз выглядят чистыми, а подмена происходит «в тёмной комнате» между ними, на самом билд-сервере. Именно эта комната исторически была слепым пятном: на неё не смотрел никто, потому что «ну собралось же и подписалось». L2 включает там свет и делает любую подмену видимой на проверке.

Откуда берётся provenance уровня L2

Сравнение в одной таблице

SBOMSLSA provenance
Вопросчто внутрикак собрано
ФорматSPDX / CycloneDXin-toto attestation
Что ловитизвестные CVE в зависимостяхподмену на этапе сборки
Кто может создатькто угодно для чего угоднодоверенный build runner (L2+)
Подделываемостьвысокаянизкая (подписан, не вашим ключом)
Зачемприоритизация уязвимостейдоверие к происхождению

Что нужно, чтобы получить L2 в GitLab CI

Главная хорошая новость: вручную формат провенанса писать не надо — его генерирует сам GitLab Runner. Достаточно включить переменную RUNNER_GENERATE_ARTIFACTS_METADATA, и для каждого build-артефакта job-а рядом появится файл с SLSA-провенансом.

stages: [build]

variables:
  RUNNER_GENERATE_ARTIFACTS_METADATA: "true"   # runner сам сгенерит provenance

build:
  stage: build
  rules:
    - if: $CI_COMMIT_TAG
  script:
    - make build                 # ваш обычный билд
  artifacts:
    paths:
      - dist/app                 # к этому артефакту runner приложит provenance

Что здесь происходит: ваш script только собирает артефакт, а провенанс создаёт сам раннер — отдельный от вашего кода компонент платформы, к которому build-скрипт не имеет доступа. Именно это разделение и даёт L2: провенанс рождается не тем же шагом, что артефакт. Рядом с dist/app появится artifacts-metadata.json (или <имя>-metadata.json) — это in-toto Statement с предикатом в формате SLSA Provenance v1, и GitLab заявляет для него соответствие SLSA Level 2.

Для контейнерных образов логика та же: собираете и пушите образ, а провенанс на его digest прикрепляете keyless через cosign — тем же блоком id_tokens с audience sigstore, что и при подписи образа: cosign attest --type slsaprovenance --predicate artifacts-metadata.json registry.gitlab.com/acme/app@sha256:....

Проверка на стороне потребителя

Провенанс бесполезен, если его никто не проверяет. У GitLab для артефактов есть CLI glab, который сверяет провенанс через хранилище аттестаций (под капотом — Sigstore/cosign, keyless по OIDC):

glab attestation verify acme/app dist/app

Команда подтверждает, что провенанс выпущен ожидаемым builder identity, и показывает payload — внутри buildDefinition с исходным коммитом, проектом и параметрами сборки. Дальше эти поля сверяют с политикой: «принимаем только из ветки refs/tags/, только из проекта acme/app».

Для контейнерных образов то же делается через cosign verify-attestation --type slsaprovenance с issuer https://gitlab.com, а само правило вешается на admission в кластере — Kyverno умеет проверять не только подпись образа, но и attestation-ы через verifyImages с блоком attestations, сверяя условия по содержимому провенанса.

Что лежит внутри provenance

Полезно представлять, что именно вы проверяете. Провенанс — это in-toto attestation в формате SLSA Provenance v1, и в нём есть несколько ключевых блоков:

Смысл проверки — не «провенанс есть», а «builder.id тот, кому я доверяю, и externalParameters указывают на мой репозиторий и нужную ветку». Без этой сверки наличие провенанса само по себе ничего не доказывает.

Как проверить, что всё работает

Три шага. Первый — что провенанс вообще приложен: для артефакта это показывает glab attestation verify acme/app dist/app, для образа — cosign tree registry.gitlab.com/acme/app@sha256:... (attestation рядом с подписью). Второй — что verify-attestation проходит и payload содержит ожидаемый коммит: проверяйте не факт наличия, а конкретные поля. Третий — негативный тест: соберите артефакт «вручную» (локально, без пайплайна), попробуйте провести его через проверку и убедитесь, что без валидного провенанса он отклонён.

Частые грабли

Ловушка: провенанс ≠ «код безопасен»

L2 доказывает происхождение, а не качество. Он гарантирует, что артефакт собран вашим пайплайном из конкретного коммита — но если в этом коммите уязвимость или закладка, провенанс будет идеально валидным. SLSA и SBOM/сканеры решают разные задачи и не заменяют друг друга: первый отвечает за «это наш артефакт», вторые — за «в нём нет известных дыр». Полная картина — это связка: подпись образа (кто), provenance (как собрано) и SBOM/скан (из чего и насколько дырявое).

Итог

SLSA Level 2 — не «ещё одна SBOM-обёртка», а доказательство того, что артефакт произведён вашим пайплайном, а не подменён на пути от коммита до реестра. Это граница между «доверяем коду» и «доверяем сборке» — и ровно её обходили в громких supply-chain атаках. Цена входа смешная: включить одну переменную в CI и добавить одну проверку у потребителя. С учётом того, что регуляторика (US EO 14028, EU CRA) уже сделала provenance обязательным де-факто, это та гигиена, которую дешевле внедрить сейчас, чем объяснять её отсутствие потом.


Поделиться:

Предыдущая статья
eBPF без боли: Cilium и сетевая наблюдаемость в Kubernetes
Следующая статья
MCP-сервер Intervals.icu в Claude через Pomerium