Как обезопасить Git-репозитории от утечки приватных ключей

Приватный ключ в Git-репозитории — это не просто «лишний файл». Если он настоящий, доступ по нему уже можно считать скомпрометированным. Даже если вы быстро удалили файл и сделали новый коммит, ключ мог попасть в историю, в форки, в pull request, в CI-логи или в архив артефактов.

Главное правило: если приватный SSH-ключ попал в Git, его нужно не только удалить, но и отозвать, заменить новым и проверить, где ещё он мог засветиться.

Что именно нужно защищать

Чаще всего в Git попадают приватные SSH-ключи: id_rsa, id_ed25519, deploy_key, файлы с расширением .pem или .key. Они могут использоваться для доступа к GitHub, GitLab, Bitbucket, внутреннему Git-серверу, деплою на сервер или доступу к инфраструктуре.

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

Если ключ уже попал в репозиторий

В такой ситуации не нужно спорить, «нашёл ли кто-то этот ключ». Практичнее действовать так, будто его уже скопировали.

  1. Отзовите старый ключ. Удалите его из GitHub, GitLab, Bitbucket или другого хостинга. Если это deploy key, отключите его. Если ключ использовался для доступа к серверам или облаку, закройте и этот доступ тоже.
  2. Остановите использование ключа локально. Удалите его из ssh-agent, если он там был: ssh-add -l покажет список загруженных ключей, а ssh-add -D очистит агент.
  3. Проверьте историю коммитов. Приватный ключ мог быть в одной старой ветке, которую уже забыли. Для быстрого поиска можно использовать команды вроде:
    git log --all -S "BEGIN OPENSSH PRIVATE KEY" --oneline
    git log --all -S "BEGIN RSA PRIVATE KEY" --oneline

    Для больших репозиториев лучше использовать сканеры: gitleaks, trufflehog или аналогичные инструменты.

  4. Удалите ключ из истории, если это оправданно. Обычный git rm убирает файл только из текущей версии. В истории он останется. Для полной очистки используют git filter-repo или BFG Repo-Cleaner. После этого потребуется принудительная отправка изменений и согласование с командой, потому что история коммитов изменится.
  5. Проверьте побочные места. Ключ мог попасть в pull request, fork, CI-артефакт, лог сборки, тикет в Jira, скриншот, ZIP-архив, резервную копию или локальный клон старого участника команды.
  6. Создайте новый ключ. Не копируйте старый «с небольшими изменениями». Сгенерируйте новый, добавьте passphrase и подключите его только к нужному сервису или проекту.
  7. Проверьте логи доступа. Посмотрите, не было ли подозрительных пушей, чтений приватных репозиториев, деплоев или изменений прав доступа.

Удаление из истории снижает риск, но не отменяет ротацию. Это как с паролем: если он утекл, его меняют, а не просто стирают сообщение в чате.

Откуда ключи обычно появляются в Git

На практике утечки редко выглядят как осознанное решение «сейчас я закоммичу ключ». Чаще это случайность.

  • Кто-то добавил в репозиторий всю папку .ssh вместе с приватными ключами.
  • Ключ положили рядом с проектом, чтобы «было удобно», а потом случайно закоммитили.
  • Файл попал в .env, а .env оказался в Git.
  • Ключ вставили в CI-переменную, а потом включили подробные логи сборки.
  • Deploy key положили в репозиторий, потому что «так проще настроить деплой».
  • Ключ был в старой ветке, которую давно не удалили.
  • Файл попал в форк или pull request, даже если в основной ветке его быстро удалили.

Как правильно хранить SSH-ключи для Git

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

Не стоит использовать один id_rsa для всего: работы, личных проектов, серверов, деплоя и облака. Если такой ключ утечёт, вы не сможете быстро понять, где именно проблема, и будете вынуждены менять доступ почти везде.

Практичный набор правил такой:

  • Используйте отдельные ключи. Например, id_ed25519_github_work, id_ed25519_gitlab_work, deploy_key_staging.
  • Задавайте passphrase. Даже если ключ хранится на вашем ноутбуке, passphrase спасает при краже файла или резервной копии.
  • Предпочитайте Ed25519. Для большинства Git-хостингов это хороший современный выбор. Если организация требует RSA, используйте не меньше 3072 или 4096 бит, а старые и слабые ключи заменяйте.
  • Проверьте права доступа к файлам. На Linux и macOS приватный ключ не должен быть доступен всем пользователям системы.
    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/id_ed25519_work
    chmod 644 ~/.ssh/id_ed25519_work.pub
  • Не синхронизируйте ~/.ssh через облачные папки. Резервные копии допустимы, но они должны быть защищены. Синхронизация приватных ключей между личным и рабочим компьютером — частый источник проблем.
  • Для высокого риска используйте аппаратные ключи. Если хостинг и ваши инструменты поддерживают SSH security keys, это заметно снижает риск кражи приватного материала с диска.

Пример создания рабочего ключа:

ssh-keygen -t ed25519 -C "work@example.com" -f ~/.ssh/id_ed25519_work

После этого подключите публичный ключ к нужному Git-хостингу, а приватный оставьте только на рабочей машине.

Настройте SSH так, чтобы не путать ключи

Когда ключей несколько, удобно использовать ~/.ssh/config. Это не только удобнее, но и безопаснее: вы явно указываете, какой ключ для какого хоста использовать.

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_github_work
  IdentitiesOnly yes
  ForwardAgent no
  AddKeysToAgent yes

Host gitlab.company.com
  HostName gitlab.company.com
  User git
  IdentityFile ~/.ssh/id_ed25519_gitlab_work
  IdentitiesOnly yes
  ForwardAgent no
  AddKeysToAgent yes

Здесь особенно полезны две настройки:

  • IdentitiesOnly yes — Git не будет перебирать все ключи из агента и подставлять первый подходящий.
  • ForwardAgent no — ключ не будет пересылаться дальше на удалённый сервер. Это защищает от ситуации, когда скомпрометированный сервер может использовать ваш агент.

Что добавить в .gitignore

.gitignore не спасает от уже отслеживаемых файлов, но помогает не положить лишнее в новые коммиты. Для Git-проекта я бы добавил туда как минимум:

.ssh/
*.pem
*.key
id_rsa
id_rsa.*
id_ed25519
id_ed25519.*
*.ppk
.env
.env.*

С .env.* есть нюанс: часто в проектах хранят .env.example с примерами переменных. Это нормально, но внутри не должно быть реальных ключей, токенов и приватных данных.

Если файл уже попал в Git, .gitignore его не остановит. Такой файл нужно удалить из индекса и истории, а ключ — заменить.

Сканеры секретов: не надежда, а нормальная страховка

Человек может не заметить приватный ключ в папке с десятком файлов. Сканер заметит быстрее. Хорошо, когда проверка стоит в двух местах: локально перед коммитом и в CI перед merge request или pull request.

Для локальной проверки перед коммитом часто используют pre-commit hooks. В CI можно запускать gitleaks, trufflehog или встроенный secret scanning хостинга. Например, локально можно запустить:

gitleaks detect --source . --redact

Но сканеры не должны быть единственной защитой. Они могут пропустить обфусцированный ключ, ключ в base64, в архиве или в странном формате. Поэтому сканеры работают лучше всего вместе с правилами доступа, CI-настройками и дисциплиной команды.

CI/CD — место, где ключи часто утекают не через код

В CI ключи часто нужны для деплоя. Но это не значит, что их надо класть в репозиторий. Правильнее хранить их в секретнице CI-системы или во внешнем секрет-менеджере.

Практичные правила для CI:

  • Не храните приватные ключи в репозитории. Даже в папке deploy/keys, даже «зашифрованными», если пароль от шифрования ходит в чате команды.
  • Используйте минимальные права. Для деплоя лучше отдельный deploy key или отдельная учётная запись с доступом только к нужным репозиториям и окружениям.
  • Делайте ключи read-only, если достаточно только чтения. Например, для загрузки зависимостей из приватного репозитория не нужен ключ с правом записи.
  • Не выводите секреты в логи. Опасны set -x, debug-режим, подробные ошибки библиотек и команды, которые печатают содержимое файла.
  • Создавайте файл ключа только на время задачи. После использования удалите его с раннера.
  • Не сохраняйте ~/.ssh в артефактах сборки. Артефакты часто живут дольше, чем кажется, и доступны большему кругу людей.
  • Используйте ephemeral runners. Временные раннеры безопаснее долгоживущих машин, на которых могут оставаться следы старых сборок.

Пример безопаснее, чем хранение ключа в коде, но всё равно требует аккуратности:

printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keygen -y -f ~/.ssh/deploy_key > /dev/null

Если ключ хранится в CI как base64 или multiline PEM, проверьте, что переносы строк не сломались. Частая ошибка — ключ вроде бы есть, но SSH ругается на формат, и разработчик включает подробные логи, где ключ наконец-то «видно».

Какие подходы использовать в зависимости от риска

Подход Что защищает Когда использовать Ограничения
.gitignore Не даёт случайно добавить типовые файлы ключей в новые коммиты В любом проекте Не удаляет уже отслеживаемые файлы и не ловит все форматы
Pre-commit hooks Останавливает коммит с секретом до отправки в удалённый репозиторий Для команд, где разработчики часто коммитят локально Локальные hooks можно обойти или не обновить
Сканеры в CI Проверяют пуши, merge request и pull request Почти всегда, особенно в командах Могут давать ложные срабатывания и пропускать замаскированные секреты
Server-side hooks Не дают принять в репозиторий коммит с приватным ключом Для self-hosted GitLab, Gitea, Gitolite и внутренних серверов Нужно поддерживать и тестировать правила на стороне сервера
Deploy keys Ограничивают доступ отдельного процесса или окружения Для деплоя, сборки, выгрузки зависимостей Нужно следить за ротацией и правами
SSH-ключи с passphrase Защищают приватный файл при краже с ноутбука или из резервной копии Для всех разработчиков
Оцените статью
PEFile — Безопасность и технологии простым языком