Выберите продукт

Trivy в CI/CD: скан Docker-образов, SBOM и fail‑threshold на практике

Практическое руководство по внедрению Trivy в CI/CD: как сканировать Docker-образы, генерировать SBOM, настраивать fail-threshold и политики, уменьшать шум через .trivyignore, ускорять сканы кэшем и собирать осмысленные отчёты. Всё на языке команд и готовых пайплайнов.
Trivy в CI/CD: скан Docker-образов, SBOM и fail‑threshold на практике

Trivy давно стал «дефолтным» инструментом для container security у тех, кто хочет получать быстрые и воспроизводимые результаты прямо в CI/CD. Он объединяет сканирование уязвимостей, поиск секретов, анализ конфигураций и генерацию SBOM. В этой статье соберём рабочий пайплайн: где и как запускать Trivy, как настроить fail-threshold по уровню severity, как управлять шумом, генерировать SBOM и хранить отчёты, а также как не растратить минуты CI на загрузку базы.

Зачем Trivy в CI/CD и где его запускать

Сканирование «на локалке» хорошо для первичной оценки, но безопасность выигрывает, когда проверки запускаются неизбежно, одинаково и на каждом коммите/релизе. CI/CD — идеальное место: вы закрепляете политику, не полагаетесь на ручные проверки и можете блокировать релизы при нарушении порогов риска. Trivy отлично подходит для этого: бинарь лёгкий, не требует сложной инсталляции, работает как с файловой системой, так и с образами в реестре, а также умеет собирать SBOM в популярных форматах.

Точки встраивания:

  • Перед сборкой: анализ Dockerfile и зависимостей репозитория (trivy fs, trivy config).
  • После сборки: скан Docker-образа (trivy image) до публикации в реестр.
  • После публикации: периодический скан по тегу/дижесту в реестре для выявления «поздних» уязвимостей.

Какие проверки включать

Trivy поддерживает несколько сканеров: vuln (уязвимости), secret (секреты в коде), config (мисконфиги IaC и Kubernetes-манифестов), license (лицензии пакетов). В CI/CD обычно начинают с уязвимостей образа и постепенно добавляют остальные, закрепляя политику «что блокирует релиз, а что — только предупреждение».

Для самостоятельных раннеров и приватного реестра удобно использовать изолированную инфраструктуру на облачном VDS.

Базовый скан Docker-образа и fail-threshold по severity

Самый простой и при этом практичный вариант: сканировать собранный образ и «проваливать» джоб, если обнаружены уязвимости требуемой важности. В Trivy это реализуется комбинацией фильтра по --severity и кода выхода --exit-code. По сути, это и есть ваш fail-threshold: все найденные уязвимости с уровнем не ниже выбранного порога приведут к ненулевому exit code и остановят пайплайн.

trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed --no-progress myrepo/app:build-123

Пояснения:

  • --severity HIGH,CRITICAL — фильтрует вывод и порог риска; всё равно ниже порога можно посмотреть в отдельном отчёте.
  • --exit-code 1 — обрушит джоб, если найдено хотя бы одно совпадение под выбранные severity.
  • --ignore-unfixed — исключит нефиксированные уязвимости дистрибутива (уменьшает шум, полезно для базовых образов).
  • --no-progress — чистый лог для CI.

Если вам важен более мягкий порог (например, «не падать при HIGH, падать только при CRITICAL»), меняйте --severity или делайте два шага: один с порогом HIGH на предупреждение (exit code 0), второй — строгий с CRITICAL и --exit-code 1.

Актуальность базы уязвимостей и экономия минут CI

Первый запуск Trivy скачивает базу уязвимостей. На «холодных» раннерах это может занимать ощутимое время. В CI важно:

  • Кэшировать директорию Trivy: ~/.cache/trivy либо путь, указанный в TRIVY_CACHE_DIR.
  • Явно вызывать предзагрузку базы отдельным шагом, чтобы разделить сетевую часть и скан.
  • Периодически обновлять базу, но не на каждом шаге, если у вас плотные пайплайны.
# Предзагрузка базы (опционально)
trivy image --download-db-only
# Последующие сканы без повторной загрузки
trivy image --skip-db-update --severity HIGH,CRITICAL --exit-code 1 myrepo/app:build-123

На некоторых раннерах выгодно хранить кэш между джобами/ветками. Не забудьте инвалидировать кэш при редких, но важных изменениях (например, раз в сутки).

Кэш Trivy и шаг предзагрузки базы в CI-пайплайне

Снижаем шум: .trivyignore и селективные исключения

Любой реальный проект сталкивается с «шумом»: уязвимости в инструментах, которые вы не используете, временно нефиксированные проблемы в базовом образе и т.п. Trivy предлагает два механизма:

  • Простой файл .trivyignore — перечисление ID уязвимостей (CVE и аналоги), которые нужно игнорировать.
  • .trivyignore.yaml — расширенный формат с условиями, комментариями и сроком действия исключения.
# .trivyignore (простой формат)
CVE-2023-12345
CVE-2022-99999
# .trivyignore.yaml (пример со сроком и обоснованием)
ignoreRules:
  - id: CVE-2023-12345
    reason: В компоненте неиспользуемая функциональность; сменим базовый образ в Q2
    expiresOn: 2025-06-30
  - id: CVE-2022-99999
    reason: false positive для версии libxyz в alpine:3.19

Подход с истекающими исключениями дисциплинирует: исключение не «вечное», и CI напомнит, когда пора его пересмотреть. Важно хранить эти файлы в репозитории рядом с кодом — это и есть «политика как код».

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды Финляндия

SBOM: зачем, как генерировать и хранить

SBOM (Software Bill of Materials) — перечень компонентов вашего образа с версиями и метаданными. SBOM помогает выполнять требования комплаенса, ускоряет расследование инцидентов («а у нас есть уязвимый пакет X?») и позволяет сравнивать состав между релизами. Trivy умеет собирать SBOM в популярных форматах CycloneDX и SPDX.

# Генерация SBOM для образа в формате CycloneDX JSON
trivy sbom --format cyclonedx --output sbom.cdx.json myrepo/app:build-123

# В формате SPDX JSON
trivy sbom --format spdx-json --output sbom.spdx.json myrepo/app:build-123

Практика: сохраняйте SBOM как артефакт сборки и как метаданные релиза; при необходимости отправляйте в реестр артефактов. Если в вашей организации принят единый формат (чаще CycloneDX JSON), придерживайтесь его и автоматизируйте проверку валидности.

Форматы отчётов и интеграция с системами обзора

Trivy умеет выводить результат не только в таблицу, но и в машинные форматы: JSON для последующей агрегации, SARIF для систем кода-ревью. Так вы получаете статус «прошёл/не прошёл» и одновременно полезный артефакт для аналитики.

# JSON-отчёт (для артефактов/агрегации)
trivy image --severity HIGH,CRITICAL --format json --output trivy-vuln.json myrepo/app:build-123

# SARIF (для аннотаций в обзоре кода)
trivy image --severity HIGH,CRITICAL --format sarif --output trivy.sarif myrepo/app:build-123

Хороший приём — не смешивать «блокирующий» шаг и генерацию расширенных отчётов. Сначала быстрый фильтр с --severity и --exit-code, затем — дополнительный шаг, который всегда выполняется и выгружает подробную аналитику (даже если релиз заблокирован).

Обзор JSON и SARIF отчётов безопасности Trivy

Политика как код: от правил к автоматике

Базового порога --severity часто достаточно, но зрелые команды идут дальше: по проектам задаются разные пороги и исключения, часть проверок идёт как «warning», а часть — как «error». Это удобно описывать как код: конфиг-профили сканирования, .trivyignore(.yaml), преднастроенные форматы отчётов, репозиторные правила. Кроме того, Trivy поддерживает Rego-политики для выборочных игноров находок и расширенного контроля, используя ключи, доступные в вашей версии инструмента (например, загрузка Policy Bundle).

Совет: начните с простого профиля «CRITICAL блокирует релиз, HIGH помечаются как предупреждения». Через 2–4 недели пересмотрите долю «ложного шума» и закройте его точечными исключениями с истечением срока. Потом постепенно ужесточайте пороги.

GitHub Actions: минимальная рабочая конфигурация

Ниже пример, где первый шаг прогревает базу, второй — выполняет «строгий» скан и падает при HIGH/CRITICAL, а третий сохраняет SBOM и JSON-отчёт артефактами:

name: security-scan
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  trivy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Docker build
        run: docker build -t myrepo/app:${{ github.sha }} .

      - name: Trivy DB warmup
        run: trivy image --download-db-only

      - name: Trivy image scan (fail on HIGH+)
        run: trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed --no-progress myrepo/app:${{ github.sha }}

      - name: Trivy SBOM and reports
        if: always()
        run: |
          trivy sbom --format cyclonedx --output sbom.cdx.json myrepo/app:${{ github.sha }}
          trivy image --format json --output trivy-vuln.json myrepo/app:${{ github.sha }}

      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: trivy-reports
          path: |
            trivy-vuln.json
            sbom.cdx.json

GitLab CI: кеш и строгий порог

В GitLab удобно вынести кэш Trivy в общий ключ и разделить шаги:

stages:
  - build
  - scan

cache:
  key: trivy-cache
  paths:
    - .trivy-cache/

variables:
  TRIVY_CACHE_DIR: .trivy-cache

build:
  stage: build
  script:
    - docker build -t registry.example.com/app:$CI_COMMIT_SHA .

trivy:db:
  stage: scan
  script:
    - trivy image --download-db-only
  artifacts:
    expire_in: 1 day
    paths:
      - .trivy-cache/

trivy:scan:
  stage: scan
  dependencies: ["trivy:db"]
  script:
    - trivy image --skip-db-update --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed registry.example.com/app:$CI_COMMIT_SHA
  artifacts:
    when: always
    paths:
      - trivy-vuln.json
      - sbom.cdx.json
  after_script:
    - trivy image --skip-db-update --format json --output trivy-vuln.json registry.example.com/app:$CI_COMMIT_SHA
    - trivy sbom --format cyclonedx --output sbom.cdx.json registry.example.com/app:$CI_COMMIT_SHA

Jenkins Pipeline: быстрый ввод

Jenkins традиционно даёт большую гибкость. Пример Jenkinsfile с отдельными стадиями и строгим порогом:

pipeline {
  agent any
  stages {
    stage('Build') {
      steps {
        sh 'docker build -t myrepo/app:${BUILD_NUMBER} .'
      }
    }
    stage('Trivy DB warmup') {
      steps {
        sh 'trivy image --download-db-only'
      }
    }
    stage('Scan (fail on HIGH+)') {
      steps {
        sh 'trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed myrepo/app:${BUILD_NUMBER}'
      }
    }
    stage('Reports') {
      when { expression { true } }
      steps {
        sh 'trivy image --format json --output trivy-vuln.json myrepo/app:${BUILD_NUMBER}'
        sh 'trivy sbom --format cyclonedx --output sbom.cdx.json myrepo/app:${BUILD_NUMBER}'
        archiveArtifacts artifacts: 'trivy-vuln.json, sbom.cdx.json', fingerprint: true
      }
    }
  }
}

Сканирование до сборки: Dockerfile, репозиторий, конфигурации

Критичные находки лучше ловить раньше, чем вы потратите минуты CI на сборку. Добавьте лёгкие проверки до сборки образа:

  • trivy fs . — обнаружение известных уязвимых зависимостей и секретов в репозитории.
  • trivy config . — анализ конфигураций IaC и манифестов на мисконфиги.
  • Быстрый линт Dockerfile: базовый образ, слой с установкой пакетов, удаление кэшей менеджеров пакетов, пользователь по умолчанию.

Эти шаги обычно не «роняют» пайплайн на первых итерациях — пусть отчёты копятся, а команда исправляет постепенно. После стабилизации можно включать более строгие пороги.

Скан удалённого образа в реестре и «поздние» уязвимости

Уязвимости появляются и после релиза: базы обновляются ежедневно. Полезно добавить периодические джобы, которые сканируют образы по тегам/дижестам прямо в реестре. Пример:

trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed registry.example.com/app:1.4.2

Так вы поймаете «поздние» критики и запустите процедуру внепланового обновления. Для приватного реестра удобно настроить автоматический TLS и реверс‑прокси на входе.

Практические советы по Dockerfile для снижения риска

Trivy лишь выявляет проблемы. Чтобы их было меньше, придерживайтесь нескольких правил при сборке образов:

  • Выбирайте минималистичные базовые образы и своевременно обновляйте теги (не застревайте на «latest» без контроля).
  • Устанавливайте пакеты точечно и удаляйте кэш менеджера пакетов в том же слое, чтобы не тащить лишнее в финальный образ.
  • Следите за апстримом: обновление базового образа нередко «выносит» десятки уязвимостей без единой строки вашего кода.
  • Разделяйте сборку и рантайм через multi-stage build; финальный образ должен содержать только необходимое.
  • Запускайте процесс под непривилегированным пользователем; многие сканеры (и аудиторы) отмечают это как критичную практику безопасности.

Расширения: секреты, лицензии и комбинированные профили

Помимо уязвимостей, постепенно включайте другие сканеры:

  • Секреты (--scanners secret): быстрый способ отловить случайно закоммиченные токены/ключи.
  • Лицензии (--scanners license): контроль нежелательных OSS-лицензий в зависимостях.
  • Конфигурации (trivy config): проверка IaC/Kubernetes на мисконфиги.

Комбинируйте проверки по приоритету: секреты и критичные мисконфиги чаще всего заслуживают блокирующего порога, лицензии — предупреждения и отчёт.

Диагностика и устойчивость пайплайна

Иногда скан «шевелит» нестабильные сети или артефакт-реестры. Несколько флагов повышают устойчивость:

  • --timeout — увеличить таймаут сетевых операций.
  • --retry и --retry-max-backoff (если доступны в вашей версии) — на случай непредсказуемых сбоев.
  • --quiet — для чистых логов без прогресса и лишних сообщений.

Отдельно контролируйте размер логов: текстовые таблицы удобны глазу, но для артефактов лучше JSON или SARIF. Включите ротацию артефактов в CI и храните долгосрочно только то, что нужно для аудита. Для централизации логов сборки и инфраструктуры пригодится материал про логирование в JSON и отправку в Loki/ELK.

Как подойти к «идеальному» fail-threshold

Единого универсального порога не существует. На практике хорошо работает поэтапное внедрение:

  1. Неделя 1–2: блокируем только CRITICAL, собираем отчёты по HIGH в артефакты.
  2. Неделя 3–4: закрываем «быстрые» HIGH, документируем исключения с истечением срока.
  3. Далее: поднимаем порог до «падать на HIGH+», оставляя редкие исключения под контроль.

При таком подходе команда не тонет в «красных» билдах, но движется к реальной безопасности, а не к «галочке» в чек-листе.

Частые ошибки и как их избежать

  • Скан только локально. Решение — автоматизировать в CI/CD, назначить пороги и артефакты отчётов.
  • Отсутствие кэша базы. Решение — кэшировать TRIVY_CACHE_DIR и разделять обновление БД и скан.
  • Слишком жёсткий порог с первого дня. Итог — постоянные «красные» сборки. Решение — staged rollout порогов.
  • Вечные исключения. Решение — использовать .trivyignore.yaml с expiresOn и регулярным пересмотром.
  • Скан только до публикации. Решение — периодический пост-релизный скан по дижесту.

Итоги

Trivy закрывает 80% задач container security в CI/CD с минимальной ценой внедрения. Комбинация фильтра по severity, строгого --exit-code как fail-threshold, дисциплины исключений и регулярной генерации SBOM превращает «сканер по требованию» в надёжный контрольный пункт конвейера. Начните с простого: прогрейте базу, сканируйте собранный образ, блокируйте только CRITICAL и складывайте отчёты в артефакты. Через месяц у вас будет достаточно данных, чтобы принять зрелую политику для HIGH и добавлять скан мисконфигов, секретов и лицензий. Безопасность — это не разовый акт, а привычка, и Trivy встраивает её прямо в ваш процесс.

Поделиться статьей

Вам будет интересно

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...