По Секрету Всему Свету Или Всё, Что Случается В Git Остаётся В Git (Не Совсем)

Intro

Представим себе ситуацию: мы что-то в спешке коммитим в git и по какой-то причине в наш коммит попадает пароль, файл с токенами или какой-то иной секрет. И кажется, что логичное решение это удалить последний коммит.

1
2
git reset --hard HEAD~1
git push origin main --force

Но git push --force может быть запрещен (как минимум для ветки main), да и в целом это не совсем верный подход, даже если вы работаете над своим небольшим проектом в гордом одиночестве. И это в целом не про удаление секретов, а про отмену последнего коммита и его применение в удаленном репозитории, через git reflog доступ к коммитам остаётся.

Например:

1
2
3
4
git reflog
git reset --hard your_hash_id
git branch new_branch_name your_hash_id
git log -p -2

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

Где искать эти ваши секреты?

Где в git можно найти секреты:

  • в CI/CD
  • в самом коде и конфигах Это мы можем покрыть обычными сканерами секретов, так у нас всё как код.

На каких этапах взаимодействия с git мы можем обнаружить захардкоженные секреты:

  • При коммите (pre-commit)
  • При пуше (pre-push)
  • При получении изменений (pre-receive)

Из хуков пожалуй будет интересен pre-commit, его и рассмотрим. Но для любопытных есть GitGuardian есть документация по использованию ggshield в pre-push и pre-receive.

Pre-commit hook

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Создаём pre-hook
mkdir -p ~/.git-hooks
touch ~/.git-hooks/pre-commit
chmod +x ~/.git-hooks/pre-commit

# Добавляем следующее содержимое в ~/.git-hooks/pre-commit:

#!/bin/sh
trufflehog git file://. --since-commit HEAD --branch=main --results=verified,unknown --fail

# Добавляем хук в конфиг
git config --global core.hooksPath ~/.git-hooks

Теперь при коммите Trufflehog будет проверять на наличие секретов.

Это не является единственным решением или точкой проверки, так как при необходимости проверку можно обойти.

Так же для pre-commit хуков можно использовать фреймворк pre-commit или Husky.
Trufflehog так же не лучшее решение, но об этом чуть ниже.

Поиск секретов в кодовой базе

Pre-commit hook это отлично, но не гарантирует полное отсутствие секретов в коде. Для этого есть множество как OSS так и платных инструментов. Что-то можно так же использовать в Pre-commit hook, что-то в CI/CD, а что-то как sidecar-сервис.

Сами инструменты в целом похожи могут сканировать как локально (код, репозитории, конфиги), так и удалённые репозитории.

Инструменты

  • Gitleaks - Лёгкий и быстрый сканер для обнаружения токенов в Git-репозиториях. Идеально подходит для интеграции в CI/CD.
  • ggshield CLI-инструмент, которое запускается в локальной среде или в среде CI, обнаруживает более 400+ типов секретов. Но привязан к GitGuardian.
  • Detect Secrets - Инструмент от Yelp с гибкой настройкой и системой плагинов.
  • Talisman - Инструмент от ThoughtWorks, предотвращающий случайную отправку данных в репозиторий.
  • Secretlint - Линтер секретов для .env, JSON, YAML и др. без зависимости от Git. Лучше запускать в Docker.
  • TruffleHog - Мощный инструмент для поиска, классификации и анализа утечек ключей и токенов в коде. Поддерживает более 800 типов секретов и может проверять их актуальность.

Warning

Сейчас OSS-версия Trufflehog имеет значительные ограничения по источникам сканирования. Из 21 источника в OSS поддерживается только 9. Подробнеей смотрите в документации.

НазваниеgitCI/CDLocal
TruffleHog
Gitleaks
ggshield
Detect Secrets
Talisman
Secretlint

Сервисы

  • GitGuardian - обнаруживает более 350 типов секретов как в публичных, так и в приватных репозиториях. Предлагает подробные отчёты и интеграции с различными платформами. Платный, но есть демо на 30 дней.
  • TruffleHog Enterprise - 20+ интеграций, 800+ типов секретов, демо нет.

Рекомендации

  • Интеграция в CI/CD: Gitleaks, ggshield, detect-secrets.
  • Pre-commit хуки: detect-secrets, talisman, gitleaks.
  • Сканирование файлов вне Git: detect-secrets, secretlint, ggshield.
    Из-за ограничений сейчас Trufflehog рекомендовать не буду. Но на общем фоне вполне интересно выглядит detect-secrets, хоть он и требует особого подхода, но обновлялся он почти год назад. Так же неплохо работает ggshield, если вас не смущает привязка к GitGuardian.

Удаляем неудаляемое

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

  • git filter-branch - встроенный в git инструмент для изменения истории. Требует хорошего знания git и работает довольно медленно.
  • git-filter-repo - инструмент для удаления секретов в коде.
  • BFG Repo-Cleaner - более простая и быстрая альтернатива git-filter-branch для очистки истории Git-репозитория.

Возьмем для примера BFG Repo-Cleaner. Для начала нам нужен список секретов

1
2
3
4
5
6
7
8
# Сканируем и генерируем отчёт в json
trufflehog git file://. --branch=main --results=verified,unverified,unknown -j > report.json

# Уберем лишнее, вроде ssh-ключей: 
cat report.json  | jq -r '.Raw | select (test("^-") |not)' > secrets.list

# Скачиваем bfg
curl https://repo1.maven.org/maven2/com/madgag/bfg/1.15.0/bfg-1.15.0.jar -o bfg.jar

Далее нам потребуется bare repo,что означает, что ваши обычные файлы не будут видны, но это полная копия базы данных Git вашего репозитория. И на данном этапе стоит сделать бэкап репозитория.

Так же для простоты использования bfg используется алиас для java -jar bfg.jar (например: alias bfg='java -jar ~/.local/bin/bfg.jar')

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Клонируем репозиторий с опцией --mirror
git clone --mirror git://example.com/repo-with-secrets.git

# Удаляем ssh-ключи (если они были)
bfg --delete-files id_{dsa,rsa}  my-repo.git

# Удаляем секреты в репозитории, которые содержатся в подготовленном файле
bfg --replace-text secrets.list . repo-with-secrets.git

# Для защищенных веток (например main) добавляем флаг --no-blob-protection
bfg --replace-text secrets.list repo-with-secrets.git --no-blob-protection

BFG изменит ваши коммиты и все ветки и теги.
После чего проведём небольшую уборку с git reflog и git gc.

1
2
cd some-big-repo.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive

Если всё в порядке, можем запушить изменения (обратите внимание, что поскольку мы использовали --mirrorфлаг, этот push обновит все ссылки в репозитории) :

1
git push

Outro

Как можно заметить, удаление коммита не является решением проблемы, а удаление секрета это недостаточная мера, его в любом случае необходимо ротировать.
Покрыть только инструментами поиск секретов довольно сложно. Да и с увеличением количества репозиториев задача становится сложнее. Нельзя всё решить хуками или проверками в CI/CD. Вариант с живущим параллельным сканером в целом неплоха, но лучше применять всё в комплексе.
Секреты в принципе удаляемы, но всё же есть риск что-нибудь сломать.

Не храните секреты в коде и конфигах (есть же встроенные средства Gitlab и Github, или даже Vault) и будет вам счастье.


Github Docs: Removing sensitive data from a repository
Git-SCM: Customizing Git - Git Hooks
TruffleHog Docs: Pre-commit hooks
Git-SCM: git-reflog
Habr: Коммитить нельзя сканировать: как мы боремся с секретами в коде

0%