Приватный ключ в Git-репозитории — это не просто «лишний файл». Если он настоящий, доступ по нему уже можно считать скомпрометированным. Даже если вы быстро удалили файл и сделали новый коммит, ключ мог попасть в историю, в форки, в pull request, в CI-логи или в архив артефактов.
Главное правило: если приватный SSH-ключ попал в Git, его нужно не только удалить, но и отозвать, заменить новым и проверить, где ещё он мог засветиться.
- Что именно нужно защищать
- Если ключ уже попал в репозиторий
- Откуда ключи обычно появляются в Git
- Как правильно хранить SSH-ключи для Git
- Настройте SSH так, чтобы не путать ключи
- Что добавить в .gitignore
- Сканеры секретов: не надежда, а нормальная страховка
- CI/CD — место, где ключи часто утекают не через код
- Какие подходы использовать в зависимости от риска
Что именно нужно защищать
Чаще всего в Git попадают приватные SSH-ключи: id_rsa, id_ed25519, deploy_key, файлы с расширением .pem или .key. Они могут использоваться для доступа к GitHub, GitLab, Bitbucket, внутреннему Git-серверу, деплою на сервер или доступу к инфраструктуре.
Публичный ключ с расширением .pub сам по себе не даёт доступа. Опасен именно приватный файл. Но публичные ключи тоже обычно не коммитят без необходимости: они не являются секретом, но создают лишний шум и могут запутать команду.
Если ключ уже попал в репозиторий
В такой ситуации не нужно спорить, «нашёл ли кто-то этот ключ». Практичнее действовать так, будто его уже скопировали.
- Отзовите старый ключ. Удалите его из GitHub, GitLab, Bitbucket или другого хостинга. Если это deploy key, отключите его. Если ключ использовался для доступа к серверам или облаку, закройте и этот доступ тоже.
- Остановите использование ключа локально. Удалите его из
ssh-agent, если он там был:ssh-add -lпокажет список загруженных ключей, аssh-add -Dочистит агент. - Проверьте историю коммитов. Приватный ключ мог быть в одной старой ветке, которую уже забыли. Для быстрого поиска можно использовать команды вроде:
git log --all -S "BEGIN OPENSSH PRIVATE KEY" --oneline git log --all -S "BEGIN RSA PRIVATE KEY" --onelineДля больших репозиториев лучше использовать сканеры:
gitleaks,trufflehogили аналогичные инструменты. - Удалите ключ из истории, если это оправданно. Обычный
git rmубирает файл только из текущей версии. В истории он останется. Для полной очистки используютgit filter-repoили BFG Repo-Cleaner. После этого потребуется принудительная отправка изменений и согласование с командой, потому что история коммитов изменится. - Проверьте побочные места. Ключ мог попасть в pull request, fork, CI-артефакт, лог сборки, тикет в Jira, скриншот, ZIP-архив, резервную копию или локальный клон старого участника команды.
- Создайте новый ключ. Не копируйте старый «с небольшими изменениями». Сгенерируйте новый, добавьте passphrase и подключите его только к нужному сервису или проекту.
- Проверьте логи доступа. Посмотрите, не было ли подозрительных пушей, чтений приватных репозиториев, деплоев или изменений прав доступа.
Удаление из истории снижает риск, но не отменяет ротацию. Это как с паролем: если он утекл, его меняют, а не просто стирают сообщение в чате.
Откуда ключи обычно появляются в 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 | Защищают приватный файл при краже с ноутбука или из резервной копии | Для всех разработчиков |
