Привет! Я Максим Коровенков, DevSecOps Lead в Купере (ex СберМаркет). Хочу поделиться мыслями по поводу минимально необходимого набора процессов, сопутствующих внедрению сканеров безопасности в пайплайны разработки.
В результате отвечу на вопрос: «А что, собственно, стоит иметь в виду под фразой “Мы внедрили сканеры безопасности в пайплайны разработки”?»
Да, в тысячный раз про пайплайны, но, как вы, думаю, догадываетесь, желание поделиться казалось бы очевидными мыслями появилось не случайно! Не так давно я завершил найм и укомплектовал свою DevSecOps-команду. В рамках поиска пришлось провести достаточное количество интервью, на которых я любопытствовал, как обстоят дела у соискателей с пайплайнами безопасности на текущем/предыдущем месте работы. Это удивительно, но для 90% респондентов фраза «внедрение сканеров безопасности в пайплайны» означает только факт внедрения. Лишь некоторые кандидаты упоминали отправку результатов в VMs/ASOC систему и выстраивание сопутствующих с этим процессов.
Ведь действительно кажется, что все просто: запаслись вокабуляром из нескольких аббревиатур, внедрили инструменты, SAST, SCA, может что-то еще, настроили отправку результатов для AppSec-ов и, как-будто, можно идти бить баклуши. Но я на своем опыте убедился, что DevSecOps — это про глубокую проработку процессов. Поэтому в данной статье сконцентрируюсь больше на процессах, нежели на технике.
Предлагаю наконец переходить к сути!
![](https://habrastorage.org/getpro/habr/upload_files/d1d/29f/27e/d1d29f27e1d7fdd61dbc202ee533aacc.png)
Все началось с идеи описать полностью наш опыт построения DevSecOps процессов с нуля. Я искренне надеюсь, что это действительно кому-то будет полезно.
Таким образом данная статья является логическим продолжением предыдущей, вероятно не будет последней, и, что важно, в ней будут повторяться некоторые тезисы из предыдущей статьи, поскольку невозможно не повторив базу, в полной мере раскрыть текущую тему.
Сначала контекст. У нас все как у всех:
Архитектура — микросервисная;
Основной языковой стек — Golang, Ruby, JS/TS, Python;
Кодохранилка — GitLab;
CI/CD — GitLab CI;
Существует свой PaaS, с помощью которого мы автоматически шерим пайплайны безопасности на все новые микросервисы;
Размещаем все наши сканеры в своем downstream pipeline и триггерим его из основного, чтобы иметь полный контроль над набором проверок/джобов;
Типы проверок безопасности, выполняемые в пайплайнах — SAST, анализ на наличие секретов, SCA;
Насколько «слева» проводим сканирование — сканируем каждый коммит.
Важная особенность которая отличает наш подход к внедрению сканеров безопасности от большинства других — приверженность идее построения девелопер-центричной системы и выстраивание DevSecOps-процессов As a service.
Тезисно про выбранный подход:
Используем в построении собственных процессов инструменты разработчиков там, где это возможно и уместно.
Задействуем разработчиков, где они могут быть полезны, например, в процессе триажа файндингов.
Живем в парадигме, где безопасность является неотъемлемой частью разработки (SSDLC без первой «эс»).
Наша реализация сканирования кода проектов
Мы довольно давно сделали первые шаги для построения процесса анализа исходного кода. Коротко о нашей реализации сканирования:
Отказались от системы управления уязвимостями. Сами реализовали нормализацию/дедупликацию результатов.
Показываем результаты сканирования в отдельных джобах, в нашем даунстрим-пайплайне. Результаты доступны как разработчикам, так и AppSec-инженерам.
Статусы джобов с результатами демонстрируют наличие/отсутствие необработанных файндингов.
Реализовали фреймворк для работы с исключениями
реализовали QG
Как выглядит наш даунстрим:
![](https://habrastorage.org/getpro/habr/upload_files/351/bc3/9e3/351bc39e3cad190c60c70bd0a8c6a375.png)
На скриншоте мы видим три джобы с результатами отдельно для каждого типа сканирования — их название заканчивается на :results
. Также видим джобу quality gate
, в который выполняется анализ полученных результатов и сравнение с приемлемыми значениями риска (threshold). Все джобы, в которых выполняется сканирование, сгруппированы в scanner
.
Весь процесс анализа и обработки результатов отражен на следующем скрине:
![](https://habrastorage.org/getpro/habr/upload_files/9a4/abc/f7b/9a4abcf7b1000739959765a2d83fe8d1.png)
С точки зрения процесса:
Разработчик пушит код в ветку.
Отрабатывает CI, в рамках которого триггерится даунстрим с проверками безопасности.
В даунстриме выполняются проверки безопасности всех трех типов: SAST, SCA, Secrets.
Если для сервиса уже были внедрены исключения, то эти исключения применяются для полученных результатов. Все исключения хранятся в S3.
Отправляются метрики в Prometheus.
В джоб-логе отражаются результаты сканирования (в тех трех джобах, о которых речь велась выше).
Если были найдены новые файндинги, разработчик имеет возможность перейти в каждую джобу с результатами и сделать анализ.
Если конкретный файндинг подтверждается, он переходит в статус уязвимости, разработчик ее исправляет.
Если файндинг не подтверждается, т.е. разработчик посчитал его «фолсом», разработчик добавляет исключение для этого файндинга в отдельном репозитории в виде MR'а.
AppSec ревьюит изменения в репозитории и мержит их.
После аппрува новые исключения отправляются в S3 и применяются уже при следующем сканировании.
И вот, наконец, дав контекст и базовую информацию об уже сложившемся процессе, можем приступить к рассмотрению различных сопутствующих процессов и нюансов без которых «внедрение сканеров безопасности в пайплайны» не может быть полноценным.
Дисклеймер: автор ни в коем случае не претендует на истину в последней инстанции, лишь делится своим видением.
Процессы до внедрения security-проверок. Выбор сканеров
Начнем с самых простых вещей, а именно: «Чем будем сканировать?»
На что стоит смотреть при выборе, SAST-сканеров:
Используемые ЯП и фреймворки.
Типы уязвимостей. Следует получить список CWE для сравниваемых сканеров из документации/конфигурации в исходном коде/вывода в cli и выбрать тот, в котором реализовано бОльшее количество проверок. Если используются несколько сканеров, следует выбрать их так, чтобы покрытие CWE было максимальным.
Типы проверок. Разные сканеры по-разному находят уязвимости. Основных типов проверок три: pattern-based, семантический анализ и taint-анализ. Следует использовать несколько сканеров, выполняющих разный тип проверок. Этот подход даст существенный плюс в виде минимального количества ложноотрицательных срабатываний, но также и минус в виде большого количества ложноположительных. При правильно выстроенной работе с файндингами минус можно нивелировать.
Какие сканеры используем мы:
Semgrep для pattern-based анализа;
Golangci-lint/brakeman/bandit для семантического анализа;
{Не названный} коммерческий сканер для taint-анализа.
Итого 2-3 сканера анализируют каждый коммит на наличие уязвимостей и делают это по-разному.
На что важно обратить внимание, говоря о композиционном анализе (SCA):
поддерживаемые ЯП и сборочные файлы;
качество построения SBOM;
показ дерева происхождения зависимостей с акцентом на связь между прямыми и транзитивными зависимостями.
{пункт со звездочкой} определение использования уязвимых функций библиотеки в исходном коде разрабатываемого сервиса.
Сразу про пункт со звездочкой: если в разработке используется Golang, следует использовать govulncheck, который умеет определять используются ли уязвимые функции библиотеки в коде. Это снизит количество ложных срабатываний.
Что касаемо остальных пунктов, с ними отлично справляется нами используемый сканер trivy. Он репрезентативно показывает транзитивные зависимости. Выглядит это так:
![](https://habrastorage.org/getpro/habr/upload_files/a52/10d/f48/a5210df480a280f65d1054767daa2be0.png)
Горячо любим и рекомендуем к использованию.
Конфигурирование сканеров
Не будем останавливаться на банальных вещах, что нужно правильно конфигурировать сканеры, исключать тестовые директории и прочее. Есть темы поинтереснее.
Регулярный пересмотр конфигурации сканеров
Для оптимизации времени сканирования и минимизации ложноположительных срабатываний необходимо периодически пересматривать конфигурацию сканеров.
Случай номер 1
Пролема: Вы настроили сканеры, исключили тестовые директории, но AppSec-специалисты, работая с результатами сканирования, постоянно исключают файндинги из одних и те же директорий для всех сервисов.
Решение: Следует пересмотреть конфигурации сканеров и добавить в исключения директории, файндинги в которых постоянно/автоматически исключаются на стадии обработки результатов.
Случай номер 2
Проблема: Вы используете несколько сканеров, но для каких-то типов проверок два использующиеся вами сканера находят одни и те же уязвимости.
Решение: Проблемы нет, если вы корректно дедуплицируете результаты. Однако есть возможность оптимизировать время сканирования путем отключения проверок для сканера, полностью дублирующего результаты другого.
Получение достоверных результатов сканирования
Помимо пересмотра конфигураций сканеров, также следует побеспокоиться о достоверности получаемых результатов сканирования. У разработчика есть как минимум несколько вариантов как, возможно не нарочно, изменить результаты сканирования, проводимого в пайплайне:
-
Добавить инлайн комментарии в исходном коде проекта. Многие анализаторы поддерживают инлайн комментарии — если оставить комментарий, например,
#nosec
с идентификатором типа уязвимости, то ранее находившаяся уязвимость сканером bandit больше обнаружена не будет.
Как бороться:включить игнорирование инлайн комментариев с помощью параметров командной строки;
перед стартом сканирования «вырезать» с помощью find и sed все инлайн комментарии из проекта, благо все они должны быть известны для вами используемых сканеров.
Разместить файлы конфигурации для конкретного сканера в корне проекта. Сканеры могут считывать конфигурационный файл из различных стандартных директорий —текущая директория/корень сканируемого проекта/специально создаваемая директория самим сканером. Если конфигурационные файлы будут расположены сразу в нескольких директориях стандартных для сканера, они будут применены с разным приоритетом. Соответственно конфигурационный файл, лежащий в корне проекта, конкретно для используемого вами сканера, может иметь первый приоритет, что создаст потенциальные проблемы. Очевидно, с помощью кастомного конфигурационного файла можно исключить и директории проекта, и типы проверок, тем самым изменив результаты сканирования.
Бороться с этим просто: названия файлов конфигурации заранее известны, поэтому можно как с инлайн комментариями удалить их перед выполнением сканирования.Самый вопиющий случай, но вполне реализуемый — разработчик может в локальном .gitlab-ci.yml файле разместить джобу с тем же названием как у джобы в даунстриме, переопределив, например, секцию script. Результатом подобных действий, очевидно, может быть то, что сканирование реально выполнено не будет.
Бороться с этим превентивно возможно не имеет смысла, но можно раз в определенный период искать в GitLab все джобы с таких же названием, как в основном даунстриме с проверками безопасности.
Добавление кастомных правил сканирования
Еще из достаточно просто и полезного — внедрение кастомных правил сканирования. Распространенный запрос от AppSec-инженеров — иметь возможность проверить наличие найденной вручную уязвимости, во всех сервисах, где внедрены пайплайны безопасности. Если проверку можно формализовать в виде pattern-based правила, то с этой задачей точно справится Semgrep. Этот сканер обладает своим плейграундом для тестирования правил и очень подробной документацией о том, как писать кастомные правила. Все, что нужно сделать:
организовать репозиторий где AppSec-и будут хранить правила
сделать CI с линтером, тестами, публикацией правил и триггером пересборки образа со сканером
организовать общее место хранения правил в S3
Визуализация описанного выше:
![](https://habrastorage.org/getpro/habr/upload_files/a63/0f7/3d7/a630f73d7bc6dc2159b5ad1277d1210c.png)
Данная схема — лишь пример, показывающий, что решение задачи с внедрением кастомных правил лежит на поверхности. Тем более, что линтер и тест кастомных правил реализован в самом Semgrep и выполняется командами semgrep --validate
и semgrep --test
.
Работа с результатами
Важно то, как будет организована работа с результатами сканирования: кто в какой системе будет лицезреть файндинги и какие действия предпринимать в зависимости от результата их анализа.
Для решения задачи нормализации, дедупликации, а также остальных необходимых функций для работы с результатами, был разработан фреймворк, который мы называем DSOP — о нём подробнее было рассказано в первой статье. Тут рассмотрим только его ключевые функции.
Как известно мы показываем результаты сканирования в выводе специальных джоб {sast|secrets|dependencies}:results. Вот как это выглядит:
![](https://habrastorage.org/getpro/habr/upload_files/368/766/034/368766034e693eda0c0899811ff1b391.png)
Можно признать вывод «портянкой», особенно если представить в результатах 100+ файндингов. Но над выводом можно поработать так, чтобы даже будучи портянкой, он имел удобную для анализа структуру. Что нужно сделать:
сгруппировать файндинги по файлам;
отсортировать файлы по наивысшей степени критичности файндингов (сверху показывать те, в которых критические уязвимости и т.д.);
отсортировать файндинги для одного файла по степени критичности.
Итого мы получили удобный список, который можно анализировать листая сверху вниз.
Данный вывод доступен как AppSec инженерам, так и разработчикам (так как он представлен в самом GitLab).
После того, как мы решили задачу с демонстрацией результатов сканирования, необходимо предоставить возможность обработать результаты.
Какие две основные опции тут есть:
Если файндинг подтверждается как уязвимость, то ставится задача на ее исправление. С нашей стороны дополнительно ничего не требуется.
Если файндинг признается ложноположительным, его следует исключить из результатов сканирования. Для обработки этого типового случая был разработан функционал добавления и применения исключений. Он был описан в общей схеме обработки и анализа результатов сканирования; более подробно — в предыдущей статье.
На текущий момент могу заверить, что этот метод обработки файндингов работает на ура. Разработчики знают о нашем фреймворке и научились самостоятельно с ним работать. Более 60 авторов в репозитории с исключениями помимо AppSec-ов подтвердят =)
Далее, как подсветить проблемы, найденные в репозитории сканерами безопасности? Разработчики могут не заметить или просто проигнорировать варнинги в триггер джобе безопасности. На помощь нам пришел сервис Scoring. О нем опять же подробно было рассказано в предыдущей статье. Тут скажу, что данный сервис аккумулирует количественные показатели различных метрик, в том числе метрики по безопасности. В зависимости от критичности найденных проблем выставляется оценка от C, в случае наличия критичных уязвимостей, до A+, если проблем безопасности нет. Плохие оценки влияют на квартальные оценки разработчиков. Вот как это выглядит:
![](https://habrastorage.org/getpro/habr/upload_files/185/903/549/185903549eeef9ca96a430ae081b4c80.png)
Оповещать разработчиков о проблемах безопасности мы научились. Но как в отсутствии ASOC/VMs системы AppSec-ам реагировать на потенциальные проблемы безопасности и помогать справляться разработчикам с новыми файндингами? Для этого мы написали бота, который в ежедневном формате рапортует об ухудшении оценок в Scoring:
![](https://habrastorage.org/getpro/habr/upload_files/68f/190/545/68f1905453659d211c9a0c848d02997b.png)
Бот умеет тегать нужного AppSec BP, в сервисах которого, произошли ухудшения оценок.
Помимо ухудшившихся оценок, бот также еженедельно присылает информацию обо всех сервисах с оценками ниже A.
Набор из тех практик/сервисов, что были описаны выше, помог нам просто и эффективно наладить работу с файндингами сканеров и вовремя реагировать на проблемы безопасности. В качестве промежуточного вывода, перечислим, что было реализовано:
показ файндингов в логах джобы;
внедрение возможности работать с исключениями;
публикация результатов в сервис оценок Scoring для разработчиков;
публикация ухудшения оценок/сервисов с плохими оценками с помощью бота для AppSec инженеров.
Оптимизация времени сканирования
Задумывались ли вы, как долго сканируются коммиты с помощью внедренных вами проверок безопасности? Мы задумывались и в оптимизации времени сканирования, кажется, уже взяли черный пояс.
Как обстояли дела сначала. Медиана по времени работы всего даунстрима:
![](https://habrastorage.org/getpro/habr/upload_files/6d5/ed4/4d9/6d5ed44d9f25a31101589edaec365877.png)
95 перцентиль:
![](https://habrastorage.org/getpro/habr/upload_files/77e/4b2/d2a/77e4b2d2ab6d83a2680f721ce5e2427e.png)
Как вы понимаете, начинали мы с неприемлемых значений времени сканирования, с которыми пришлось комплексно поработать.
Ответим на главный и насущный вопрос: «Какое время сканирования (время работы даунстрима со всеми проверками безопасности) является оптимальным?» Практика показала, что оптимальным является среднее время выполнения всех джобов на том же стейдже. В нашем случае речь про 3-4 минуты.
Способы оптимизации времени работы инструментов в пайплайне:
Кэши. Если инструмент до выполнения сканирования собирает проект или скачивает зависимости проекта, вероятно, это можно сделать заранее. Сюда же можно отнести все, что касается обновлений (как самих инструментов, так и их сигнатур). Если можно сделать заранее, лучше сделать, чтобы не иметь штраф по времени во время каждого сканирования.
Инкременты. Если сканер умеет в полное/инкрементальное сканирование, следует этим воспользоваться. Тут дополнительно подчеркну, что git diff подходит только для инструментов, которые выполняют pattern-based проверки. Но правда жизни заключается в том, что они, как правило, и так достаточно быстро сканируют.
Смена инструмента. Если на рынке есть инструмент, который обеспечивает то же качество сканирования, что и используемый инструмент, но делает это быстрее, следует перейти на него.
Точечное увеличение количества ресурсов. Если ничего из вышеперечисленного не сработало, можно попробовать увеличить количество ресурсов раннера для сканирования «больших» проектов или для конкретного сканера.
Далее рассмотрим каждый метод по-отдельности, потому что мы использовали их все для оптимизации времени сканирования.
Использование кешей. Первая попытка ускорить Gosec
Gosec был одним из тех сканеров, которые использовались в нашем даунстриме с самого начала, поскольку Golang — это основаной ЯП для разработки. Проблема заключалась в том, что он в выполнял сканирование достаточно долго: медиана в районе 6 минут, тогда как нужно было дойти до 3-4.
Когда мы стали изучать, что происходит во время тестирования, обнаружили непустые директории c кешами результатов сборки и с используемыми зависимостями проекта.
Еще одно наблюдение: если запустить сканирование одного проекта два раза подряд, то мы всегда получали меньшее время сканирования для второго прогона, при этом разница была существенной — 20-40%. Далее мы стали прорабатывать идею с сохранением кэшей, их обновлением раз в n-релизов и использованием в пайплайнах разработки.
В итоге пришли к следующей схеме выполнения сканирования:
![](https://habrastorage.org/getpro/habr/upload_files/8e6/712/454/8e67124549d094e63dd7e41d0382d4a5.png)
Коротко о том, что на схеме: был реализован отдельный даунстрим для полного сканирования проектов с помощью Gosec без кэшей (да, даунстрим от даунстрима) и сохранением результирующих кешей в S3. Работа этого даунстрима не аффектила по времени основной даунстрим безопасности — там перед сканированием с помощью Gosec проверялось наличие кешей и, в случае их наличия, производилось их скачивание и применение при сканировании. Обновлялись кеши раз в пять пайплайнов на master-ветке.
Помогло ли это? Да, сканирование стало проводиться быстрее на, в среднем, минуту-полторы, при этом мы не потеряли в качестве (если кэши в той или иной степени были неактуальными, Gosec умный, он докачивал/дособирал то, что ему не хватало). Но это все равно было недостаточно быстро.
Смена инструмента
Коллеги, разрабатывающие наш PaaS, довольно давно использовали Golangci-lint для своих целей тестирования. Как известно Golangci-lint имеет поддержку линтера Gosec и в какой-то момент было решено сравнить Golangci-lint с линтером gosec и Gosec по времени и качеству сканирования.
По качеству сканирования результаты двух сканеров были идентичными. По скорости сканирования Golangci-lint был быстрее в 2-3 раза. Почему, осталось загадкой, но трейс сравнения этих инструментов остался, можно поудивляться:
![](https://habrastorage.org/getpro/habr/upload_files/346/e01/706/346e017069b83d032be5897ade5221f5.png)
Итого мы переехали на Golanci-lint, сократив среднее время сканирования с 6 минут до 3-4, как нам и было нужно.
Инкременты. Если сканер умеет, надо делать
{Не названный} коммерческий сканер, который по-взрослому выполняет taint-анализ, логично выполняет сканирование дольше всех. К счастью этот сканер умеет в инкрементальное сканирование. Поделюсь схемой сканирования:
![](https://habrastorage.org/getpro/habr/upload_files/fc2/0e6/1a8/fc20e61a8c34f4a1b9cf54a102f1a51f.png)
Схема очень похожа на схему сканирования Gosec с кешами, но есть небольшие различия:
в даннном случае реализован не даунстрим от даунстрима, а триггер отдельного пайплайна, в котором выполняется полное сканирование;
в S3 сохраняются кеши, собранные в результате полного сканирования, для использования в основном даунстриме безопасности и выполнения инкрементальных сканирований;
кеши обновляются каждые 3 дня, а не через n-релизов.
Почему схемы для сбора кешей Gosec'а и применения инкрементов для {Не названного} коммерческого сканера различаются? Потому, что было интересно понять плюсы и минусы двух разных подходов. Обсудить их, если будет интересно, можем в комментариях, ибо действительно свои плюсы и минусы у каждого подхода имеются.
Но какая бы ни была схема выполнения полных/инкрементальных сканирований, это принесло свои плоды, время сканирования значительно уменьшилось. Но, как и с Gosec-ом этого было недостаточно, поэтому был применен еще один способ оптимизации времени сканирования, о котором поговорим далее.
Увеличиваем количество ресурсов для «больших» проектов
Если ни инкременты, ни применение кешей, ни смена инструмента не помогли, можно попробовать увеличить количество ресурсов на раннере, выполняющем сканирование. Но сделать это целесообразно точечно.
Следует использовать раннеры с увеличенным количеством ресурсов только для сканирования тех проектов, которые сканируются дольше остальных или дольше оптимального для нас времени сканирования. Как правило, речь ведется о монолитах или сервисах с большим количеством строк кода.
Как это сделали мы: собрали список «больших» проектов и сохранили в переменную:
![](https://habrastorage.org/getpro/habr/upload_files/9ab/dcc/fba/9abdccfba0772300fe4b8ac33c0473e2.png)
В зависимости от наличия СI_PROJECT_PATH_SLUG текущего проекта в переменной GLOBAL_HEAVY_PROJECT_LIST выставляем тег с раннером, обладающим нужным количеством ресурсов:
![](https://habrastorage.org/getpro/habr/upload_files/9cc/7e6/a01/9cc7e6a01ab552d62aa6a749c16ae6b4.png)
По дефолту переменная TAG_VAR содержит имя раннера со стандартным минимальным количеством ресурсов и применяется для всех джоб безопасности.
Соответственно для нужного сканера вместо захардкоженного значения тега используется переменная, меняющее значение в зависимости от имени проекта и его наличия в списке «больших» проектов.
Подытожим про процессы оптимизации времени сканирования: была проделана большая работа, которая в конечном счете привела к нужным показателям.
![](https://habrastorage.org/getpro/habr/upload_files/de4/84c/ae6/de484cae65cfd7dfea551aa4fd9420e7.png)
Внедрение Quality Gate
Наконец, мы переходим к самой интересной теме — к оценке степени риска информационной безопасности и автоматизированном принятии решения о допуске сканируемой версии сервиса в релиз.
Я называю внедрение QG (особенно в блокирующем режиме) «венцом успеха» и вот почему: к моменту внедрения QG у вас должны быть:
настроены пайплайны для сканирования сервисов;
оптимизировано время сканирования так, что разработчики разучились отменять джобы безопасности;
налажен процесс обработки найденных файндингов в пайплайнах.
Все вышеперечисленное говорит о достаточной степени зрелости процессов AppSec/DevSecOps для начала проработки процессов включения QG.
Какие сопутствующие процессы/нюансы нужно проработать для включения QG:
Критерии прохождения QG. Вы должны выработать условия, при которых не допустите сканируемую версию сервиса в релиз. Как пример: наличие высокого уровня критичности уязвимости или наличие хотя бы одного секрета.
Нормативная база. Разработчики должны быть ознакомлены с тем, что потенциальная блокировка релиза при несоблюдении политик ИБ — это то, с чем они добровольно-принудительно согласились и подписали.
Подход включения QG в блокирующем режиме. Вручную или автоматизированно.
Нотификации о включении QG в блокирующем режиме. Очевидно, команда разработки сервиса, для которого включается QG в блокирующем режиме, должна знать об этом. Также о блокировке должны быть уведомлены AppSec BP, если блокировка включается автоматизированно.
Процесс изменения конфигурации или смены/добавления сканеров. Должны быть выявлены все случаи потенциально резкого и неконтролируемого изменения результатов сканирования, что может сказаться на прохождении QG.
Критерии для внедрения QG в блокирующем режиме.
Напомню, как это работает у нас. В нашем даунстриме есть отдельная джоба QG. Результат ее работы выглядит следующим образом:
![](https://habrastorage.org/getpro/habr/upload_files/668/3d3/cdd/6683d3cdd726ec76425c7ee903b73c3a.png)
Для принятия решения о прохождении QG веса найденных файндингов складываются отдельно по каждому типу сканирования и сравниваются со значением приемлемого уровня риска (threshold). Если получившееся значение больше, QG считается не пройденным.
Теперь про QG в блокирующем режиме. С начала процесса внедрения блокировок на QG мы вручную добавляли имена сервисов, СI_PROJECT_PATH_SLUG, для которых QG не просто должен был завершаться не успехом, но и сама джоба с QG должна падать, останавливая пайплайн и запрещая релиз. Как работает логика выбора статуса завершения джобы QG:
выполняется анализ количественных показателей, полученных в ходе сканирования безопасности - если общий вес по одному из типов сканирования превышает приемлемый уровень риска, выставляется флаг “QG не пройден”.
при выставлении флага “QG не пройден” выбираем с каким конкретно системным кодом ошибки завершить джобу - если сканируемый сервис входит в список на блокировку, завершаем джобу с кодом ошибки 1, если не входит - с кодом ошибки 2.
джобе с QG разрешаем завершаться успехом (pass with warning) при завершении с определенным кодом ошибки, в нашем случае - при завершении с кодом ошибки 2. Сделать это можно с использованием ключевых слов allow_failure/exit_codes в GitLab CI.
Таким образом, если сервис входит в список на блокировку и QG не был пройден, джоба завершится со статусом Fail
и остановит весь пайплайн. Если сервис не входит в список на блокировку — завершится со статусом Pass with warning
, не прерывая пайплайн.
На текущий момент мы реализовали сервис для автоматического включения QG в блокирующем режиме. Какими критериями мы оперируем при выборе сервисов готовых к блокировке на QG:
Наличие хороших оценок в сервисе Scoring, которые мы отправляем по результатам сканирования. Хорошие оценки (A и A+) означают, что в исходном коде сервиса не найдены секреты, нет уязвимостей высокого уровня критичности и нет использующихся с уязвимостями высокого уровня критичности библиотек.
Наличие файла конфигурации в репозитории с исключениями devsecops-configs. Наличие этого файла гарантирует, что была проведена работа по анализу результатов сканирования — были добавлены исключения или был добавлен пустой файл конфигурации, как подтверждение отсутствия проблем.
Еще раз вспомним про девелопер-центричность. Да, круто, что при появлении уязвимостей, результаты публикуются и для разработчиков, и для AppSec инженеров, а также влияют на метрики в Scoring и на успешность прохождения QG. Обратная сторона этого преимущества — проблема резкого ухудшения результатов сканирования. Факт того, что ухудшение результатов сканирования сразу сказывается на прохождении или непрохождении QG, становится большой проблемой.
Я бы выделил 2 причины «резкого» или «неожиданного» ухудшения результатов сканирования:
Когда мы сами меняем инструмент или конфигурацию сканеров. Наши примеры: смена Gosec на Golangci-lint (да, тут разницы в результатах не было, но стало понятно, что нужен процесс), а также добавление сканера секретов deepsecrets.
Обновление сигнатур в trivy: код не изменился, а уязвимость в библиотеке появилась — вечером QG проходили, утром, ничего не меняя, уже не прошли. Как вы понимаете, так работать не должно.
Что было сделано для реализации плавного перехода на новый сканер или изменения конфигурации:
внедрили grace-период равный двум неделям на обработку файндингов нового сканера;
внедрили атрибут для нового сканера в DSOP, который позволяет не учитывать файндинги нового сканера в Scoring и при оценке QG на протяжении grace-периода;
добавили нотификацию в лог джобы, если был найден файндинг нового сканера;
добавили визуализацию в Grafana для AppSec-инженеров, демонстрирующую количество файндингов нового сканера после дедупликации с другими (т.е. учитываются только уникальные файндинги).
Вот так выглядит нотификация в логе джобы:
![](https://habrastorage.org/getpro/habr/upload_files/856/9d8/30e/8569d830ea2a4d6d5e2918e97cd1c45d.png)
Атрибут нового сканера добавляется вручную. Он представлен в виде переменной с названием сканера, файндинги которого исключаются из метрик.
Для обработки случая с обновлением сигнатур trivy мы также реализовали grace-период, но равный трем дням. Три дня — это среднее время обнаружение уязвимости AppSec инженерами плюс 1 день с момента ее публикации. В среднем времени обнаружения заложены временные штрафы на:
обновление сигнатур;
проведение первого сканирования;
появление информации об ухудшении оценки.
Этих трех дней достаточно, чтобы обработать информацию о новой уязвимости и, в случае, если например уязвимость принята не применимой, добавить ее в исключения для одного сервиса или для всех сервисов сразу.
В grace-период данные о новых уязвимостях учитываются в Scoring, но не учитываются при прохождении QG.
Реализовано просто — данные о времени публикации уязвимости есть в отчете trivy и для реализации grace-периода достаточно сравнить текущую дату и дату публикации, чтобы учитывать или нет уязвимость при подсчете количественных показателей при прохождении QG.
Метрики
Важность метрик очевидна — ни один из вышеперечисленных процессов не мог бы быть реализован в их отсутствии. Также нельзя было бы утверждать об эффективности применяемых изменений для улучшения каких-либо практик и показателей.
Допустим, в анализируемом сервисе не были найдены файндинги, это потому, что действительно все хорошо или из-за того, что сканер не отработал?
Джоба с проверкой QG зеленая из-за отсутствия уязвимостей или из-за того, что разработчик отменил джобы со сканерами?
Если ваш даунстрим долго по времени выполняется, то из-за какой конкретно джобы? А в каком количестве сервисов она долго выполняется? Что общего у этих сервисов?
У вас все пайплайны зеленые в N количестве сервисов, а N — это какой процент от всех сервисов в продакшене?
На эти и на многие другие вопросы помогут дать ответы собранные и грамотно интерпретированные метрики. Какие метрики точно стоило бы собирать:
по покрытию;
количественные: количество файндингов до/после применения исключений;
временные: время выполнения отдельных джобов/пайплайна (медиана/95 перцентиль) без учета/с учетом подготовки окружения/старта раннеров;
статусы джобов: необходимо собирать все статусы джобов, pass/fail/warning/cancel, особенно, если статусы имеют дополнительную смысловую нагрузку, как например, в джобе c QG. Статус
Warning
означает не прохождение QG, но не заблокированный релиз, аFail
не прохождение QG и блокировку пайплайна. Статус джобыCancel
при включенном QG в блокирующем режиме — инцидент ИБ.
Итого
Для того, чтобы с полной уверенностью утверждать об успешном внедрении сканеров безопасности в пайплайны разработки, необходимо учесть большое количество сопутствующих процессов.
Я бы обобщил все вышеописанное следующим образом:
Внедрение сканеров безопасности в пайплайны разработки — это не решение проблем, а их приобретение =)
Просуммируем все, что следовало бы сделать для достижения минимально необходимого уровня зрелости процесса сканирования кода сервисов в пайплайне разработки:
выбрать сканеры;
выполнить конфигурирование сканеров и учесть не самое очевидное, например, возможность разработчиков влиять на результаты сканирования;
выстроить процесс обработки файндингов — в нашем случае это DSOP и девелопер-центричность;
настроить сбор метрик для подсчета эффективности всех сопутствующих процессов;
оптимизировать время до приемлемого — все зависит от вашей модели внедрения сканеров, но если как у нас, сканируете каждый коммит, следует сократить время насколько это возможно сохраняя эффективность;
{пункт со звездочкой} внедрить QG и по возможности внедрить возможность блокировки релизов в случае несоблюдения политик ИБ.
Возможно, у вас будут свои идеи как то, что освещалось в статье, можно улучшить или оптимизировать. Может каких-то процессов не хватает? Делитесь своим опытом в комментариях. Давайте развивать DevSecOps-практики вместе.
Tech-команда Купера (ex СберМаркет) ведет соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и на YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.
Nec_Romancer
Что за сервис Scoring?