Gitleaks в GitLab CI: первый прогон по истории нашёл три живых AWS-ключа
Интегрировали gitleaks в GitLab CI для поиска секретов в репозиториях. Первый исторический скан выдал три действующих AWS-ключа разработчиков - ротировали немедленно.
Обнаружение секретов в git-репозиториях стало критической практикой после серии утечек AWS credentials в 2018
В 2018 году тема утечки AWS-ключей из публичных репозиториев стала настолько громкой, что её сложно было игнорировать. Истории одна за другой: разработчик закомитил .env с production-credentials, через несколько минут GitHub-боты нашли ключи, AWS-счёт вырос на несколько тысяч долларов за ночь - биткоин-майнинг за чужой счёт работает круглосуточно. Нас эта тема касалась напрямую: клиентов с AWS-инфраструктурой достаточно, DevOps-практики везде разные, и мы решили разобраться что у нас с этим вообще происходит.
Инструмент выбрали быстро - gitleaks. Написан на Go, умеет сканировать как текущее состояние репозитория, так и полную историю коммитов. Именно исторический скан нас и интересовал: даже если ключ удалили неделю назад, в git-истории он живёт.
Интеграция в пайплайн
Добавить gitleaks в GitLab CI несложно. Базовая конфигурация выглядит так:
secrets-detection:
stage: security
image: zricethezav/gitleaks:latest
script:
- gitleaks --repo-path=. --verbose --redact
allow_failure: true
allow_failure: true поставили намеренно - на первом этапе хотели видеть находки, не блокируя пайплайны. Потом это уберём. --redact режет сами значения секретов в выводе, оставляя только местоположение - разумная предосторожность, чтобы не хранить credentials в логах CI.
Gitleaks использует набор регулярных выражений для поиска известных паттернов: AWS Access Key ID (AKIA...), AWS Secret Access Key, приватные RSA-ключи, GitHub-токены, Slack-токены и ещё несколько десятков форматов. Можно добавлять кастомные правила через конфигурационный файл - мы так добавили паттерны для внутренних API-ключей клиента.
Исторический скан - отдельная задача
Текущий репозиторий - это одно. Но у нас есть репозитории с историей в несколько лет. Исторический скан запускается отдельно, не в рамках обычного пайплайна, а как разовая операция:
gitleaks --repo-path=/path/to/repo \
--verbose \
--report=/tmp/leaks-report.json \
--report-format=json \
--commit-from=$(git rev-list --max-parents=0 HEAD)
Вот тут нас и накрыло.
Первый же прогон по одному из репозиториев выдал несколько находок. Часть - ложноположительные, знакомые по другим инструментам сканирования: тестовые значения в комментариях, примеры в документации, fixture-файлы с явно фейковыми данными. Но три находки оказались настоящими AWS Access Key ID.
Три ключа и что с ними было
Разбирали находки вместе с командой разработки. Все три ключа принадлежали разработчикам - не сервисным аккаунтам, а личным AWS IAM-пользователям. История у каждого своя:
Первый - ключ попал в .env.example в коммите двухлетней давности. Кто-то заполнил «пример» реальными значениями, видимо скопировал из своего рабочего окружения. Файл пытались удалить в следующем коммите, но из истории он никуда не делся.
Второй - закомичен в скрипт деплоя на короткое время: разработчик хардкодил ключ «временно», потом убирал, но несколько коммитов с ключом в истории остались. Это классика.
Третий - в конфигурационном файле Ansible, который когда-то был в публичном репозитории, потом репозиторий перевели в private. Ключ туда попал ещё до смены видимости.
Статус ключей проверили через AWS CLI - два из трёх были ещё активны. Один уже деактивирован - видимо разработчик его ротировал, но не знал что история коммита сохранилась.
Ротировали немедленно, не дожидаясь проверки CloudTrail на предмет несанкционированного использования. Точнее - сначала ротировали, потом смотрели CloudTrail. Ничего подозрительного не нашли, но это не означает что ничего не было: может просто не заметили, может данные уже смотрел кто-то тихо.
Pre-commit хук
Параллельно с настройкой CI-скана добавили pre-commit хук для локального использования. Gitleaks умеет работать в этом режиме:
gitleaks --pre-commit --verbose --staged
Хук проверяет staged-изменения перед коммитом. Положили скрипт в репозиторий, добавили инструкцию по установке. Принудительно не навязываем - инструмент на стороне разработчика, не можем гарантировать что хук установлен у всех. Именно поэтому CI-скан важнее: он срабатывает независимо от локального окружения.
Что получили в итоге
Результат работы двух дней - сканирование активно работает в пайплайнах нескольких репозиториев, исторические базы прогнаны. Три живых ключа ротированы.
Шум от ложноположительных на первых запусках большой. Gitleaks умеет брать allowlist - список хешей коммитов или конкретных путей, которые нужно игнорировать. Неделю ушла на разметку: что реальное, что нет. Это ручная работа, без неё отчёт нечитаем.
Два наблюдения по итогам:
- Исторический скан важнее текущего. Текущий состав файлов - это то, что дошло до продакшна. История - это всё что было по пути, включая «временные» хаки. Именно там живут самые старые скелеты.
- CI не заменяет культуру. Инструмент поймает то что уже закомичено. Pre-commit хук поймает то что собираются закомитить - но только если установлен. Разговор с командой о том почему credentials не должны быть в коде - это отдельная работа, gitleaks её не делает.
Для клиентов, которым проводим аудит безопасности, исторический скан репозиториев теперь входит в стандартный чеклист. Не как отдельная услуга - как часть общей картины того, что и где лежит.