Сканирование Trivy (trivy) проверяет проект сканером Trivy с открытым исходным кодом. Сканирование обязательное: политика выполнения сканирования добавляет его задачи в пайплайны проекта, и сканируемый проект не может отключить сканирование или изменить его настройки. Политика хранится в файле .deckhouse_code/security-policies/policy.yml отдельного проекта политик; .gitlab-ci.yml сканируемого проекта при этом не меняется. Сканирование выполняется отдельными задачами модулей: сканирование зависимостей, сканирование лицензий, обнаружение секретов, сканирование образов контейнеров и сканирование кластеров Kubernetes. Все настройки Trivy задаются в политике; интеграции в разделе «Настройки» → «Интеграции» у этого сканирования нет.

Что делает сканирование

Модули, их задачи и отчёты:

МодульЗадачаОтчётыЧто приводит задачу к ошибке
«Сканирование зависимостей»trivy_scadependency_scanning, cyclonedx, junitНесоответствие, достигшее общего порога блокировки
«Сканирование лицензий»trivy_licensecyclonedx, junitНесоответствие, достигшее общего порога блокировки
«Обнаружение секретов»trivy_secretssecret_detection, junitЛюбой секрет (раздел «Обнаружение секретов»)
«Сканирование образа контейнера»trivy_container, по одной задаче на образcontainer_scanning, junitНесоответствие, достигшее общего порога блокировки
«Сканирование кластеров Kubernetes»trivy_k8scluster_image_scanning, junitНесоответствие, достигшее общего порога блокировки

Эти правила действуют для каждого модуля и каждой его задачи:

  • Все задачи выполняются на этапе пайплайна, который называет политика (раздел «Этап задач сканирования»), по умолчанию — fe-security-scanner. У задач нет needs, поэтому они ждут все более ранние этапы пайплайна и получают их артефакты.
  • Имя задачи начинается с имени задачи из таблицы выше. Остальная часть имени называет политику, которая добавила задачу, а для модуля образов контейнеров — ещё и строку с образом. Например, trivy_sca:trivy-everywhere-p42 — задача сканирования зависимостей политики Trivy everywhere из проекта политик с ID 42. Имя одинаково в пайплайнах пуша и в пайплайнах по расписанию. Кириллица в имени политики транслитерируется: Ночной скан превращается в nochnoi-skan.
  • Каждая политика, которая требует Trivy, получает собственные задачи. Это верно и тогда, когда Trivy требуют несколько политик одного policy.yml. Задачи различаются частью имени, которая называет политику, и сохраняют stage, tags и variables своей политики. Поэтому две политики с одинаковыми настройками сканируют дважды. Две политики одного файла не могут иметь одинаковое имя. Два разных имени, которые дают одинаковую часть имени задачи, например Trivy everywhere и trivy-everywhere, сохраняются. Тогда их задачи получают одно имя, и выполняется только одна из них.
  • Первая строка лога каждой задачи называет политику, которая её добавила: This scan runs because the security policy "<name>" mandates it. Policies: <URL>.
  • Каждая задача выгружает артефакты с when: always, поэтому отчёты сохраняются, даже если задача завершилась с ошибкой.
  • Перед запуском Trivy задача удаляет из окружения все переменные TRIVY_* и пишет об этом в лог: Trivy: <NAME> is set in this job's environment and is ignored.
  • Сервер записывает настройки политики прямо в задачу, и переменные CI/CD сканируемого проекта с такими же именами их не меняют.
  • Задачи прерываемые: если в проекте включена настройка «Автоматическая отмена избыточных пайплайнов», более новый пайплайн той же ветки отменяет их.
  • Задачи всегда сканируют отправленный коммит. Настройки Git сканируемого проекта на это не влияют. Задачи загружают всю историю (GIT_STRATEGY=fetch, GIT_DEPTH=0) и удаляют из рабочей копии все неотслеживаемые файлы (GIT_CLEAN_FLAGS=-ffdx). Поэтому первая загрузка большого репозитория занимает больше времени. Политика может задать другие значения в variables действия. Исключение — задача trivy_k8s: она не читает репозиторий и ничего не загружает (GIT_STRATEGY=none).

Этап задач сканирования

Ключ stage задаёт этап пайплайна для всех сканирований политики. В редакторе политик ключу соответствует поле «Этап пайплайна». Этап один на всю политику. Если поле пустое, редактор не записывает ключ stage.

stage в политикеГде выполняются задачи сканирования
Не заданНа этапе fe-security-scanner, который политика добавляет в конец этапов проекта, перед .post
Этап, который перечислен в stages: проектаНа этом этапе, там, где его поставил проект
Этап, которого нет у проектаНа этом этапе, который политика добавляет в конец этапов проекта, перед .post

Для проекта со stages: [build, test, deploy] значение stage: test запускает сканирования на этапе test. Для того же проекта значение stage: security даёт этапы build, test, deploy, security. Чтобы поставить сканирования в другое место, перечислите этап в собственном stages: проекта в нужной позиции. Можно перечислить и fe-security-scanner.

Если политика называет этап, который есть у проекта, проект определяет, когда запускается обязательное сканирование. Проект может поставить этот этап раньше сборки. Пайплайн по расписанию и пайплайн проекта без .gitlab-ci.yml содержат только сканирования, поэтому в них политика всегда добавляет этап сама.

Редактор отклоняет имя этапа:

  • из одних пробелов;
  • с пробелом в начале или в конце;
  • длиннее 255 символов;
  • .pre и .post.

Для .post редактор выводит ошибку Сканирования не могут выполняться на этапе .post.

Если политика с таким stage написана вручную, пайплайн её отбрасывает. Остальные политики файла продолжают работать.

Предварительные требования

Прежде чем сканирование сможет запуститься, убедитесь, что:

  • администратор включил флаг функций fe_security_scan_policies так, как описано на странице Ранний доступ;
  • проект политик привязан к проекту или к группе выше него;
  • проекту доступен GitLab Runner с executor docker, который может загрузить образ сканирования (раздел «Образ сканирования и базы уязвимостей») и разрешает политику загрузки always (раздел «Политика загрузки образа на раннере»);
  • раннер работает на linux/amd64, либо политика указывает образ сканирования, собранный под архитектуру раннера;
  • для модуля образов контейнеров образ существует к моменту запуска сканирования: задача сборки отправляет его в хранилище образов или сохраняет как артефакт на более раннем этапе;
  • для модуля кластеров к кластеру подключён агент для Kubernetes, и он разрешает сканируемому проекту доступ в своём ci_access (раздел «Доступ к кластеру»).

Включение сканирования

Политику можно составить в редакторе политик или написать в формате YAML.

  • Режим правил
  • Режим .yaml
  1. Откройте проект или группу и перейдите в «Безопасность» → «Политики».
  2. Нажмите «Новая политика», затем в карточке «Политика выполнения сканирования» нажмите одноимённую кнопку.
  3. В поле «Имя» введите имя политики, например Trivy everywhere.
  4. В разделе «Когда запускать сканирования» отметьте, какие пайплайны сканировать, например «На любом пайплайне».
  5. Если политика должна запускать только Trivy, в разделе «Сканирования безопасности» снимите флажок «Включить этот сканер» у сканирования «Обнаружение секретов (встроенный Gitleaks)».
  6. Раскройте «Trivy (встроенный)» и установите флажки модулей, которые нужно включить, например «Сканирование зависимостей» и «Обнаружение секретов».
  7. Нажмите «Настройка с запросом на слияние».

Trivy в разделе Сканирования безопасности редактора политик

Новая политика открывается с отмеченными условиями «Запросы на слияние» и «Ветка по умолчанию» и с включённым сканированием «Обнаружение секретов (встроенный Gitleaks)». Флажок «На любом пайплайне» снимает и блокирует флажки «Запросы на слияние», «Ветка по умолчанию» и «По имени ветки». В блоке «Предпросмотр .yaml» справа от формы редактор показывает политику, которую запишет. Свёрнутый блок сканера показывает в заголовке, запускает ли политика сканер:

  • «Отключён», если сканер выключен;
  • включённые модули у «Trivy (встроенный)»;
  • «Включён» у сканера без модулей.

У сканера без настроенной интеграции заголовок дополнительно показывает «Нужна интеграция».

Если к проекту ещё не привязан проект политик, при сохранении редактор создаёт его и привязывает: для проекта — <путь проекта>-security-policy-project в той же группе, для группы — <путь группы>-security-policy-project внутри неё. Затем редактор открывает в проекте политик запрос на слияние с политикой; политика начинает действовать, когда этот запрос на слияние принят.

Переключите редактор в «Режим .yaml» над формой или отредактируйте файл .deckhouse_code/security-policies/policy.yml в проекте политик напрямую.

Политика Trivy в режиме .yaml

Добавьте действие scan: trivy. Каждый модуль включается собственной переменной со значением 'true': FE_SCANS_TRIVY_SCA, FE_SCANS_TRIVY_LICENSE, FE_SCANS_TRIVY_SECRETS, FE_SCANS_TRIVY_CONTAINER, FE_SCANS_TRIVY_K8S. Схема политики отклоняет действие trivy, если ни одна из них не названа.

scan_execution_policy:
  - name: Trivy everywhere
    enabled: true
    rules:
      - type: pipeline
        branch_type: all
    actions:
      - scan: trivy
        variables:
          FE_SCANS_TRIVY_SCAN_IMAGE: 'registry.example.com/trivy-with-db:0.74.0'
          FE_SCANS_TRIVY_FAIL_ON: high
          FE_SCANS_TRIVY_SCA: 'true'
          FE_SCANS_TRIVY_DETECTION_PRIORITY: precise
          FE_SCANS_TRIVY_SECRETS: 'true'

FE_SCANS_TRIVY_SCAN_IMAGE обязательна: политику без неё сохранить нельзя, и ошибка называет переменную. Политика, написанная вручную, может не указывать FE_SCANS_TRIVY_FAIL_ON и FE_SCANS_TRIVY_DETECTION_PRIORITY: тогда сканирование выполняется с порогом high и приоритетом precise.

Политика, которую вы сами добавили в проект политик, действует, только когда этот проект политик привязан: к проекту — кнопкой «Редактировать проект политик» в разделе «Безопасность» → «Политики», к группе — в разделе «Настройки» → «Политика безопасности».

Каждый пайплайн, который подходит под правила политики, получает задачи включённых модулей; в примере выше это trivy_sca и trivy_secrets.

Флажок модуля записывает в политику переменную, которая включает модуль, и значения по умолчанию:

ФлажокЧто записывается в политику
«Сканирование зависимостей»FE_SCANS_TRIVY_SCA: 'true', FE_SCANS_TRIVY_DETECTION_PRIORITY: precise
«Сканирование лицензий»FE_SCANS_TRIVY_LICENSE: 'true'
«Обнаружение секретов»FE_SCANS_TRIVY_SECRETS: 'true'
«Сканирование образа контейнера»FE_SCANS_TRIVY_CONTAINER: 'true', FE_SCANS_TRIVY_CONTAINER_IMAGES: $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG, FE_SCANS_TRIVY_CONTAINER_DETECTION_PRIORITY: precise
«Сканирование кластеров Kubernetes»FE_SCANS_TRIVY_K8S: 'true'

Флажок первого включённого модуля дополнительно заполняет поле «Образ сканирования» блока «Общие настройки» образом текущего релиза и записывает FE_SCANS_TRIVY_FAIL_ON: high. Отмеченный модуль раскрывает свой блок настроек. У каждого блока есть подсказка рядом с заголовком.

Общие настройки

Блок «Общие настройки» применяется к каждому включённому модулю и не имеет собственного флажка.

  • Режим правил
  • Режим .yaml

Блок Общие настройки

Поля «Проект с trivy.yml» и «Проект с trivy-secret.yaml» появляются, только когда заполнено поле файла над ними; очистка поля файла убирает проект из политики.

Эти поля — переменные действия trivy, а «Теги раннера» — список tags этого действия:

actions:
  - scan: trivy
    variables:
      FE_SCANS_TRIVY_SCAN_IMAGE: 'registry.example.com/trivy-with-db:0.74.0'
      FE_SCANS_TRIVY_CONFIG_FILE: trivy.yml
      FE_SCANS_TRIVY_CONFIG_PROJECT: security/trivy-config
      FE_SCANS_TRIVY_SECRET_CONFIG: trivy-secret.yaml
      FE_SCANS_TRIVY_SECRETS_RULES_PROJECT: security/trivy-config
      FE_SCANS_TRIVY_FAIL_ON: high
      FE_SCANS_TRIVY_SCA: 'true'
    tags:
      - docker
ПолеПеременнаяЧто задаёт
«Образ сканирования»FE_SCANS_TRIVY_SCAN_IMAGEОбраз, в котором выполняется задача каждого модуля, со встроенными базами уязвимостей
«Переменная логина реестра»FE_SCANS_REGISTRY_LOGIN_VARIABLEПеременная CI/CD в виде $ИМЯ, в которой хранится логин для хранилища образа сканирования. Задаётся вместе с переменной пароля или не задаётся совсем; см. Учётные данные хранилища образов для сканера
«Переменная пароля реестра»FE_SCANS_REGISTRY_PASSWORD_VARIABLEПеременная CI/CD в виде $ИМЯ, в которой хранится пароль или токен для этого хранилища
«Файл конфигурации trivy.yml»FE_SCANS_TRIVY_CONFIG_FILEПуть к файлу конфигурации Trivy, общему для всех модулей. Пусто — файл не передаётся
«Проект с trivy.yml»FE_SCANS_TRIVY_CONFIG_PROJECTПроект, из которого читается trivy.yml, в формате group/project. Пусто — файл читается из сканируемого репозитория
«Файл конфигурации trivy-secret.yaml»FE_SCANS_TRIVY_SECRET_CONFIGПуть к правилам и спискам исключений поиска секретов. Пусто — встроенные правила Trivy
«Проект с trivy-secret.yaml»FE_SCANS_TRIVY_SECRETS_RULES_PROJECTПроект, из которого читается trivy-secret.yaml. Пусто — файл читается из сканируемого репозитория
«Учитывать .trivyignore и .trivyignore.yml в сканируемом проекте»FE_SCANS_TRIVY_USE_IGNOREFILEПо умолчанию выключено. Действует на все модули, кроме модуля кластеров. Подробнее — под таблицей
«Порог блокировки»FE_SCANS_TRIVY_FAIL_ONПорог задач сканирования зависимостей, лицензий, образов контейнеров и кластеров, по умолчанию «Высокий и выше» (раздел «Блокировка»)
«Теги раннера»tags действияТеги раннера через запятую; применяются к задаче каждого модуля

«Образ сканирования» редактор заполняет образом текущего релиза, trivy-with-db:0.74.0 из хранилища образов продукта, и политика продолжает использовать этот образ после обновления продукта, пока кто-нибудь не отредактирует поле. Образа по умолчанию на сервере нет. Пока включён хотя бы один модуль, поле обязательно, и форму с пустым полем сохранить нельзя. Сервер не сохраняет политику без FE_SCANS_TRIVY_SCAN_IMAGE, даже если она написана вручную. Ошибка называет переменную. Политика без образа, которая уже лежит в проекте политик, продолжает действовать: её остальные сканирования выполняются. Вместо задач Trivy пайплайн получает одну задачу trivy_no_image. Она сразу завершается с ошибкой и называет FE_SCANS_TRIVY_SCAN_IMAGE и поле «Образ сканирования». Значение — полная ссылка на образ с тегом, например registry.example.com/trivy-scan:0.74.0, либо с дайджестом (@sha256: и 64 шестнадцатеричных символа). Ссылка без тега или дайджеста и ссылка с символом $ отклоняются. Сервер записывает образ в задачу, поэтому сканируемый проект не может его изменить.

Флажок «Учитывать .trivyignore и .trivyignore.yml в сканируемом проекте» по умолчанию выключен: задача передаёт пустой список игнорирования, и .trivyignore сканируемого проекта не может скрыть несоответствие. Если флажок установлен, задача ищет в корне репозитория сначала .trivyignore.yml, затем .trivyignore.yaml и применяет первый найденный файл; если не найден ни один, Trivy читает .trivyignore. Лог называет применённый файл и сообщает, когда .trivyignore пропущен из-за использования YAML-файла. Модуль образов контейнеров тоже читает файл игнорирования из репозитория, а не из образа. Модуль кластеров никогда не читает файл игнорирования, при любом состоянии флажка: его задача всегда передаёт --ignorefile /dev/null. Включённый флажок позволяет разработчику сканируемого проекта скрыть несоответствие без ведома автора политики. Формат описан в справочнике Trivy по фильтрам.

Настройки каждого модуля

Каждый блок модуля содержит собственные поля «Пропускать каталоги», «Пропускать файлы» и «Дополнительные аргументы»; значение действует только на задачу этого модуля, потому что модули выполняются отдельными задачами. У модуля кластеров есть «Дополнительные аргументы» и собственное поле «Тайм-аут», а списков пропуска нет (раздел «Сканирование кластеров Kubernetes»). В имени переменной <MODULE> — это SCA, LICENSE, SECRETS или CONTAINER, в зависимости от модуля, которому принадлежит поле:

ПолеПеременнаяЧто задаёт
«Пропускать каталоги»FE_SCANS_TRIVY_<MODULE>_SKIP_DIRSПо одному каталогу или шаблону в строке. Для модуля образов контейнеров — пути внутри файловой системы образа
«Пропускать файлы»FE_SCANS_TRIVY_<MODULE>_SKIP_FILESПо одному файлу или шаблону в строке. Для модуля образов контейнеров — пути внутри образа
«Дополнительные аргументы»FE_SCANS_TRIVY_<MODULE>_EXTRA_ARGSФлаги, которые передаются в запуск Trivy этого модуля как есть, для флагов, которых нет в форме

«Дополнительные аргументы» ставятся в командной строке перед флагами формы, поэтому поле формы имеет приоритет над тем же флагом, написанным здесь. Файл конфигурации передаётся последним, поэтому «Дополнительные аргументы» не могут направить сканирование на другой файл конфигурации. Флаг, который сужает отчёт, например --severity, приводит к тому, что сканирование сообщает меньше, чем нашло. Флаг, заданный формой, полностью заменяет одноимённый ключ в trivy.yml: scan.skip-files в trivy.yml не действует, если заполнено поле «Пропускать файлы».

Тайм-аут модуля задаётся флагом --timeout в поле «Дополнительные аргументы», например --timeout 15m; подсказка поля приводит его как пример. Ключ timeout в общем trivy.yml задаёт тайм-аут для всех модулей, а --timeout его переопределяет. Модуль кластеров — исключение. Он всегда передаёт --timeout из своего поля «Тайм-аут». Если поле пустое, модуль передаёт 20m. Ключ timeout из trivy.yml на модуль не действует. Если не задано ни то ни другое, действует значение Trivy по умолчанию, 5 минут, которого может не хватить большому репозиторию. Некорректное значение не проверяют ни форма, ни схема политики: Trivy отклоняет его при запуске, и задача завершается с ошибкой (раздел «Trivy отклоняет значение тайм-аута»). Примеры в разделах модулей ниже показывают эти поля заполненными.

Сканирование зависимостей

Модуль находит в рабочей копии репозитория пакеты операционной системы и зависимости приложения с известными уязвимостями. Собственные поля модуля:

ПолеПеременнаяЧто задаёт
«Связи пакетов»FE_SCANS_TRIVY_PKG_RELATIONSHIPSКакие связи зависимостей попадают в отчёт, через запятую из root, workspace, direct, indirect. Пусто — все
«Приоритет обнаружения»FE_SCANS_TRIVY_DETECTION_PRIORITYprecise (по умолчанию у Trivy) сообщает только то, что Trivy относит к зависимости уверенно; comprehensive сообщает больше, вместе с большим числом ложных срабатываний

В блоке у флажков root, workspace, direct и indirect нет подписи «Связи пакетов»: они стоят первыми, сразу под заголовком блока. У каждого значения есть подсказка:

ЗначениеЧто означает
rootСама цель сканирования
workspaceОбъявлено в манифесте одного из пакетов, когда несколько пакетов лежат в одном репозитории
directОбъявлено в манифесте самого проекта
indirectПодтянуто другим пакетом

Несколько значений записываются через запятую, например direct,indirect.

  • Режим правил
  • Режим .yaml

Блок Сканирование зависимостей

В примере в отчёт попадают только прямые зависимости, каталог vendor пропускается, а тайм-аут увеличен до 15 минут.

actions:
  - scan: trivy
    variables:
      FE_SCANS_TRIVY_SCA: 'true'
      FE_SCANS_TRIVY_PKG_RELATIONSHIPS: direct
      FE_SCANS_TRIVY_DETECTION_PRIORITY: precise
      FE_SCANS_TRIVY_SCA_SKIP_DIRS: vendor
      FE_SCANS_TRIVY_SCA_EXTRA_ARGS: '--timeout 15m'

Многострочное поле, например «Пропускать каталоги» с несколькими каталогами, записывается в YAML как блок с одним значением в строке (|-).

Задача запускает Trivy дважды: один раз — чтобы записать SBOM (Software Bill of Materials) в формате CycloneDX, gl-sbom.cdx.json, второй — чтобы найти уязвимости. Из SBOM данные берут страницы «Список зависимостей» и «Соответствие лицензий». «Связи пакетов» и «Приоритет обнаружения» действуют только на поиск уязвимостей, поэтому SBOM всегда содержит всё дерево зависимостей, а пакет, отфильтрованный из отчёта об уязвимостях, остаётся в списке зависимостей. Неудачный запуск, который строит SBOM, только выводит предупреждение и не влияет на найденные уязвимости. Несоответствия ниже порога блокировки остаются в отчёте: порог решает только исход задачи. Сканирование ничего не загружает и читает базы уязвимостей, встроенные в образ сканирования. Если одной из баз в образе нет, задача останавливается до сканирования (раздел «Диагностика»). Зависимости, которые задача сборки проекта передала через cache: или artifacts:, оказываются в рабочем каталоге задачи и тоже сканируются.

Сканирование лицензий

Модуль определяет категории лицензий пакетов, которые находит Trivy. Собственные поля модуля:

ПолеПеременнаяЧто задаёт
«Полное сканирование»FE_SCANS_TRIVY_LICENSE_FULLПо умолчанию выключено. Включено: Trivy читает тексты лицензий и заголовки файлов в дереве исходного кода вместо метаданных пакетов. Медленнее и сообщает больше, включая ложные совпадения
«Игнорируемые лицензии»FE_SCANS_TRIVY_LICENSE_IGNOREDПо одному идентификатору SPDX в строке, например MIT или Apache-2.0: несоответствия по ним отбрасываются
«Уверенность совпадения»FE_SCANS_TRIVY_LICENSE_CONFIDENCEЧисло от 0 до 1: насколько точно текст лицензии должен совпасть при полном сканировании. Значение Trivy по умолчанию — 0.9; меньшее значение сообщает о более слабых совпадениях
  • Режим правил
  • Режим .yaml

Блок Сканирование лицензий

В примере включено «Полное сканирование».

actions:
  - scan: trivy
    variables:
      FE_SCANS_TRIVY_LICENSE: 'true'
      FE_SCANS_TRIVY_LICENSE_FULL: 'true'

Каждая лицензия попадает в одну из категорий, и у каждой категории своя критичность:

КатегорияКритичность
forbiddenКритическая
restrictedВысокая
reciprocalСредняя
notice, permissive, unencumberedНизкая

Лицензия, которую Trivy не распознаёт, считается неизвестной и не приводит задачу к ошибке ни при каком пороге. При пороге по умолчанию «Высокий и выше» лицензия категории forbidden или restricted приводит задачу к ошибке. Категория лицензии задаётся в trivy.yml (раздел «Категории лицензий в trivy.yml») и описана в справочнике Trivy по сканеру лицензий.

Задача выгружает отчёт JUnit и SBOM gl-sbom-license.cdx.json. Отчёта безопасности у неё нет: проверка лицензии отражается как результат теста, и несоответствия по лицензиям видны только на вкладке «Тесты». Страница «Соответствие лицензий» и столбец «Лицензия» страницы «Список зависимостей» объединяют лицензии компонента из обоих SBOM: SBOM модуля зависимостей берёт лицензии из метаданных пакетов, а в SBOM этого модуля «Полное сканирование» добавляет лицензии из текстов исходного кода. Поэтому SBOM модуля лицензий только добавляет лицензии к тому, что показывают страницы. Модуль лицензий не использует базы уязвимостей.

Лицензии неустановленных зависимостей

Найдёт ли Trivy лицензию зависимости без установки пакетов, зависит от формата lock-файла. Одни lock-файлы несут лицензию каждого пакета; другие — только имя и версию, и тогда Trivy читает лицензию из метаданных установленного пакета: node_modules/<package>/package.json для npm, .dist-info/METADATA для Python, установленный gemspec для Ruby. Лицензии Go Trivy всегда берёт из кеша модулей.

Lock-файлЛицензия без установки пакетов
package-lock.json с lockfileVersion 2 или 3Да
composer.lockДа
package-lock.json с lockfileVersion 1Нет
yarn.lock версии 1Нет
Cargo.lockНет
Gemfile.lockНет
poetry.lockНет

То же самое верно для лицензий в SBOM, который пишет модуль зависимостей: там, где lock-файл не несёт лицензию и пакеты не установлены, столбец «Лицензия» списка зависимостей остаётся пустым.

Задача сканирования не устанавливает зависимости проекта. Устанавливайте их на более раннем этапе в .gitlab-ci.yml сканируемого проекта, в его собственном образе, и передавайте дальше через cache: или artifacts::

install:
  stage: build
  image: node:20
  script:
    - npm ci
  cache:
    key:
      files: [package-lock.json]
    paths:
      - node_modules/

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

Категории лицензий в trivy.yml

Категории — forbidden, restricted, reciprocal, notice, permissive и unencumbered; у каждой лицензии, которую распознаёт Trivy, есть встроенная категория (у MIT это notice). Если перечислить лицензию под категорией в trivy.yml, она добавляется в эту категорию и остаётся во встроенной. Лицензия оказывается сразу в двух категориях, и Trivy заново выбирает одну из них при каждом запуске, независимо от того, менялся ли код или конфигурация. Поэтому проверка лицензии на неизменном коде то проходит, то нет: при пороге по умолчанию «Высокий и выше» запуск, выбравший forbidden (критическая), приводит задачу к ошибке, а запуск, выбравший notice (низкая), завершается успешно.

Чтобы это исправить, переопределите также встроенную категорию лицензии. Тогда лицензия останется ровно в одной категории. Например, для MIT:

license:
  forbidden:
    - MIT
  notice: []

Результат стабилен, если лицензия перечислена под своей же встроенной категорией (license.notice: [MIT]), или если ни один просканированный пакет не несёт эту лицензию.

Обнаружение секретов

Модуль ищет в репозитории секреты, попавшие в коммиты. Собственное поле модуля:

ПолеПеременнаяЧто задаёт
«Не блокировать на находках»FE_SCANS_TRIVY_SECRETS_REPORT_ONLYПо умолчанию выключено. Включено: несоответствия попадают в отчёт, а задача завершается успешно
  • Режим правил
  • Режим .yaml

Блок Обнаружение секретов

В примере пропускается каталог test/fixtures: иначе тестовые ключи в нём были бы отмечены как утечка секретов.

actions:
  - scan: trivy
    variables:
      FE_SCANS_TRIVY_SECRETS: 'true'
      FE_SCANS_TRIVY_SECRETS_SKIP_DIRS: test/fixtures

Любой секрет приводит задачу к ошибке независимо от его критичности. Флажок «Не блокировать на находках» присваивает задаче вместо этого порог report_only. Модуль читает только файлы рабочей копии на сканируемом коммите и не просматривает историю git, поэтому секрет, добавленный в одном коммите и убранный в следующем, не находится. Лог сообщает об этом: Trivy: searching the checked-out tree for secrets; git history is not read. Модуль не использует базы уязвимостей и работает на раннере без доступа в интернет. Модуль читает trivy.yml и trivy-secret.yaml, названные в блоке «Общие настройки» (раздел «Файлы конфигурации»).

Сканирование образов контейнеров

Модуль сканирует образ, собранный пайплайном: пакеты операционной системы его базового образа и всё, что добавлено во время сборки. Несоответствия попадают в отчёт об уязвимостях как сканирование контейнера. Собственные поля модуля:

ПолеПеременнаяЧто задаёт
«Список образов для сканирования»FE_SCANS_TRIVY_CONTAINER_IMAGESОбязательное. По одному образу в строке, не больше 20 строк (раздел «Список образов для сканирования»)
«Также сканировать зависимости приложения внутри образа»FE_SCANS_TRIVY_CONTAINER_APP_DEPSПо умолчанию выключено: сканируются только пакеты операционной системы образа
«Не проваливать задачу, если по адресу нет образа»FE_SCANS_TRIVY_CONTAINER_ALLOW_MISSINGПо умолчанию выключено: отсутствующий образ приводит задачу к ошибке
«Приоритет обнаружения»FE_SCANS_TRIVY_CONTAINER_DETECTION_PRIORITYprecise или comprehensive, как в модуле зависимостей
  • Режим правил
  • Режим .yaml

Блок Сканирование образа контейнера

В примере заданы образ, который отправил пайплайн, образ, который задача сборки сохранила как артефакт, и базовый образ из другого хранилища. Блок «Подсказка по заполнению списка образов» под полем объясняет формы строк и допустимые переменные.

actions:
  - scan: trivy
    variables:
      FE_SCANS_TRIVY_CONTAINER: 'true'
      FE_SCANS_TRIVY_CONTAINER_DETECTION_PRIORITY: precise
      FE_SCANS_TRIVY_CONTAINER_IMAGES: |-
        $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG
        artifact:dist/worker-image.tar
        registry.example.com/base-images/alpine:3.20

Задача читает образ из хранилища образов и никогда — из локального демона Docker на раннере: она выполняет trivy image --image-src remote. Образ, собранный на раннере и никуда не отправленный, задача не найдёт: отправьте его в хранилище или сохраните как артефакт.

Каждая строка поля «Список образов для сканирования» сканируется отдельной задачей со своим статусом. Имя задачи — trivy_container:<номер строки>-<цель> с суффиксом политики: <номер строки> считается с 1, а <цель> — строка как она написана в политике, без подстановки переменных, в нижнем регистре, с заменой остальных символов на -, обрезанная до 40 символов. Имя остаётся одинаковым от коммита к коммиту, поэтому «Итоги тестирования» запроса на слияние сопоставляют наборы тестов базовой и новой версии.

Задача ищет в образе и уязвимости, и секреты, независимо от того, включён ли модуль обнаружения секретов. Секрет, попавший в образ, оценивается по общему порогу блокировки, как и уязвимости образа (раздел «Блокировка»).

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

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

Лог задачи называет образ, который был фактически прочитан, вместе с его дайджестом: trivy_container: scanned <image>, digest sha256:…, <OS>. Для артефакта он выводит путь к файлу и ID образа.

Для этого модуля «Пропускать каталоги» и «Пропускать файлы» — пути внутри образа, а trivy.yml, trivy-secret.yaml и файл игнорирования берутся из репозитория: названного проекта или сканируемого. Уязвимость, которую дистрибутив пометил как not_affected, will_not_fix, end_of_life или fix_deferred, отражается как пропущенная и не учитывается порогом; то же верно для модуля зависимостей (раздел «Блокировка»). Уязвимый пакет базового образа устраняется пересборкой на более новом базовом образе; об этом задача сообщает, когда завершается с ошибкой.

Список образов для сканирования

Строка поля «Список образов для сканирования» имеет одну из следующих форм:

ФормаПодробности
Адрес, собранный из перечисленных ниже переменныхНапример, $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG
artifact:<путь>Образ, который задача сборки сохранила как артефакт: путь внутри рабочей копии репозитория, относительный, без .. и без шаблонов. Строка, начинающаяся с artifact:, всегда читается как путь, поэтому образ, который действительно называется artifact, указывайте полностью: docker.io/library/artifact:1.0
Полная ссылка на образ с тегом или дайджестомИз любого хранилища образов, например postgres:15-alpine или registry.example.com/group/project@sha256:<DIGEST>

Строка без тега или дайджеста отклоняется при сохранении политики; так же отклоняется одиночный $CI_REGISTRY_IMAGE. Флажок модуля записывает в поле строку $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG: по этому адресу образ отправляет сборка Auto DevOps. Те же сведения, что в этом разделе, редактор показывает под полем, в блоке «Подсказка по заполнению списка образов». Редактор помечает больше 20 строк и не даёт сохранить: «Не больше 20 строк, сейчас .»

В адресе разрешены только перечисленные ниже переменные; любая другая $ПЕРЕМЕННАЯ отклоняется при сохранении политики.

ПеременнаяЗначение
$CI_REGISTRY_IMAGEХранилище образов контейнеров сканируемого проекта, например registry.example.com/group/project
$CI_COMMIT_REF_SLUGВетка или тег в виде тега образа: feature/foo становится feature-foo. Для сборок, которые называют тег образа по ветке
$CI_COMMIT_SHAПолный SHA коммита пайплайна. Для сборок, которые называют тег образа по коммиту
$CI_COMMIT_TAGТег Git этого пайплайна. Строка с этой переменной пропускается в пайплайне ветки
$CI_APPLICATION_REPOSITORYСтандартный путь к образу: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG для ветки, $CI_REGISTRY_IMAGE для тега. Проект, который переопределил переменную для собственной сборки Auto DevOps, здесь не учитывается. Его образ указывайте полностью
$CI_APPLICATION_TAGСтандартный тег образа: $CI_COMMIT_SHA для ветки, $CI_COMMIT_TAG для тега

Сервер подставляет в задачу значения этих переменных из проекта, ветки и коммита пайплайна, с приоритетом над одноимённой переменной CI/CD сканируемого проекта, поэтому проект не может направить обязательное сканирование на другой образ. Строка пропускается в пайплайне, который не может её заполнить: строка с $CI_COMMIT_TAG в пайплайне ветки, или строка с $CI_REGISTRY_IMAGE либо $CI_APPLICATION_REPOSITORY, когда хранилище образов выключено для проекта или для экземпляра. Остальные задачи выводят в лог каждую пропущенную строку: Trivy container: line <N> skipped (<reason>): <line>. Когда ни одна строка не подходит пайплайну или поле пусто, выполняется одна задача trivy_container (плюс суффикс политики): она завершается с ошибкой, а при установленном флажке «Не проваливать задачу, если по адресу нет образа» — успешно, с сообщением в логе.

Пример поля из нескольких строк приведён в разделе «Сканирование образов контейнеров».

Учётные данные хранилища образов

Чтение образа требует учётных данных, которые зависят от того, где он хранится. Не помещайте учётные данные хранилища образов в политику: она хранит свои значения в открытом виде.

Если адрес образа совпадает с CI_REGISTRY, образ лежит в хранилище образов этой установки. Задача читает такой образ только собственным токеном CI/CD. Задача никогда не отправляет этот токен в другое хранилище. Образ из другого приватного проекта этой установки читается, только если тот проект разрешает доступ токену задачи сканируемого проекта: в проекте с образом откройте «Настройки» → «CI/CD» → «Права доступа токена заданий CI/CD» и добавьте туда сканируемый проект. Иначе задача завершается с ошибкой UNAUTHORIZED и DENIED: access forbidden. Для хранилища образов этой установки задача не читает учётные данные из переменных сканируемого проекта.

Образ в другом хранилище образов читается по DOCKER_AUTH_CONFIG либо по паре CS_REGISTRY_USER и CS_REGISTRY_PASSWORD из переменных CI/CD сканируемого проекта. DOCKER_AUTH_CONFIG — это JSON в формате ~/.docker/config.json:

{"auths": {"registry.example.com": {"auth": "<BASE64_USER_PASSWORD>"}}}

<BASE64_USER_PASSWORD> — это user:password, закодированное в Base64. Пароль передаётся в Trivy через стандартный ввод и не попадает в лог задачи, даже если переменная не замаскирована. Задача печатает команду Trivy, которую выполняет, без пароля.

Образ, сохранённый как артефакт

artifact:dist/image.tar читает $CI_PROJECT_DIR/dist/image.tar, и задача сборки того же пайплайна должна его там оставить:

  • задача сборки выполняется на этапе раньше этапа сканирований (по умолчанию fe-security-scanner), поскольку у задачи сканирования нет needs, и она загружает артефакты всех более ранних этапов;
  • файл перечислен в artifacts:paths и не истёк к моменту запуска сканирования;
  • образ сохранён вместе с тегом, например docker save myapp:1.2 -o dist/image.tar: имя образа в несоответствии берётся из тега внутри архива, поэтому docker save <ID образа> без тега даёт несоответствия без имени образа;
  • для запуска сканирования в пайплайнах запросов на слияние задаче сборки нужны rules: задача без rules или only не добавляется в пайплайн запроса на слияние, тогда как задачи политики добавляются в него всегда, и тогда задача строки artifact: завершается с ошибкой.

Например, в .gitlab-ci.yml сканируемого проекта:

build-image:
  stage: build
  rules:
    - when: on_success
  script:
    - docker build -t myapp:$CI_COMMIT_SHORT_SHA .
    - mkdir -p dist
    - docker save myapp:$CI_COMMIT_SHORT_SHA -o dist/image.tar
  artifacts:
    paths: [dist/image.tar]
    expire_in: 1 day

Артефакту не нужны ни хранилище образов, ни учётные данные.

Пайплайны без сборки образа

Задачи политики добавляются в пайплайны любого типа, включая пайплайны запросов на слияние, и обходят workflow:rules проекта:

  • проект без задач для пайплайнов запросов на слияние всё равно получает такой пайплайн, и в нём есть только сканирования;
  • настройка «пайплайн запроса на слияние вместо пайплайна ветки» через workflow:rules не убирает дублирование: пайплайн ветки остаётся, и в нём тоже есть только сканирования;
  • проект без собственного .gitlab-ci.yml всё равно получает пайплайны, в которых есть только задачи сканирования.

В пайплайне, где сборка не выполнялась, задачи строк artifact: и адресов, куда сборка должна была отправить образ, завершаются с ошибкой. Например, в пайплайне, который запускает правило политики с type: schedule, выполняются только задачи сканирования, и задачи строк artifact: в нём завершаются с ошибкой, а адрес хранилища образов работает, если образ там уже есть.

Политика, которой не нужно сканирование при каждой отправке изменений, может ограничить правило источниками пайплайна.

  • Режим правил
  • Режим .yaml

Снимите флажок «На любом пайплайне» и отметьте «Запросы на слияние» и «Ветка по умолчанию»: сканирование тогда выполняется в пайплайнах запросов на слияние, нацеленных на ветку по умолчанию, и в пайплайнах, которые запускает отправка изменений в ветку по умолчанию.

Раздел Когда запускать сканирования

Эти флажки записывают правила ниже. pipeline_sources.including также принимает api, chat, external, external_pull_request_event, merge_request_event, pipeline, push, schedule, trigger, web. Вручную написанное правило может добавить источники, которых нет среди флажков, например web.

rules:
  - type: pipeline
    branch_type: target_default
    pipeline_sources:
      including:
        - merge_request_event
  - type: pipeline
    branch_type: default
    pipeline_sources:
      including:
        - push

Либо установите флажок «Не проваливать задачу, если по адресу нет образа» для веток, которые ничего не собирают.

Сканирование кластеров Kubernetes

Модуль сканирует образы нагрузок, которые работают в перечисленных неймспейсах кластера Kubernetes. Задача trivy_k8s запускает trivy k8s на раннере и обращается к кластеру через агент для Kubernetes по его ci_access. Kubeconfig тот же, который агент выдаёт обычной задаче CI/CD. Модуль ничего не устанавливает в кластер, не настраивает агент и не клонирует сканируемый репозиторий. Модуль сообщает только об уязвимостях в пакетах образов. Модуль не проверяет конфигурацию, RBAC, секреты в манифестах, узлы и control plane.

Собственные поля модуля:

ПолеПеременнаяЧто задаёт
«Контекст агента»FE_SCANS_TRIVY_K8S_AGENTОбязательное. Агент в виде пути его проекта, двоеточия и имени, например group/project:agent
«Пространства имён»FE_SCANS_TRIVY_K8S_NAMESPACESОбязательное. Имена неймспейсов через запятую без пробелов, например apps,shop
«Тайм-аут»FE_SCANS_TRIVY_K8S_TIMEOUTСколько времени Trivy может потратить на всё сканирование кластера: длительность в формате Go, например 20m, 1h30m или 90s. Пусто — 20m
«Настройки TLS»FE_SCANS_TRIVY_K8S_TLS_MODEКак задача проверяет сертификат прокси агента (раздел «Настройки TLS прокси агента»). По умолчанию — system
«Удостоверяющий центр»FE_SCANS_TRIVY_K8S_CA_CERTСертификат в формате PEM. Появляется и становится обязательным при варианте «Доверять удостоверяющему центру ниже»
«Дополнительные аргументы»FE_SCANS_TRIVY_K8S_EXTRA_ARGSФлаги trivy k8s, которых нет в форме. Задача передаёт их без изменений, например --parallel 2

Редактор не принимает пустой список неймспейсов: без списка Trivy сканирует весь кластер. Редактор отклоняет тайм-аут без единицы (20), с заглавной единицей (10M) и нулевой (0s). Пока поле не исправлено, создать запрос на слияние нельзя. Задача ставит «Дополнительные аргументы» перед своими флагами. Если флаг задан в обоих местах, действует флаг задачи: --scanners, --include-namespaces, --timeout и другие. Если снять флажок модуля, редактор удаляет из политики все переменные модуля.

  • Режим правил
  • Режим .yaml
  1. В блоке «Trivy (встроенный)» установите флажок «Сканирование кластеров Kubernetes».
  2. В поле «Контекст агента» введите агент, например group/project:agent.
  3. В поле «Пространства имён» введите неймспейсы для сканирования, например apps,shop.
  4. В разделе «Когда запускать сканирования» установите флажок «Сканирование по расписанию» и заполните поле «Расписание» (раздел «Когда запускать сканирование кластеров»).

Блок Сканирование кластеров Kubernetes

scan_execution_policy:
  - name: Cluster scan
    enabled: true
    rules:
      - type: schedule
        cadence: '0 3 * * *'
        timezone: Europe/Moscow
        branch_type: default
    actions:
      - scan: trivy
        variables:
          FE_SCANS_TRIVY_SCAN_IMAGE: 'registry.example.com/trivy-with-db:0.74.0'
          FE_SCANS_TRIVY_K8S: 'true'
          FE_SCANS_TRIVY_K8S_AGENT: 'group/project:agent'
          FE_SCANS_TRIVY_K8S_NAMESPACES: 'apps,shop'

Собственного порога и образа у модуля нет: действуют «Порог блокировки» и «Образ сканирования» из блока «Общие настройки». Модуль применяет общий trivy.yml. Если файл назван без проекта, задача загружает его с ветки по умолчанию сканируемого проекта (раздел «Файлы конфигурации»). Модуль не ищет секреты, поэтому trivy-secret.yaml к нему не относится.

Одна политика сканирует один кластер: для второго кластера нужна вторая политика со своим агентом. Политику с двумя действиями trivy, в которых включён модуль кластеров, сохранить нельзя, даже если она написана вручную. Каждая политика с модулем кластеров получает собственную задачу trivy_k8s, а каждая политика по расписанию — собственный пайплайн.

Настройки TLS прокси агента

Задача обращается к кластеру через прокси агента этой установки по TLS. «Настройки TLS» определяют, как задача проверяет сертификат прокси:

ВариантЗначение в политикеЧему доверяет задача
«Доверять тому, чему уже доверяет задача»systemСистемному хранилищу сертификатов образа сканирования. Подходит, если сертификат прокси выдан публичным удостоверяющим центром
«Доверять удостоверяющему центру ниже»custom_caТолько сертификату из поля «Удостоверяющий центр»: удостоверяющему центру, который подписал сертификат прокси, или самому сертификату прокси
«Не проверять сертификат»skip_verifyЛюбому сертификату. Любой, кто перехватит соединение между раннером и прокси, получит токен задачи. Используйте этот вариант только в тестовой установке

При custom_ca политику нельзя сохранить без сертификата, который начинается с -----BEGIN CERTIFICATE-----:

FE_SCANS_TRIVY_K8S_TLS_MODE: custom_ca
FE_SCANS_TRIVY_K8S_CA_CERT: |
  -----BEGIN CERTIFICATE-----
  MIIDdzCCAl+gAwIBAgIU...
  -----END CERTIFICATE-----

Настройка касается только соединения задачи с прокси агента. Соединение агента с установкой не меняется. Обе переменные объявлены в задаче, поэтому переменные CI/CD сканируемого проекта их не переопределяют.

Доступ к кластеру

Задача выполняется в каждом проекте, на который распространяется политика. KUBECONFIG с контекстом агента задача получает, только когда выполнены все условия:

  • проект или его группа перечислены в ci_access конфигурации агента, .gitlab/agents/<имя агента>/config.yaml в проекте агента;
  • проект агента находится в той же группе верхнего уровня, что и сканируемый проект, если не включена авторизация агентов на уровне всей установки;
  • в записи ci_access нет environments: задача сканирования выполняется вне окружения;
  • в записи не задано protected_branches_only: true, либо пайплайн выполняется на защищённой ветке.
ci_access:
  projects:
    - id: group/scanned-project
  groups:
    - id: group

Проекту самого агента запись в ci_access не нужна. В кластере у задачи права агента (access_as: agent, значение по умолчанию записи ci_access). Чарт gitlab-agent по умолчанию выдаёт агенту cluster-admin. Самому сканированию нужны get и list на Pod, Deployment, ReplicaSet, StatefulSet, DaemonSet, Job, CronJob, ReplicationController, Secret и ServiceAccount. Параметр чарта rbac.useExistingRole сужает права агента.

Без доступа задача завершается с ошибкой до запуска Trivy и перечисляет все четыре условия (сообщение has no KUBECONFIG в разделе «Диагностика»).

Когда запускать сканирование кластеров

Модуль работает с любым правилом политики. Задача trivy_k8s выполняет одну и ту же работу в пайплайне по расписанию и в пайплайне пуша. Состояние кластера не зависит от коммита. Правило schedule сканирует кластер раз в период. Правило pipeline сканирует те же образы заново при каждом пуше в подходящую ветку.

Учётные данные хранилищ образов кластеров

Trivy берёт пароль к хранилищу образов из кластера: из Secret, который назван в imagePullSecrets пода или его ServiceAccount. Для таких образов ничего настраивать не нужно.

Узлы могут загружать образы по учётным данным kubelet или через плагин kubelet для получения учётных данных. Если при этом у подов нет imagePullSecrets, Trivy не видит учётных данных и не сканирует образ. Чтобы узнать, у каких подов есть imagePullSecrets, выполните команду:

d8 k -n <NAMESPACE> get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.imagePullSecrets}{"\n"}{end}'

Для образов, у подов которых нет imagePullSecrets, задайте DOCKER_AUTH_CONFIG в переменных CI/CD сканируемого проекта, в формате из раздела «Учётные данные хранилища образов» выше. Задача записывает DOCKER_AUTH_CONFIG в ~/.docker/config.json перед сканированием и сообщает об этом в логе: Trivy k8s: using DOCKER_AUTH_CONFIG for images the cluster holds no pull secret for. Trivy сначала пробует учётные данные из кластера и читает этот файл, когда их нет.

Образы загружает раннер. Хранилище образов, доступное только узлам кластера, для сканирования недоступно.

Если раннер не загрузил образ, на вкладке «Тесты» появляется ошибка Could not scan <образ> с сообщением Trivy. Несоответствий по этому образу нет. Если не удалось просканировать только часть ресурсов, задача остаётся успешной и пишет в лог, сколько ресурсов из скольких не просканировано: trivy_k8s: 1 of 4 resources could not be scanned; each is an error in the test summary. Если не удалось просканировать ни одного ресурса, сканирование не завершено. Задача завершается с ошибкой ERROR: the scan itself did not complete, so this pipeline scanned no cluster. Отчёт об уязвимостях получает отчёт незавершённого сканирования без несоответствий. В DefectDojo ничего не выгружается. Неймспейс без нагрузок ошибкой не считается.

Несоответствия сканирования кластеров

Несоответствия попадают в отчёт об уязвимостях с источником «Сканирование образов кластеров». Расположение несоответствия — нагрузка в виде namespace/kind/name, затем образ без тега и пакет, например apps/Deployment/web nginx:libssl1.1. Один образ в двух нагрузках даёт два несоответствия, поскольку исправлять нужно обе нагрузки. Одна и та же нагрузка в двух кластерах тоже даёт два несоответствия: они различаются агентом, которого в расположении не видно. Повторное сканирование и новый тег того же образа снова дают те же несоответствия.

Trivy не сообщает имена контейнеров, поэтому несоответствие называет контейнер по образу, без хранилища и тега: redis, nginx, alpine. Имя совпадает с spec.containers[].name только у контейнеров, названных по своему образу.

На вкладке «Тесты» у теста сканирования кластеров нет файла: нагрузка указана в квадратных скобках в имени теста и строкой Resource: namespace/kind/name в его тексте. Лог задачи не выводит индикатор прогресса Trivy. Вместо него при каждом изменении счётчика задача пишет в лог Trivy k8s: N of M resources scanned. Исходный отчёт Trivy лежит в артефактах задачи: security-reports/raw/trivy-k8s.json.

Интеграция с DefectDojo выгружает отчёт парсером «GitLab Container Scan» в тест с именем задачи, trivy_k8s:<часть имени политики>-p<ID>. Отдельного поля для нагрузки у этого парсера нет, поэтому заголовок несоответствия называет пакет, образ, нагрузку и уязвимость, например apt 1.4.8 in redis:5.0.0 (apps/Deployment/cache): CVE-2019-3462 …. Две политики с модулем кластеров в одном проекте дают два теста. После переименования политики DefectDojo получает новый тест.

Ограничения сканирования кластеров

  • Один кластер в нескольких проектах сканируется несколько раз. Задача выполняется в каждом проекте, на который распространяется политика, и у каждого проекта своя копия несоответствий. Сканируйте кластер из одного проекта: привяжите политику с модулем только к нему или сузьте её через policy_scope.
  • Сканируемый проект может перенаправить сканирование. Переменные Kubernetes от агента попадают в задачу раньше переменных CI/CD проекта. Поэтому переменная KUBECONFIG, заданная в сканируемом проекте, заменяет kubeconfig агента. Задача сканирует кластер, который выбрал проект, или завершается с ошибкой о контексте.
  • Лимит Docker Hub. Docker Hub ограничивает анонимные загрузки с index.docker.io на один IP-адрес. Действующие лимиты приведены на странице лимитов Docker Hub. После исчерпания лимита Trivy сообщает TOOMANYREQUESTS, и образ становится ошибкой на вкладке «Тесты».
  • Отчёт растёт вместе с образами. Неймспейс с тремя небольшими образами даёт отчёт около 1 МБ. Один образ python:3.9 даёт отчёт 33 МБ, а исходный отчёт Trivy — 51 МБ. Отчёт больше лимита размера артефактов установки (max_artifacts_size, по умолчанию 100 МБ) не выгружается. Сканируйте крупные неймспейсы в разных политиках.

Файлы конфигурации

Часть настроек Trivy читает из файлов, которые названы в блоке «Общие настройки»: trivy.yml, собственного файла конфигурации Trivy, и trivy-secret.yaml, правил и списков исключений поиска секретов. В примере раздела «Общие настройки» оба файла читаются из проекта security/trivy-config.

Задачи всех модулей читают один и тот же trivy.yml: с указанным проектом — из ветки по умолчанию этого проекта, без проекта — из сканируемого репозитория, без файла — конфигурация не передаётся. У модуля кластеров нет рабочей копии. Файл trivy.yml, названный без проекта, задача загружает с ветки по умолчанию сканируемого проекта как снимок всего дерева. На большом репозитории задача выполняется дольше. Для сканирования кластеров укажите проект, в котором лежит trivy.yml. Один trivy.yml одинаково подходит модулям, которые работают с репозиторием, модулю образов контейнеров и модулю кластеров: Trivy без предупреждения пропускает ключи, которые не использует конкретный запуск. Пару trivy-secret.yaml читают модуль обнаружения секретов и модуль образов контейнеров. Если пара не задана, оба используют встроенные правила Trivy. Модуль образов контейнеров никогда не подхватывает trivy-secret.yaml из репозитория самостоятельно. Правила поиска секретов, записанные прямо в trivy.yml, Trivy молча игнорирует: такие правила должны быть в trivy-secret.yaml. Не задавайте порог в trivy.yml: его ключ severity вырезает несоответствия ниже указанного уровня из отчёта, и они не попадают даже в отчёт об уязвимостях. Что приводит задачу к ошибке, решает порог блокировки политики.

Относительные пути внутри trivy.yml и файлы, которые Trivy ищет по умолчанию (trivy.yaml, trivy-secret.yaml, .trivyignore), разрешаются относительно рабочего каталога задачи — рабочей копии сканируемого проекта, а не цели сканирования. Для модуля образов контейнеров это значит, что пути в trivy.yml указывают на репозиторий, а не на образ. При пустом поле файла задача не передаёт файл конфигурации, и Trivy в этом случае тоже не подставляет собственные файлы по умолчанию.

Обе пары, trivy.yml и trivy-secret.yaml, устроены одинаково. Пустое поле проекта означает сканируемый репозиторий: задача читает файл из рабочей копии на сканируемом коммите, и тогда конфигурацию сканирования контролирует сам сканируемый проект. Политика, которая не должна позволять сканируемому проекту менять конфигурацию, называет проект явно. Названный проект задача читает собственным токеном задачи CI/CD, всегда из его ветки по умолчанию. Токен несёт права того, кто запустил пайплайн: если у этого пользователя нет доступа к проекту, задача завершается с ошибкой. Откройте доступ одним из способов: сделайте проект с файлами внутренним или публичным; добавьте сканируемый проект (или его группу) в список разрешённых токена задач CI/CD проекта с файлами; сделайте того, кто запускает пайплайны, участником проекта с файлами. При сохранении политики названный проект проверяется только на существование и наличие репозитория. Права доступа не проверяются, потому что читателем файла станет тот, кто запустит пайплайн позже.

Поле проекта редактор показывает, только пока заполнено поле файла; очистка поля файла убирает проект из политики. Политику, написанную вручную с проектом и без файла, сохранить нельзя: ошибка называет поле, которое нужно заполнить. Уже действующая политика с проектом и без файла останавливает задачу перед сканированием, назвав пустое поле. Файл без проекта читается из сканируемого репозитория.

Задача останавливается, если названный проект не удалось прочитать, файла нет в источнике, файл пуст или путь абсолютный либо содержит .. (схема политики тоже отклоняет такие пути). Лог задачи всегда называет источник файла, а для названного проекта — ещё и коммит, на котором файл прочитан: Trivy: configuration read from <project> at <commit SHA>. и Trivy: <VARIABLE> applied from <source> (<file>). Этот коммит — единственное место, где видно, какие правила применило сканирование. Формат trivy.yml описан в справочнике Trivy по файлу конфигурации, а формат trivy-secret.yaml — в справочнике Trivy по сканеру секретов.

Блокировка

Задачи trivy_sca, trivy_license, trivy_container и trivy_k8s завершаются с ошибкой, когда несоответствие достигает порога блокировки. Все четыре читают общий «Порог блокировки», FE_SCANS_TRIVY_FAIL_ON, и завершаются успешно, если порога не достигло ни одно несоответствие. Задача trivy_secrets общий порог не читает: её завершает с ошибкой любой секрет, какой бы ни была его критичность. Флажок «Не блокировать на находках» присваивает ей порог report_only. Несоответствия ниже порога всё равно попадают в отчёты и на вкладку «Тесты»: порог решает только исход задачи.

НазваниеЗначение в политикеЧто приводит задачу к ошибке
«Только критический»criticalКритическое несоответствие
«Высокий и выше» (по умолчанию у модулей сканирования зависимостей, лицензий, образов контейнеров и кластеров)highВысокое и критическое
«Средний и выше»mediumСреднее и выше
«Низкий и выше»lowНизкое и выше
«Любая находка»infoНесоответствие любой распознанной критичности
«Ничего не блокировать»report_onlyНичего; несоответствия всё равно отражаются в отчётах

Несоответствие с неизвестной критичностью (UNKNOWN) не приводит задачу к ошибке ни при каком пороге, включая «Любая находка». Уязвимость, которую дистрибутив пометил как not_affected, will_not_fix, end_of_life или fix_deferred, отражается на вкладке «Тесты» как пропущенная и никогда не приводит задачу к ошибке. На вкладке «Тесты» несоответствие на уровне порога или выше — это неудачный тест, ниже порога — пройденный, отложенное — пропущенный. Сканирование без несоответствий даёт один тест, «No findings».

В версии Trivy, которая поставляется в образе, у секретов разная критичность: ключ доступа AWS и токен GitHub — критической, приватный ключ — высокой, JWT и вебхук Slack — средней, публикуемый ключ Stripe — низкой. Секрет, попавший в образ, оценивается по общему порогу, как и уязвимости образа: при пороге по умолчанию «Высокий и выше» секрет средней критичности, например JWT, попадает в отчёт, но не приводит задачу сканирования образа к ошибке (раздел «Сканирование образов контейнеров»).

FE_SECURITY_FAIL_ON, записанная прямо в variables действия trivy, игнорируется. Задача также завершается с ошибкой, если сам Trivy не отработал до конца, независимо от порога: тогда лог показывает ERROR: the scan itself did not complete, so this pipeline scanned nothing. Задача, завершившаяся с ошибкой, печатает, сколько несоответствий достигло порога, например ERROR: 3 finding(s) at or above high. (у задачи лицензий — licence(s), у задачи секретов — secret(s)).

Где отображаются результаты

Несоответствия и отчёты появляются в разных местах в зависимости от того, что это за данные.

ГдеЧто там видно
Вкладка «Тесты» пайплайнаПо одному набору JUnit на каждую выполнившуюся задачу: несоответствия на уровне порога и выше — как неудачные тесты, ниже — как пройденные, отложенные — как пропущенные
«Итоги тестирования» запроса на слияниеТе же наборы, сопоставленные с целевой веткой
«Отчёт об уязвимостях» проектаНесоответствия trivy_sca (сканирование зависимостей), trivy_secrets (обнаружение секретов), каждой задачи trivy_container (сканирование образов контейнеров) и trivy_k8s (сканирование образов кластеров). Несоответствия по лицензиям сюда не попадают
Страницы «Список зависимостей» и «Соответствие лицензий»SBOM от trivy_sca и trivy_license, из последнего пайплайна ветки по умолчанию. Подробнее — на странице Список зависимостей и соответствие лицензий
DefectDojoОтчёты сканирования зависимостей, секретов, образов контейнеров и образов кластеров, если настроена интеграция с DefectDojo

Когда страница «Отчёт об уязвимостях» доступна в проекте и как с ней работать, описано на странице Отчёт об уязвимостях. В отчёте об уязвимостях инструментом этих несоответствий указан Trivy, какой бы модуль их ни нашёл.

Образ сканирования и базы уязвимостей

Trivy выполняется в собственном образе, помеченном версией Trivy, которую несёт: по умолчанию это trivy-with-db:0.74.0 из хранилища образов продукта (раздел «Общие настройки»). Образ несёт Trivy, обе базы Trivy — базу уязвимостей и базу Java — по пути /var/cache/trivy (db/trivy.db, java-db/trivy-java.db), Ruby с библиотеками json и csv для сборки отчётов и git для чтения файлов конфигурации из проекта. Опубликованный образ по умолчанию собран только под linux/amd64: раннеру другой архитектуры, например arm64, нужен собственный образ сканирования, собранный под его архитектуру (раздел «Собственный образ сканирования»).

Задачи запускают Trivy с выключенными обновлением баз и проверкой версии, поэтому сканирование ничего не загружает и не нуждается в выходе в интернет; базы остаются такими же свежими, как на момент сборки образа. trivy --version в начале лога каждой задачи печатает дату сборки базы (UpdatedAt) и NextUpdate — по этой строке видно, насколько устарела встроенная база. Хранилище, где лежит образ по умолчанию, отклоняет анонимную загрузку. Раннер загружает образ сканирования так же, как любой образ задачи: ему нужен доступ к этому хранилищу либо копия образа в доступном ему хранилище, указанная в поле «Образ сканирования».

Политика загрузки образа на раннере

Базы обновляются пересборкой образа под тем же тегом, поэтому каждая задача Trivy просит у раннера pull_policy: [always, if-not-present]: загружать образ при каждом запуске и использовать локальную копию, если хранилище недоступно. Раннер применяет политики, которые просит задача и которые сам разрешает: пересечение списка задачи с allowed_pull_policies раннера, а если allowed_pull_policies не задан — с его собственным pull_policy. У executor Docker pull_policy по умолчанию равен always; чтобы базы обновлялись, раннер должен разрешать always. Раннер, у которого pull_policy — это if-not-present, а allowed_pull_policies не задан, загружает образ один раз и дальше сканирует с теми же базами.

Раннер разрешаетЧто происходит
always и if-not-presentОбраз загружается при каждом запуске; если хранилище образов недоступно, используется локальная копия
только alwaysОбраз загружается при каждом запуске; если хранилище образов недоступно, задача завершается с ошибкой
только if-not-presentОбраз загружается один раз; свежие базы не приходят
Ни одна, например только neverЗадача завершается с ошибкой ещё до запуска, с сообщением об ошибке политики загрузки раннера

Чтобы разрешить обе политики для задач Trivy, не меняя политику раннера по умолчанию для остальных задач, настройте executor Docker в config.toml раннера:

[[runners]]
  executor = "docker"
  [runners.docker]
    pull_policy = ["if-not-present"]
    allowed_pull_policies = ["always", "if-not-present"]

Задачи, которые не называют политику загрузки, по-прежнему используют if-not-present; задачи Trivy получают обе. pull_policy раннера должен содержать хотя бы одну из разрешённых политик. У executor Kubernetes те же ключи — в [runners.kubernetes]. Раннер, который ограничивает образы задач через allowed_images, должен разрешить образ сканирования.

Закрытый контур

Самому сканированию интернет не нужен: базы уже в образе. Установке без выхода в интернет нужен образ сканирования в доступном ей хранилище: скопируйте образ в собственное хранилище и укажите копию в поле «Образ сканирования» политики. Политика сохраняет названный образ и после обновления продукта. Чтобы перевести политику на новый образ, обновите «Образ сканирования».

Собственный образ сканирования

Собирайте собственный образ сканирования для раннера другой архитектуры процессора или чтобы самим решать, когда обновлять базы. Встройте базы по тем же путям, /var/cache/trivy/db/trivy.db и /var/cache/trivy/java-db/trivy-java.db, доступным для записи пользователю задачи. В образе также нужен git, чтобы читать файлы конфигурации из проекта, и Ruby с библиотеками json и csv, чтобы собирать отчёты. Задача, в образе которой нет Ruby, устанавливает его через apk или apt-get, если один из них есть в образе, а иначе завершается с ошибкой. Способ встроить базы: trivy --cache-dir /var/cache/trivy image --download-db-only и trivy --cache-dir /var/cache/trivy image --download-java-db-only. Чтобы обновить базы, пересоберите образ под тем же тегом; раннеры на политике загрузки always подхватят пересборку автоматически (раздел «Политика загрузки образа на раннере»). Раннеру архитектуры, отличной от linux/amd64, например arm64, тоже нужен образ, собранный под его архитектуру, например с помощью docker buildx build --platform linux/arm64.

Диагностика

Причину сбоя задачи Trivy или её отсутствия показывает пайплайн или лог задачи.

Сканирование не появляется в пайплайне

Проверьте, что:

  • проект политик привязан к проекту или к группе выше него, и в его policy.yml есть действие scan: trivy (в редакторе политик оно называется «Trivy (встроенный)»);
  • действие включает хотя бы один модуль значением 'true';
  • правила политики подходят пайплайну: его ветке и его источнику;
  • запрос на слияние с политикой принят в проекте политик;
  • доступен GitLab Runner с executor docker.

Задача завершается с ошибкой «turns no module on»

Это задача trivy_no_module: действие trivy называет переменные модулей, но ни одна из них не равна 'true'.

ERROR: this policy mandates a Trivy scan but turns no module on.
  Nothing was scanned. Open the policy and enable the dependency scan, the licence scan, the secret search, the container image scan, the cluster scan, or any of them --
  in the editor, or by naming FE_SCANS_TRIVY_SCA / FE_SCANS_TRIVY_LICENSE / FE_SCANS_TRIVY_SECRETS / FE_SCANS_TRIVY_CONTAINER / FE_SCANS_TRIVY_K8S in the policy.

Установите флажок у блока модуля в редакторе или задайте одной из этих переменных значение 'true' в политике.

Задача trivy_no_image завершается с ошибкой

Политика включает модули Trivy и не называет «Образ сканирования»: она написана до того, как образ стал обязательным. Вместо задач Trivy в пайплайне одна задача trivy_no_image, и её лог называет FE_SCANS_TRIVY_SCAN_IMAGE и поле «Образ сканирования».

Укажите образ сканирования в политике (раздел «Общие настройки»).

Задача останавливается: проект указан без файла

Задача останавливается перед сканированием, потому что общее поле «Проект с trivy.yml» или «Проект с trivy-secret.yaml» заполнено, а его собственное поле файла пусто. Лог называет пустое поле.

Заполните поле файла либо очистите поле проекта.

Задача завершается с ошибкой «carries no vulnerability database»

Это задача trivy_sca, trivy_container или trivy_k8s, и «Образ сканирования» называет образ без встроенных баз, например публичный образ aquasec/trivy.

ERROR: this scan image carries no vulnerability database at /var/cache/trivy/db/trivy.db.

Если отсутствует база Java, сообщение вместо этого называет java-db/trivy-java.db. Задача останавливается и базу не загружает. Назовите образ с базами по пути /var/cache/trivy (раздел «Собственный образ сканирования»).

Раннер отклоняет политику загрузки образа

Задача завершается с ошибкой ещё до запуска своего скрипта, с ошибкой вида the configured PullPolicies (…) are not allowed by AllowedPullPolicies (…). Раннер не разрешает ни always, ни if-not-present. Разрешите обе политики на раннере (раздел «Политика загрузки образа на раннере»).

Сканирование не находит недавно опубликованную уязвимость

Базы в образе, который использовал раннер для сканирования, старше, чем уведомление об уязвимости. Проверьте UpdatedAt в выводе trivy --version в начале лога задачи. Если эта дата старая, а опубликованный образ новее, раннер не загружает образ при каждом запуске: разрешите политику загрузки always (раздел «Политика загрузки образа на раннере»). Если устарел сам опубликованный образ, укажите более новый образ в поле «Образ сканирования» либо соберите собственный (раздел «Собственный образ сканирования»).

Задача завершается с ошибкой «no image applies here»

Задача завершается с ошибкой, потому что для этого пайплайна не подошла ни одна строка поля «Список образов для сканирования».

ERROR: this policy mandates a container image scan and no image applies here.

Все строки были пропущены, и лог называет причину для каждой, либо поле пустое. Добавьте строку, которую этот пайплайн может заполнить, либо установите флажок «Не проваливать задачу, если по адресу нет образа» для веток, которые ничего не собирают.

Задача завершается с ошибкой «it does not exist»

Задача завершается с ошибкой, потому что образ или файл артефакта, названные в политике, не нашлись при запуске сканирования.

ERROR: this policy mandates a scan of <image> and it does not exist.

Хранилище образов ответило, что образ или тег неизвестны, либо файл artifact: не найден на диске. Если образ задан адресом, перенесите задачу сборки на этап раньше этапа сканирований или укажите адрес, куда проект отправляет образ. Для артефакта выполните требования из раздела «Образ, сохранённый как артефакт». Для пайплайна запроса на слияние добавьте задаче сборки rules.

Задача завершается с ошибкой UNAUTHORIZED или DENIED

Для образа в хранилище образов этой установки задача завершается с ошибкой UNAUTHORIZED и DENIED: access forbidden, потому что проект с образом не разрешает доступ токену задачи сканируемого проекта. Откройте «Настройки» → «CI/CD» → «Права доступа токена заданий CI/CD» в проекте с образом и добавьте туда сканируемый проект. Для образа в другом хранилище задайте DOCKER_AUTH_CONFIG либо CS_REGISTRY_USER и CS_REGISTRY_PASSWORD в переменных CI/CD сканируемого проекта. Когда сканирование завершается с ошибкой по причине, отличной от отсутствующего образа, лог также показывает ERROR: the scan itself did not complete, so this pipeline scanned no image.

Лог показывает realm host "10.0.0.5" is a private or link-local address, когда хранилище образов отправляет Trivy за токеном на IP-адрес из приватного диапазона или диапазона loopback, например realm="http://10.0.0.5/jwt/auth". Trivy отклоняет такой адрес, и никакой флаг не отключает эту проверку. Хранилище образов должно указывать realm токена именем хоста: укажите в external_url установки доменное имя.

Задача завершается с ошибкой «could not be read»

Задача завершается с ошибкой, потому что не смогла прочитать названный проект с конфигурацией.

ERROR: the Trivy configuration project <project> could not be read.

У токена того, кто запустил пайплайн, нет доступа к названному проекту, либо проект не существует. Откройте доступ одним из способов из раздела «Файлы конфигурации».

Соседние сообщения: ERROR: <VARIABLE> names "<file>", which <source> does not have. — для отсутствующего файла, ... and that file is empty. — для пустого файла, и ... which is not a path inside a repository. — для абсолютного пути или пути, содержащего ...

Trivy отклоняет значение тайм-аута

Значение --timeout, записанное в поле «Дополнительные аргументы» модуля, не распознаётся Trivy как длительность. Trivy останавливается при запуске, и задача завершается с ошибкой. Указывайте значение как длительность, например 90s, 15m или 1h30m.

Задача сканирования лицензий то проходит, то нет на неизменном коде

Лицензия перечислена в trivy.yml под категорией, отличной от встроенной, поэтому Trivy заново выбирает одну из двух категорий при каждом запуске. Исправление — в разделе «Категории лицензий в trivy.yml».

Лог задачи предлагает выполнить npm install

[npm] Run "npm install" to collect the license information of packages — подсказка, которую Trivy печатает всякий раз, когда не находит node_modules. Отчёт при этом может быть полным, если сам lock-файл несёт лицензии. Подробнее — в разделе «Лицензии неустановленных зависимостей».

Задача завершается с ошибкой «has no KUBECONFIG»

Задача trivy_k8s за несколько секунд завершается с ошибкой до запуска Trivy, потому что агент не разрешил доступ сканируемому проекту.

ERROR: this job has no KUBECONFIG: the agent group/project:agent has not authorised this project.

Дальше сообщение перечисляет четыре условия из раздела «Доступ к кластеру». Перечислите проект или его группу в ci_access агента без environments. Проверьте, что проект агента находится в той же группе верхнего уровня.

Задача завершается с ошибкой «has no context»

У задачи есть kubeconfig, но в нём нет контекста агента, указанного в поле «Контекст агента», например из-за опечатки в имени агента.

ERROR: the kubeconfig of this job has no context group/project:agent.
  The agents that authorised this project are:
  group/project:agent-2

Укажите в поле «Контекст агента» одного из перечисленных агентов. Другой вариант — разрешите доступ проекту в ci_access нужного агента. В обоих случаях на вкладке «Тесты» один тест Scanner error с тем же текстом, а отчёт об уязвимостях получает отчёт незавершённого сканирования без несоответствий.

Сканирование кластеров завершается с ошибкой x509

Лог показывает x509: certificate signed by unknown authority. В «Настройках TLS» выбран вариант «Доверять тому, чему уже доверяет задача», а сертификат прокси агента подписан удостоверяющим центром, которому образ сканирования не доверяет. Выберите «Доверять удостоверяющему центру ниже» и вставьте сертификат удостоверяющего центра, который подписал сертификат прокси (раздел «Настройки TLS прокси агента»).

Образ сканирования кластеров отмечен «Could not scan»

Раннер не смог загрузить этот образ: в кластере нет секрета для его загрузки, хранилище образов доступно только из кластера, или исчерпан лимит Docker Hub (TOOMANYREQUESTS). Задайте DOCKER_AUTH_CONFIG в переменных CI/CD сканируемого проекта или откройте раннеру доступ к хранилищу (раздел «Учётные данные хранилищ образов кластеров»).

Дополнительные ресурсы