Привет Хабр! Меня зовут Игорь Игнатьев и сегодня я хочу рассказать про нашу внутреннюю AppSec платформу Security Gate. Начну с предпосылок для ее создания, подробно опишу архитектуру решения и поделюсь открытиями и маленькими неожиданностями, которые ждали нас (и могут ждать любого в рамках построения похожего инструмента).

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

Предпосылки 

Предпосылки для внедрения AppSec‑платформы (как и в целом, любых DevSecOps‑практик) в любой компании, зачастую, схожи. Для нас ими стали: активное развитие продуктов и сервисов VK, наращивание кодовой базы и рост количества языков программирования, а также применение разнородных решений в различных продуктах компании. Все это требовало контроля безопасности, но анализировать это в ручном режиме невозможно. Поэтому в какой‑то момент мы решили, что пора создавать собственное централизованное решение, которое объединит лучшие практики и будет масштабируемо на все сервисы и продукты компании.

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

Чтобы определить потребности в центральном инструменте, мы проанализировали объем кодовой базы, которую предстоит исследовать. В итоге, в корпоративных хранилищах исходного кода мы насчитали больше 1,2 млрд строк кода и более 25 тысяч репозиториев, которые оказались очень разнородными: это и огромный разброс по технологическому стеку, и различные системы хранения кода (собственные разработки и Open Source), а также CI/CD-системы (собственные разработки и Open Source). 

Осознав масштаб, мы сформировали для себя ряд принципов, которые хотелось воплотить в новой платформе:

  • простая интеграция с платформой всего разнообразия используемых систем и решений;

  • независимость от конкретной CI/CD-системы и системы хранения кода;

  • возможность динамического внедрения новых инструментов и тюнинга правил, чтобы гармонично развивать платформу, адаптируясь под новые запросы разработчиков;

  • удобный UX/UI (но сегодня не об этом) ;

  • скорость работы.

Архитектура платформы

Архитектурно VK Security Gate можно разделить на три составных блока:

  • Первый — WebUI, представляющий собой веб‑приложение, фронтэнд, которого использует VKUI(https://github.com/VKCOM/VKUI), а бэкенд разделён на две части: пользовательскую и администраторскую, и написан на Golang.

  • Второй блок — модули, отвечающие за процесс приёмки исходного кода и инициализации сканирования, а также за обработку отчёта о сканировании и укладку его в базу данных (Orchestrator, Core, Processor).

  • Третий блок — SAST Unit, отвечающий за оркестрацию SAST‑анализаторов, выполняющий непосредственный анализ исходного кода, а также дедуплицирующий их отчёты в единый отчёт о сканировании для дальнейшей укладки в базу данных.

Отдельно стоит отметить, что вся платформа работает как cloud native (включая анализаторы кода), находится под управлением Kubernetes и может легко масштабироваться. 

Процесс сканирования

Учитывая нашу гетерогенную среду, в качестве основной точки интеграции мы выбрали API. Здесь и начинается платформа Security Gate — на ее контуре функционирует модуль Orchestrator. Он готов принять исходный код из любой точки внутри компании, и доступен для вызова из CI/CD как вручную, так и автоматизировано. Но поскольку подавляющее большинство продуктов используют автоматизированные CI/CD‑конвейеры для сборки, мы также подготовили универсальный клиент, представляющий собой бинарный файл, который передает архив с исходными кодами на анализ в Security Gate. Клиент инкапсулирует в себе всю логику взаимодействия с API Security Gate, и всё, что требуется для любой системы CI/CD — настроить шаг, содержащий вызов клиента Security Gate, добавив несколько команд в сценарий сборки, и забыть об этом навсегда. Клиент Security Gate сделает все остальное сам: создаст новый экземпляр сканирования, уберет из кодовой базы лишние медиафайлы, упакует его в архив, и передаст на сканирование в платформу.

После получения архива с исходным кодом и инициализации сканирования, за дело берётся SASTUnit. Он начинает динамический подбор инструментов с помощью сервиса, который анализирует, из чего состоит переданный проект, какие языки программирования и фреймворки в нём используются, и выбирает набор инструментов, которыми нужно проанализировать данную кодовую базу. После анализа состава проекта, запускается независимое (параллельное) сканирование несколькими анализаторами сразу — то есть на сканирование одного и того же кода может запуститься, например, три различных SAST‑инструмента, анализатор зависимостей и анализаторы секретов, и все они, просканируют этот код независимо, каждый в своем контейнере. 

Такое решение мы выбрали по нескольким причинам. Во‑первых, для достижения цели по максимально качественному анализу, поскольку мы можем использовать несколько инструментов с отдельными наборами правил, а не сосредотачиваться на каком‑то одном. Во‑вторых, это позволяет нам избежать зависимости от одного вендора.

По итогам сканирования, мы получаем для одного и того же кода сразу несколько отчетов. Как с ними быть дальше? Очевидно, что результаты сканирования могут пересекаться. Чтобы объединить отчеты, запускается модуль Resulter, осуществляющий объединение отчётов и дедупликацию результатов, а также генерирующий для каждого срабатывания уникальный идентификатор‑отпечаток (fingerprint), позволяющий в дальнейшем однозначно идентифицировать его на платформе.

Многие инструменты генерируют свои идентификаторы срабатываний, которые зачастую не совпадают друг с другом. Чтобы всё объединить, мы не связываем инструменты друг с другом и не пытаемся сопоставить между собой разные идентификаторы, а пытаемся каждое из найденных разными инструментами срабатываний скоррелировать непосредственно с кодом. Для этого мы и формируем собственный идентификатор, используя абстрактное синтаксическое древо (abstract syntax tree, AST). Это позволяет нам однозначно указать место в коде, которое вызывает срабатывание разных анализаторов, причём данный отпечаток устойчив, даже если строка, в которой анализатор обнаружил уязвимость, «переехала» ниже или выше по коду, пока она остаётся в той же функции. Такой способ построения идентификаторов срабатываний показал свою эффективность с течением времени и позволил нам успешно группировать и дедуплицировать срабатывания между инструментами. 

Сканирование кода завершено: мы получили единый результирующий набор срабатываний по всему проекту. Но как быть с данными предыдущих сканирований, ведь это сканирование может быть не первым для проекта, а вторым, десятым, сотым? 

Для этого мы храним всю историю результатов для каждого проекта, продукта и даже ветки.

На этом уровне возникает второй этап дедупликации уязвимостей — по веткам. Отдельный модуль платформы — Security Gate Processor, проводит новый этап дедупликации срабатываний, теперь с учетом результатов предыдущих сканирований и их разбора командами разработки и AppSec‑инженерами (триажа). Слияние с предыдущими сканированиями производится в три этапа:

  1. Слияние соответствия. Все срабатывания, обнаруженные ранее (в предыдущих сканах) и присутствующие в финальном отчете (текущем скане), обновляются. Также обновляются фрагменты кода (±15 строк от места сработки), после чего происходит определение нового статуса. Например, если ранее закрытая уязвимость появилась вновь – то из «Исправлено» её статус вернётся в «Не обработано». Таким образом мы не создаем дубли записей в БД, а на основе собственного идентификатора храним одно состояние срабатываний. К примеру, если оно ранее уже было помечено как ложноположительное, то после очередного сканирования не потребуется заново проводить его разбор.

  2. Новые срабатывания. Тут все просто: появление нового идентификатора, отсутствующего в ветке, предполагает создание новой записи в БД.

  3. Закрытие срабатываний. Если уязвимость была устранена в коде и повторное сканирование это подтверждает (то есть её идентификатор отсутствует в новом результирующем отчете, полученном от SAST Unit), то срабатыванию проставляется статус «Исправлено», поскольку это свидетельствует о том, что либо уязвимый код был исключен, либо был исправлен, и анализаторы более не выявляют в нём уязвимостей.

Трудности, с которыми пришлось столкнуться

За всеми историями успеха стоят трудности, которые помогли их достичь. Делимся своими — возможно, наш опыт будет полезен при создании похожих продуктов.

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

  2. Инструменты периодически падают. Если сканирование завершено успешно, а один из инструментов не сработал, то те уязвимости, которые он сгенерировал ранее закроются автоматически, потому что их нет в общем отчете. Для этого мы внедрили специальный механизм, позволяющий пережить отказ инструмента. Основан он на добавлении новой связи в СУБД между срабатыванием и инструментом. Получился он достаточно громоздким (об этом можно написать ещё одну отдельную статью ), ведь у срабатывания может быть одновременная связь с несколькими инструментами. Но зато, закончив работу над данным механизмом, мы можем пережить отказ любого количества тулов, не теряя срабатывания в ветках.

  3. Слишком много веток. Мы ожидали, что к нам будут приходить с ветками «master», «main», «stage», «test», «dev», но оказалось, что в рамках одного проекта внутри могут быть десятки и даже сотни веток (а на момент написания статьи и тысячи веток). Даже при наличии функционала миграции и наследования статусов ранее разобранных срабатываний, наследовать статусы между четырьмя ветками возможно, но между сотнями — практически нереально. Требовался новый подход к решению задачи. 

Кроссветочный триаж

Когда мы говорим про процесс разработки, оперирующий большим количеством веток (feature‑ветки, релизные и другие), то складывается понимание, что различия в кодовой базе между ветками могут быть небольшими в разрезе всего проекта. Да, на самом старте разработки это может быть иначе, но чем старше проект, тем меньше процент отличий кодовой базы между ветками. Если мы возьмем модуль на 10–100 тысяч строк кода, то зачастую отличия между ветками не превышают 5% — то есть основная кодовая база остается статичной. Собственно, и результаты сканирования основной части также не будут отличаться друг от друга.

А поскольку у нас есть устойчивые AST‑идентификаторы срабатываний, которые стабильны не только в рамках одной ветки, но и между ветками тоже, мы решили упростить жизнь всем пользователям платформы Security Gate, и провести последнее, финальное слияние срабатываний — слияние между всеми ветками. Прямо в модели данных создается новая сущность — кроссветочное срабатывание, собранное на основе результатов сканирования по всем веткам проекта.

Давайте посмотрим, к чему это привело на практике. Предположим, у нас есть проект, который подключен к платформе Security Gate и в котором есть 10 различных веток. В 8 из 10 есть общий участок кода, в котором SAST‑анализаторами была обнаружена потенциальная уязвимость (допустим, что она ложноположительна). Благодаря формированию единой кроссветочной записи в модели данных, нам достаточно единожды разобраться с этим срабатыванием и определить его статус. Даже если у проекта появится еще 10 веток, содержащих это ложноположительное срабатывание, нам не придется заново тратить ресурсы на его разбор — статус будет автоматически наследоваться.

 Данное решение мы считаем жемчужиной нашей платформы, поскольку оно позволило драматически снизить количество срабатываний на разбор. Если погрузиться в цифры, то за 7 месяцев работы платформы в сырых отчетах от инструментов у нас было примерно 180 миллионов срабатываний SAST‑анализаторов, включая сканирование всех подключенных к платформе проектов. После внедрения дедупликации между анализаторами (то есть создания общего отчета о сканировании), цифра снизилась примерно до 150 миллионов уникальных срабатываний. Следующий этап — дедупликация с данными предыдущих сканирований веток — уменьшил эту цифру до 26 миллионов срабатываний. И наконец, после внедрения кроссветочной обработки, осталось всего 770 тысяч уникальных срабатываний. Конечно, это огромное снижение нагрузки на всех — и на Application Security инженеров, и на разработчиков.

Тут стоит отметить, что мы пошли еще дальше, и итоговая цифра стала еще меньше, но об этом мы расскажем в следующих статьях.

Таким получился краткий рассказ про нашу платформу VK Security Gate. Если вам интересно больше узнать про то, как мы проводим статический анализ и развиваем платформу, а также конкретные аспекты функционирования нашей платформы — пишите в комментариях, и мы раскроем эти темы в будущих статьях. 

PS: Этот материал был написан по мотивам моего выступления на конференции VK JT. Запись моего доклада можно посмотреть по ссылке.

Комментарии (11)


  1. amarkevich
    09.07.2024 20:37
    +2

    > 1,2 млрд строк кода и более 25 тысяч репозиториев

    зеркало гитхаба?


    1. Bksz Автор
      09.07.2024 20:37
      +3

      Ах если бы)
      Но на самом деле первичный анализ действительно был такой. При этом действительно не весь код продовый. Очень много того, что накопилось и лежит мертвым грузом. Тем не менее "боевого" кода действительно прям очень много.


  1. Alekstet
    09.07.2024 20:37
    +1

    Security Gate❣️❣️❣️


    1. puzankov7
      09.07.2024 20:37
      +1

      Ностальгируешь?


      1. Bksz Автор
        09.07.2024 20:37
        +1

        Тём а ты?