Привет, Хабр! Меня зовут Александр Карпов, я работаю в команде защиты приложений ИБ VK. Сегодня я хочу рассказать про наш процесс поиска секретов в каждом коммите в GitLab. У нас, как и у большинства компаний, был классический процесс борьбы с секретами – различные инструменты сканировали кодовую базу, и при обнаружении паролей, токенов и т.д. нам приходилось их ротировать. Главная проблема такого подхода в том, что проверить, действительно ли секрет был инвалидирован, не всегда возможно. По этой причине мы решили, что оптимально будет вычищать кодовую базу от любых секретов.
Предпосылки
Во-первых, Git history. Все, что когда-то попало в VCS, остается в ней навсегда. Многие считают, что если удалить секрет, то его никто не увидит. Но это не так. В истории коммитов этот секрет сохранится. Единственный способ его полностью удалить – это найти и вырезать тот коммит, в котором был добавлен данный секрет.
Во-вторых, Merge Request Conflicts. Как я уже сказал, для удаления секрета из GitLab необходимо вырезать коммит, а это приведет к изменению дерева коммитов, и при слиянии веток возникнут конфликты. Кроме того, если в удаленном коммите были важные изменения кода, то есть риск, что после удаления коммита функциональность приложения будет нарушена.
В-третьих, перевод всех проектов в видимость internal. Как это связано? Раньше все проекты были в видимости private, и если секрет попадал в репозиторий, его могла увидеть только заранее известная группа сотрудников. В internal же к проекту, а значит, и к секрету, могут получить доступ все сотрудники, которые авторизовались в GitLab. Поэтому, чтобы вычищать кодовую базу от секретов, нам необходимо как можно скорее проверять все новые коммиты на наличие в них паролей, токенов и т.д.
Цели
Итак, наша задача – сканировать каждый новый коммит на наличие секретов. В нашем GitLab более 20 000 проектов. Проанализировав активность разработчиков, мы установили для себя, что наш сервис должен выдерживать нагрузку в 500 коммитов в минуту, так как большинство команд вносят изменения под вечер. Дополнительно, нам необходимо укладываться в 5 минут – с момента попадания коммита в GitLab до момента обнаружения и уведомления сотрудника об этом. Связано это с тем, что за это время разработчик не успеет распространить секрет по веткам, и его будет достаточно просто удалить.
Сразу скажу, что классический процесс по борьбе с секретами мы сохранили, но дали разработчикам возможность следовать новому процессу для минимизации трудозатрат на устранение. Сейчас расскажу как, но сперва о том, что у нас уже было.
Что мы уже делали с секретами?
Для поиска секретов в коде у нас есть платформа VK Security Gate, которая различными инструментами ищет секреты в исходном коде. Это процесс не быстрый, так как помимо секретов код проверяется на наличие уязвимостей.
Как я уже говорил, после обнаружения секрета мы создавали задачу на их ротацию. Проверить, действительно ли секрет стал невалидным, в некоторых проектах достаточно проблематично, например, при валидации токена, используемого в межсервисной аутентификации в продуктах с микросервисной архитектурой.
Поэтому мы стали искать возможность, как анализировать все доставляемые изменения в репозитории на наличие в них секретов быстро и удобно для разработчиков.
Какие точки контроля в GitLab у нас есть?
Нам необходимо как-то узнавать о том, что новый коммит попал в репозиторий. Для этого есть несколько механизмов: CI/CD Jobs, Server-side hooks, Webhooks и Git hooks.
Да, можно еще проходиться рекурсивно по проектам и отслеживать дату их обновления с использованием GitLab API, но это очень небыстрый процесс и могут быть ограничения из-за Rate Limits.
CI/CD Jobs
Если использовать CI/CDJobs, то можно встроить проверки в pipeline, который будет каждый раз триггериться при push в репозиторий/ветку. У этого подхода есть несколько плюсов:
Более гибкая настройка под особенности каждого проекта.
Не замедляет push, так как pipeline отрабатывает после того, как изменения попали в репозиторий.
Не блокирует/замедляет процесс выкатки hotfix. При правильной настройке pipeline, проверка коммитов может выполняться параллельно процессу сборки и выкатки.
Но к минусам можно отнести:
Слишком легко обойти/отключить. Если файл конфигурации pipeline хранится в том же репозитории, то разработчик может внести в него изменения и отключить все проверки. Кроме того, даже если файл конфигурации вынесен в отдельный репозиторий, сотрудник с ролью Maintainer и выше может изменить настройки CI/CD, указав свой файл конфигурации pipeline;
Дополнительные затраты по ресурсам на runner. Все jobs выполняются на runner, а значит для добавления проверок придется добавлять на них ресурсы или подключать новые, чтобы не замедлять основные задачи.
Server-side hooks
Server-side hooks – это промежуточные скрипты, которые отрабатывают на стороне GitLab при доставке изменений в репозиторий. Они бывают трех видов:
Pre-receive, Update, Post-receive.
Pre-receive и Update отличаются тем, что Pre-receive запускается один раз на каждый push, а Update запускается на каждую ветку (если в одном push было много веток). Схематично это показано на рисунке ниже.
Post-receive отрабатывает после того, как изменения попали в GitLab, и это не даст возможности блокировать доставку изменений с секретами в репозиторий.
Плюсы Pre-receive и Update в том, что они дают возможность блокировать push и возвращать пользователю ошибку.
А минусы:
Задержки при каждом push в репозиторий. Каким бы оптимальным ни был процесс проверки изменений на наличие в них секретов, все равно это накладывает дополнительное время на обход каждого коммита и проверки с помощью регулярных выражений. При большом количестве изменений данный процесс проверки может занимать достаточно много времени (несколько минут). Не каждый разработчик захочет ждать завершение команды git push больше нескольких секунд.
Риск заблокировать или замедлить hotfix. При обнаружении секретов в коммитах мы можем заблокировать push в репозиторий. Если командам разработки нужно срочно выкатить hotfix, мы можем замедлить данный процесс, что для бизнеса критично. Да, можно написать правила для игнорирования hotfix, но такое возможно только при условии, что команд не много и/или процессы у всех команд едины. В случае, если каждая команда выкатывается так, как ей удобно, писать под всех исключения очень непростая задача.
Сложность поддержки на стороне серверов GitLab. В GitLab нет удобного механизма для поддержки Server-side hooks. Если нам понадобится доработка скриптов, то администраторам придется каждый раз обновлять файлы вручную непосредственно на сервере.
Git hooks
Git hooks — это скрипты, которые выполняются локально на компьютерах разработчиков при создании коммитов. Они бывают нескольких видов: Pre-commit, Prepare-commit-msg, Commit-msg и Post-commit. Что они дают?
Выполняются до попадания изменений в репозиторий.
Выполняются на компьютере разработчика. Когда разработчик хочет локально закоммитить изменения, запускаются git hooks (при наличии).
Но к минусам можно отнести:
Сложно распространять и поддерживать. Для распространения необходимо попросить каждого разработчика скачать скрипты и положить их в определенную директорию “.git/hooks”. И при каждом обновлении разработчикам необходимо поддерживать данные скрипты в актуальном состоянии.
Легко отключить. Разработчикам достаточно указать флаг “--no-verify” для того, чтобы отключить проверки.
Webhooks
Механизм в GitLab CE/EE, который позволяет при определенных триггерах отправлять информацию о push на сторонние сервисы. Три вида Webhooks: на проект, на группу и системные ( то есть на все проекты и группы в VCS). Что здесь хорошего:
Не замедляют push и выкатки, выполняются в фоновом режиме.
Системные webhook не требуют дополнительных ресурсов при внедрении со стороны команд DevOps-продуктов.
Но Webhooks отрабатывают после того, как изменения попали в VCS, и тут нет возможности блокировать push.
Наш путь
Мы выбрали Webhooks, потому что при внедрении этого механизма меньше рисков (блокировка hotfix, замедление работы GitLab), их легче поддерживать и есть гарантии, что их не отключат (только если по заявке администраторов GitLab). Приятная особенность еще и в том, что они не подвержены подмене автора коммита.
Но по мере использования Webhooks мы столкнулись с некоторыми особенностями данного механизма:
GitLab может отключить отправку на наш сервис уведомлений. Сделать он это может на время (Temporarily disabled) при условии, что наш сервис вернет код ответа 5xx, а может отключить сразу при получении 4xx кода и до момента, пока мы вручную не активируем данный webhook (Permanently disabled webhooks).
Webhook не содержит сами изменения. В нем содержится вся необходимая информация для получения diff у коммита. Однако diff может быть пустой, если изменений слишком много. Это настраивается в GitLab администраторами.
GitLab может отправить два совершенно одинаковых webhooks на один push. В результате могут появиться дубликаты данных.
Учитывая все ограничения и особенности, мы разработали следующую архитектуру для нашего сервиса:
В Gitlab настроены системные hooks (Webhooks) для отправки уведомлений о событиях push на наш Listener. Listener максимально простой сервис, задача которого поймать событие и положить его в Kafka. Core забирает события, получает diff по API, так как Webhooks не содержат сами изменения, и отдает всю необходимую информацию в Scanner. Scanner анализирует diff на предмет наличия в нем секретов. После этого он возвращает в Core массив сработок (если такие были). Далее Core подготавливает информацию для нотификации и укладывает ее в БД. Данный процесс является частью платформы VK Security Gate и использует те же таблицы для укладки данных.
Модуль Notifier с определенной периодичностью забирает из базы данных новые секреты и отправляет уведомление пользователям в корпоративный мессенджер VK Teams. Если сотрудник захочет обработать сработки в мессенджере, Bot обработает нажатие на кнопки, проверит валидность данных (проверка JWT) и переведет статус сработки в нужный.
При этом для поиска секретов используется Gitleaks с некоторыми доработками:
Мы убрали обвязку cmd и оставили только движок для поиска секретов в фрагменте. Это сделано для уcкорения и удобства обработки сработок и ошибок, а также недопущения зомби процессов.
Расширили модель Finding для добавления информации о реальном авторе коммита.
Используем свои собственные Fingerprints.
Немного про Fingerprint
Мы отказались от идеи использовать Fingerprint-инструментов по умолчанию, так как внутри платформы VK Security Gate уже есть много различных инструментов и нам нужен устойчивый универсальный Fingerprint, который бы позволил проводить дедупликацию сработок от разных инструментов.
Для секретов мы долгое время использовали AST Fingerprints. Для их формирования необходимо выкачивать весь проект с исходным кодом, так как в формировании участвует полный путь до секрета. Так как в новом процессе поиска секретов мы работает только со сниппетом кода и нам необходимо использовать тот же Fingerprint для дедупликации сработок, нам бы пришлось выкачивать исходный код проекта. Процесс скачивания не быстрый и на больших объемах с ним может быть много проблем. Поэтому сейчас используется новый Fingerprint, который состоит из пути до файла и захэшированных частей секрета.
Core-компонент отвечает за:
Взаимодействие с Kafka.
Дедупликация не только на уровне конкретного Webhook, но и на уровне найденных ранее секретов.
Интеграция с платформой VK Security Gate.
В случае возникновения ошибок при обработке конкретного webhook вывод его в отдельную очередь ошибок Kafka. При возникновении ошибок нам важно не блокировать основной поток, чтобы другие события могли и дальше оперативно обрабатываться.
Многопоточность. N потоков для обработки Webhooks.
Подготовка данных для уведомления пользователей.
Нотификация пользователей. Здесь при реализации мы уделили внимание следующим моментам:
Rate Limit для конкретного пользователя.
Отключение/включение нотификации всех пользователей для проекта/группы
Группировка секретов по Fingerprint в одно сообщение.
Предоставление возможности обработки секретов как в мессенджере, так и в UI платформы VK Security Gate.
К чему мы пришли
Как я ранее говорил, мы оставили классический процесс поиска секретов. При этом мы даем разработчикам возможность устранять секреты более оперативно и с меньшими затратами путем уведомления их в течение 5 минут. Это значит, что нет необходимости ротировать секрет – достаточно вырезать коммит из истории и сделать force push в репозиторий, чтобы изменить всю историю. При этом не будет конфликтов в дальнейших Merge Request, так как это новые изменения.
Комментарии (10)
danielsedoff
13.11.2024 16:07Когда у подобных инструментов ИБ возникают проблемы с производительностью, внезапно это минус тысяча осмысленно проведенных рабочих часов. И это наверняка не единственный, а 596-й досмотр на пути кода к релизу.
bigbamblbee
13.11.2024 16:07Вы бы безо всяких секретов сделали приложение таким, чтобы им можно было худо-бедно пользоваться. Кому нафиг нужны ваши секреты в коде апплетов каких-то игр, сервисов и рекламы, если приложение ни черта не работает? Что мобильная версия, что веб-версия, гигабитный у тебя интернет или нет - не важно, будет лагать, тупить и не прогружать контент. Да сделайте вы хотя бы, чтобы оно грузилось, я рекламу хочу нормально посмотреть, не в блюре!
Newbilius
13.11.2024 16:07(пожимаем плечами) Нормально работает что в браузере, что на Android.
Всё же крик души о личных проблемах с продуктом в целом под техническими постами о частностях - не самая красивая и продуктивная штука.
akimaksim
13.11.2024 16:07не вижу большой проблемы в том, что секреты полежат некоторе время в гите, кто имеет доступ в репу скорее всего их и так знает. а когда их фоном нашли - разрабу по рукам, ревьюеру по рукам, лиду команды по башке. И принудительно меняем секреты для всего проекта, секреты уже можно с гита не удалять и коллеги скажут теплые слова в адрес автора на очередном ретро и попросят так больше не делать...
event1
13.11.2024 16:07кто имеет доступ в репу скорее всего их и так знает
Проблема возникает когда доступ в гит имеют много людей, а копия репы имеется на многих машинах. Вероятность утечки вырастает кратно и хранение токенов становится очень рискованным.
ilyaholoday
13.11.2024 16:07Основной плюс такого подхода - это почти моментальная реакция на пролитый секрет, когда разработчик разрабатывает и поддерживает фичи одного продукта - то да, ему не составляет труда вернутся к своему коду и ротировать его. А так если это виртуальные команды, ГПХ или проектный офис - то тут уже начинаются: "Я уже не занимаюсь этим продуктом", "Я больше не отвечаю за этот продукт, сходи к Сан Санычу" (который через нескольких итераций отправит к нему обратно), "Я не знаю/не помню что за секрет" аутсорсер больше не работает в компании и так далее. В целом этот пойнт был бы невалиден в случае быстрого фонового поиска, но кода много, а инженеров ИБ мало + проще поправить дефект пока ты в контексте, и шанс что это будет исправлено гораздо больше, чем через 2 месяца. Такая история :)
dude_sam
Если не путаю, то можно изменить сommit и его hash в истории и "руками" пересчитать все последующие hash. Но это, мягко говоря, сломает всем всё. Да и в локальных машинах останется много где.
olku
Все-таки форс пуш в бранч даже не мейн тоже рискованный вариант. Да, до приглашения на мерж реквест в бранче может лишь один разраб висеть, но если на сообщение по хуку он забил, то может опоздать затереть историю. Сломать билд надежнее. А чтобы не тормозить хотфиксы, пайплайн настриваем так, чтобы пропускать их как есть, но репортить по хуку как описано в статье.
Protos
В Azure Repos не работает. Если была ссылка прямо на коммит и где осталась, то по старой ссылке перейти на коммит не сложно. Нужно удалять ссылки отовсюду.