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

SonarQube и OWASP Dependency-Check в GitLab CI: первый прогон нашёл 12 критических находок, о которых никто не знал

Внедрили SAST и сканирование зависимостей в GitLab CI: SonarQube + OWASP Dependency-Check выявили 12 критических уязвимостей в продовых библиотеках с первого запуска.

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

DevSecOps-практики набирают зрелость: интеграция статического анализа (SAST) и сканирования зависимостей в CI/CD становится стандартом для команд, которые серьёзно относятся к безопасности кода.

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

Команда разработки у клиента небольшая, примерно десять человек, монорепозиторий на GitLab, деплой в Kubernetes. Код проходит ревью, тесты есть, но вопрос уязвимостей в зависимостях никто системно не отслеживал. Классическая ситуация.

Что выбрали и почему

Задача распадается на две независимые части: анализ кода (SAST) и анализ зависимостей (SCA - Software Composition Analysis). Это разные инструменты с разной логикой.

Для SAST выбрали SonarQube. Решение не новое, но надёжное: есть хорошая интеграция с GitLab через merge request decoration, поддерживает Java и Python, которые использует клиент, плагины для дополнительных правил подключаются без боли. Community Edition бесплатен и покрывает основные нужды.

Для сканирования зависимостей - OWASP Dependency-Check. Инструмент проверяет используемые библиотеки против базы CVE (NVD - National Vulnerability Database). На входе - список зависимостей или собранный артефакт, на выходе - отчёт с найденными уязвимостями, их CVSS-оценкой и ссылками на описание.

Альтернативы рассматривали: Snyk, WhiteSource. Оба хороши, но для клиента с бюджетными ограничениями и желанием не тащить данные об исходниках во внешние облака - OWASP Dependency-Check проще обосновать.

Как добавляли в пайплайн

GitLab CI с раннерами в Kubernetes - среда знакомая. Добавили два отдельных stage: sast и dependency-check. Запускаются параллельно после стандартного build, блокируют deploy если находки выше порога.

Для SonarQube развернули отдельный инстанс на выделенной VM. Первая попытка поднять в Kubernetes - отказались, SonarQube с PostgreSQL в качестве хранилища приятнее живёт на стейтфул ноде, чем в поде с persistent volume. Может, когда-нибудь вернёмся к K8s, но не сегодня.

Dependency-Check запускается прямо в CI-джобе как Docker-образ. База CVE скачивается и кешируется в GitLab CI cache - без этого каждый запуск тратил бы несколько минут только на загрузку базы.

Пример упрощённой конфигурации stage в .gitlab-ci.yml:

dependency-check:
  stage: sast
  image: owasp/dependency-check:latest
  script:
    - /usr/share/dependency-check/bin/dependency-check.sh
        --project "$CI_PROJECT_NAME"
        --scan ./
        --format HTML
        --format JSON
        --failOnCVSS 7
        --out reports/
  artifacts:
    paths:
      - reports/
    when: always

Ключевой параметр - --failOnCVSS 7. Джоб падает при нахождении уязвимости с CVSS 7.0 и выше (High). Критические (9+) блокируют пайплайн жёстко, средние (4-6.9) идут в отчёт как предупреждения, но не блокируют.

Первый прогон: 12 критических, никто не знал

Запустили на существующей кодовой базе. SonarQube нашёл несколько десятков предупреждений разной степени серьёзности - типичная картина для кода, который никогда не проходил статический анализ. Большинство - code smells и minor issues, несколько реальных potential bugs, два security hotspot-а требующих внимания.

Dependency-Check выдал 12 уязвимостей с CVSS выше 7.0 в используемых библиотеках. Из них четыре - с CVSS 9+ (Critical).

Реакция команды была предсказуемой: сначала недоверие («это же просто библиотека для парсинга XML, какие там уязвимости?»), потом тихое «ну ладно».

Список находок выглядел примерно так:

  • Apache Commons Collections старой версии - известная цепочка десериализации, CVE с высоким рейтингом. Библиотека подтянулась транзитивно через три уровня зависимостей - никто её не добавлял осознанно.
  • Jackson Databind - несколько CVE на polymorphic type handling. Версия устарела на полтора года.
  • Spring Security - одна CVE на аутентификацию, версия не обновлялась с начала проекта.
  • Ещё несколько библиотек с уязвимостями меньшего масштаба.

Половина находок - транзитивные зависимости. Команда явно не добавляла ни одну из них - они пришли как зависимости зависимостей. Это стандартная проблема для любого Java или Python проекта с приличным числом библиотек.

Что делали дальше

Исправление разбили на два потока.

Быстрые обновления - библиотеки, где можно поднять версию без breaking changes. Прошлись по каждой CVE, нашли версию с патчем, обновили, запустили тесты. Большинство обновлений прошли чисто, в двух случаях потребовалось небольшое рефакторирование API.

Сложные случаи - там, где библиотека транзитивная и прямого контроля над ней нет. Для таких случаев Maven и Gradle позволяют явно исключить уязвимую транзитивную зависимость и добавить нужную версию напрямую. Не элегантно, но работает.

После первой волны обновлений из 12 критических осталось три - в библиотеках, где апдейт требует более серьёзной проработки. Они завели задачи, установили дедлайн.

Что получилось в итоге

Пайплайн теперь выглядит так: каждый MR проходит через SonarQube и Dependency-Check автоматически. Если появляется новая зависимость с критической CVE - MR блокируется до разбора ситуации. SonarQube показывает delta - только новые проблемы в изменённом коде, не весь технический долг скопом.

Это убирает целый класс вопросов из code review: ревьюер не должен помнить, какие библиотеки уязвимы. Инструмент помнит.

Несколько практических наблюдений по итогам:

  • Ложные срабатывания есть. Dependency-Check иногда находит CVE в библиотеке, которая в конкретном проекте используется так, что уязвимый код вообще не вызывается. Нужен механизм suppression - явного помечания таких находок как неактуальных с обоснованием. У OWASP Dependency-Check для этого есть suppression XML-файл, его кладём рядом с конфигом пайплайна.

  • Время выполнения. Dependency-Check на среднем Java-проекте с локальным кешем базы CVE - около трёх минут. SonarQube - зависит от размера кода, у клиента около четырёх минут. Параллельный запуск держит общий оверхед в пределах пяти минут на пайплайн - терпимо.

  • Обновление базы CVE. Если запускать обновление в каждом джобе - медленно. Если кешировать слишком долго - пропускаешь свежие CVE. Остановились на ежедневном обновлении через scheduled pipeline, в обычных MR-пайплайнах используется вчерашний кеш.

Двенадцать критических находок при первом запуске - это не катастрофа и не повод паниковать. Это нормальный результат для кодовой базы, которая до этого никогда не сканировалась. Главное, что теперь эти находки видны и управляемы.

Контакт

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

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