SBOM в каждый пайплайн: Syft + Grype после Log4Shell в проде клиентов
Log4Shell показал: не знать состав своих контейнеров - дорого. Встраиваем Syft (CycloneDX) и Grype в GitLab CI, сохраняем в Nexus, выстраиваем политику блокировок.
Log4Shell катализирует внедрение SBOM в enterprise - Syft и Grype становятся де-факто инструментами для container image аудита в CI-пайплайнах
Log4Shell поставил болезненный вопрос перед каждой командой, которая деплоит Java-сервисы в контейнерах: «Покажи, что у тебя внутри образа». Кто к этому вопросу готовился - ответил быстро. Кто нет - провёл несколько неприятных дней с grep по Dockerfile и ручным чтением POM-файлов.
Мы в сентябре разобрали связку Syft + Grype на практике и добавили SBOM-шаг в несколько внутренних пайплайнов. После Log4Shell это перестало быть внутренним экспериментом - мы начали ставить то же самое клиентам. Вот что получилось на практике и где возникли сложности, которых в сентябре ещё не было.
Почему именно Syft + Grype, а не Trivy
Вопрос честный, тем более что мы разбирали Trivy и использовали его в MR-пайплайнах. После Log4Shell контекст изменился: нужен был не просто сканер для блокировки MR, а инструмент для генерации SBOM, который можно хранить, передавать и сканировать повторно - с разными базами, в разное время.
Syft здесь удобнее: SBOM - его основной продукт, а не побочный формат вывода. CycloneDX JSON он генерирует чисто, покрытие Java-экосистемы (jar, war, pom.xml, gradle) оказалось чуть аккуратнее для некоторых fat jar сценариев. Grype принимает этот SBOM на вход и сканирует по NVD + GitHub Advisory + базам дистрибутивов. Связка Syft -> хранилище -> Grype открывает сценарии повторного сканирования без пересборки образа.
Это не «Trivy плохой» - просто для задачи хранимого, версионированного SBOM Syft + Grype оказались удобнее.
Схема пайплайна
Структура, которую мы ставим клиентам, выглядит так:
build -> sbom-generate (Syft) -> sbom-push (Nexus) -> vuln-scan (Grype) -> [block | report]
SBOM не просто генерируется и выбрасывается - он уходит в Nexus Raw репозиторий рядом с образом. Это даёт возможность аудита: взять SBOM от образа двухнедельной давности и прогнать его через актуальную базу Grype. Log4Shell показал ценность именно такого сценария: мы могли ответить «в образе от 1 декабря log4j-core 2.14.1, в образе от 15 декабря - 2.17.0» без пересборки чего-либо.
GitLab CI: конкретная конфигурация
sbom-generate:
stage: security
image: anchore/syft:v0.29.0
script:
- syft ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
-o cyclonedx-json
--file sbom.json
artifacts:
paths:
- sbom.json
expire_in: 90 days
sbom-push:
stage: security
needs: [build, sbom-generate]
image: curlimages/curl:7.80.0
script:
- |
curl -u ${NEXUS_USER}:${NEXUS_PASSWORD} \
--upload-file sbom.json \
"${NEXUS_URL}/repository/sbom-raw/${CI_PROJECT_PATH}/${CI_COMMIT_SHORT_SHA}/sbom.json"
vuln-scan:
stage: security
needs: [sbom-generate]
image: anchore/grype:v0.27.0
script:
- grype sbom:sbom.json
--fail-on high
--only-fixed
allow_failure: false
Несколько решений, которые потребовали обдумывания.
--only-fixed - не опция, а необходимость. Без этого флага Grype падает на HIGH и CRITICAL, для которых нет фикса в upstream прямо сейчас. В базовых образах на debian:bullseye-slim живут несколько таких - это известные ситуации, где вендор дистрибутива принял решение не бэкпортировать. Блокировать пайплайн на unfixable CVE - значит парализовать выкатки без возможности что-то сделать. Поэтому: unfixable фиксируется в отчёте, попадает в тикет, рассматривается командой, но не блокирует.
Версии образов Syft и Grype - пинить явно. latest в CI-образах ломал нас уже дважды: Grype 0.26 -> 0.27 изменил формат вывода и флаги, и джоба начала падать по синтаксису, а не по уязвимостям. Фиксированная версия + периодический upgrade вручную.
needs: вместо stage:. Grype должен стартовать сразу после sbom-generate, не ждать окончания sbom-push. С needs: это работает параллельно - экономит несколько минут на длинных пайплайнах.
Nexus как хранилище SBOM
Nexus Raw Repository - самый простой вариант для тех, у кого Nexus уже стоит как artifact registry. Структура пути {project}/{commit_sha}/sbom.json позволяет потом достать любой SBOM по известному коммиту.
Один нюанс с Nexus: anonymous read нужно либо разрешить для SBOM-репозитория, либо добавить отдельный ro-токен для инструментов, которые будут забирать SBOM на ревью. У одного клиента SBOM-репозиторий оказался под теми же правами, что production artifacts - с паролем от CI. Разделили: отдельный репозиторий, отдельный пользователь с правами только на запись для CI и чтение для аудиторов.
Что нашли при первом прогоне
Когда мы запустили SBOM-генерацию по образам одного из клиентов - Java-backend на SpringBoot - нашлось несколько неожиданных вещей.
log4j-core в fat jar. Именно этот сценарий был самым неприятным в Log4Shell: Syft умеет смотреть внутрь jar-файлов и находить вложенные jar. Один сервис тащил log4j-core 2.14.0 не в слое OS и не через явную зависимость в pom.xml, а транзитивно - через один из Spring-компонентов. Без SBOM это потребовало бы ручного анализа.
Системные пакеты от базового образа. В одном образе нашли glibc 2.31 с несколькими HIGH без фикса - они там из debian:bullseye-slim, ничего нового, но теперь это явно видно в отчёте, а не является тёмным пятном.
Дублирующиеся версии одной библиотеки. В одном fat jar оказались две версии jackson-databind: 2.12.3 (из прямой зависимости) и 2.11.4 (из транзитивной цепочки). Grype флагнул 2.11.4 как уязвимую. Без SBOM это не поймал бы никакой поверхностный сканер.
Политика блокировок
После нескольких итераций пришли к такой схеме:
- CRITICAL fixable - блокирует пайплайн, нельзя смержить без исправления.
- HIGH fixable - блокирует, но есть override через environment variable с обязательным комментарием в MR.
- HIGH unfixable / MEDIUM и ниже - попадает в артефакт, создаётся issue в GitLab, не блокирует.
Override через переменную - это сознательный компромисс. Иногда нужно выкатить критический фикс, а HIGH-уязвимость в базовом образе не имеет апстримного патча ещё две недели. Запрещать override совсем - значит либо блокировать прод, либо получать давление «выключить это надоедливое сканирование». Логировать override явно - лучше, чем бороться с командой.
Где сейчас
SBOM-пайплайн работает у нескольких клиентов первую неделю. Основные наблюдения: инструменты стабильные для своей зрелости, но требуют внимания к версиям и к политике unfixable. Главная польза пока не в блокировках, а в самом факте инвентаризации - теперь на вопрос «у нас есть Log4j в образах?» есть ответ без трёхчасового расследования.
Аудит инфраструктуры как отдельная работа всё ещё нужен - SBOM в CI закрывает только образы и только то, что через него проходит. До CI-шага тоже есть что смотреть.
- SBOM на практике: Syft + Grype для container images и интеграция в CI · 13 сентября 2021