ADG Оставить заявку
Блог DevOps 5 мин чтения

Trivy в GitLab CI: блокируем merge при критических CVE в контейнерных образах

Aqua Security Trivy 0.19 добавляет SBOM-сканирование и Cosign для верификации подписей. Встраиваем в GitLab CI, сравниваем с Grype на Alpine-пакетах.

Контекст момента

Aqua Security выпускает Trivy 0.19 со сканированием SBOM и поддержкой Cosign для проверки подписей container-образов

Две недели назад мы писали про SBOM и Syft+Grype для инвентаризации контейнерных образов. Пока тот пост выходил, Aqua Security выкатила Trivy 0.19 - с поддержкой Cosign для проверки подписей образов и встроенным SBOM-сканированием. Стало интересно сравнить, и мы взялись встраивать Trivy в GitLab CI вместо того, что было раньше.

Спойлер: на Alpine-образах Trivy выигрывает у Grype заметно. Почему - ниже.

Что изменилось в Trivy 0.19

До этой версии Trivy сканировал уязвимости в OS-пакетах и нескольких языковых экосистемах. В 0.19 появилось несколько важных вещей.

Поддержка Cosign. Sigstore/Cosign - это инициатива по подписи артефактов в supply chain. Trivy теперь умеет верифицировать подпись образа перед сканированием: если подпись не сходится или её нет - можно заблокировать. Для нас это пока эксперимент, подписывание образов в пайплайне ещё не выстроено, но функция нужная, особенно после истории с supply chain атаками.

SBOM как входной формат. Trivy умел генерировать SBOM, теперь умеет принимать его на вход для сканирования - как Grype с его sbom: префиксом. Это открывает схему «сгенерировал один раз - сканируй разными инструментами».

Расширенное покрытие языковых экосистем. В 0.19 улучшена детекция Go-модулей и Rust-пакетов. Для нас актуален Go.

Alpine vs. Debian: почему Trivy выигрывает

Когда мы прогнали одни и те же Alpine-базированные образы через Grype и Trivy, разница оказалась ощутимой: Trivy находил уязвимости в apk-пакетах, которые Grype пропускал или классифицировал с меньшей точностью.

Причина инфраструктурная. Alpine использует собственную базу безопасности (secdb) - secfixes в APKBUILD-файлах репозитория. Trivy напрямую парсит Alpine secdb и связывает CVE с конкретными версиями пакетов, включая backport-фиксы (Alpine часто бэкпортирует патчи без повышения версии пакета). Grype опирается преимущественно на NVD и GitHub Advisory - там данные по Alpine менее полные, а backport-ситуации почти не учитываются.

На практике это выглядит так: libssl1.1 в Alpine 3.13 может числиться уязвимым в NVD, но в Alpine secdb для этой конкретной сборки уязвимость уже закрыта бэкпортом. Trivy это знает, Grype - нет, и выдаёт false positive. На Debian-базированных образах разница меньше, там Grype работает вполне сопоставимо.

Для нас, учитывая что большинство сервисных образов собирается на Alpine, это было решающим аргументом.

Интеграция в GitLab CI

Цель была простая: каждый MR должен сканироваться, при CRITICAL CVE - merge заблокирован. Схема через GitLab security scanning templates существует, но она завязана на GitLab Ultimate. Мы сделали через прямой вызов Trivy.

container-scanning:
  stage: test
  image:
    name: aquasec/trivy:0.19.2
    entrypoint: [""]
  variables:
    TRIVY_NO_PROGRESS: "true"
    TRIVY_CACHE_DIR: ".trivycache"
    # Сканировать только fixable уязвимости
    TRIVY_IGNORE_UNFIXED: "true"
  cache:
    paths:
      - .trivycache/
  script:
    - trivy image
        --exit-code 1
        --severity CRITICAL
        --format table
        ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Несколько моментов из практики.

--ignore-unfixed - почти обязательный флаг. Без него пайплайн валится на уязвимостях, для которых нет фикса в upstream. В базовом alpine:3.14 живут несколько CRITICAL без фиксов - это известные ситуации, где апстрим или не признаёт CVE применимым, или фикс идёт в следующую версию Alpine. Блокировать на них CI - значит парализовать разработку.

Кеш базы уязвимостей важен. Trivy скачивает базу при каждом запуске, если нет кеша. Это 30-40 секунд и несколько сотен мегабайт. С кешем на уровне GitLab runner - первый запуск медленный, дальше база обновляется инкрементально. Время сканирования типичного образа на 200-300 МБ - порядка 20-30 секунд с кешем.

--exit-code 1 только на нужный severity. Мы ставим --exit-code 1 только на CRITICAL. HIGH и ниже - отдельный шаг с --exit-code 0, который генерирует репорт как артефакт, но не блокирует.

container-scanning-report:
  stage: test
  image:
    name: aquasec/trivy:0.19.2
    entrypoint: [""]
  variables:
    TRIVY_NO_PROGRESS: "true"
    TRIVY_CACHE_DIR: ".trivycache"
    TRIVY_IGNORE_UNFIXED: "true"
  cache:
    paths:
      - .trivycache/
  script:
    - trivy image
        --exit-code 0
        --severity HIGH,MEDIUM
        --format json
        --output trivy-report.json
        ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
  artifacts:
    paths:
      - trivy-report.json
    expire_in: 14 days
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Что с SBOM-функциональностью

Trivy умеет генерировать SBOM в CycloneDX и SPDX форматах:

trivy image --format cyclonedx --output sbom.json nginx:1.21-alpine

И принимать SBOM как вход для сканирования:

trivy sbom sbom.json

На практике мы пока генерируем SBOM через Syft (покрытие языковых экосистем у него чуть шире по ощущениям), но сканируем через Trivy. Это нормально работает - форматы стандартные.

Где Grype всё ещё полезен

Несправедливо было бы хоронить Grype. Для Python-экосистемы и детальной работы с уже готовыми SBOM-файлами у него удобный CLI. Есть .grype.yaml для тонкой настройки игнорируемых CVE - у Trivy аналог это .trivyignore, тоже есть, но синтаксис проще. У Grype более удобный вывод для человека при интерактивном использовании.

Если стек преимущественно Debian/Ubuntu-образы и нет сильной зависимости от Alpine - выбор между ними менее принципиален.

Где сейчас

Trivy встал в MR-пайплайн, работает около недели. За это время один MR заблокировался на CRITICAL в старой версии openssl - разработчик не обновил базовый образ с прошлого квартала. Нашли, исправили, пошли дальше.

Следующий шаг - попробовать Cosign для подписи образов, которые уходят в прод. Sigstore набирает momentum в сообществе, и иметь верификацию подписи в пайплайне выглядит разумно. Но это отдельная история и отдельный пост.

Контакт

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

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