ADG Оставить заявку
Блог Информационная безопасность 6 мин чтения

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-шага тоже есть что смотреть.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.