Однажды разработчик пушит код на GitHub, а через 20 минут ему звонит взволнованный клиент: «Кто-то слил базу». Причина — строчка вида password = "super_secret_123" в коммите, который стал публичным. Такое случается чаще, чем кажется. Боты сканируют новые коммиты на GitHub в режиме реального времени и ищут именно это.
Разберём, как грамотно управлять секретами, чтобы не оказаться в такой ситуации.
Почему пароли в коде — это не просто «плохая практика»
Проблема не только в публичных репозиториях. Даже в приватном корпоративном git-хранилище секрет в коде — это:
- Доступ для всех, у кого есть права на репозиторий (разработчики, QA, DevOps, иногда подрядчики)
- Невозможность ротации без изменения кода и нового деплоя
- История в git — даже если удалить пароль из файла, он навсегда останется в коммитах
- Одинаковые секреты в dev и production, потому что «так удобнее»
Ротировать скомпрометированный пароль к базе в 3 ночи, когда об этом узнали — удовольствие сомнительное. Лучше выстроить нормальный процесс заранее.
Переменные окружения — минимальный порог
Самый простой шаг — вынести секреты в переменные окружения (environment variables). Вместо:
DB_PASSWORD = "my_password"
Пишем:
import os
DB_PASSWORD = os.environ.get("DB_PASSWORD")
Или в Node.js:
const dbPassword = process.env.DB_PASSWORD;
Секрет живёт вне кода — на сервере, в CI/CD, в Docker-контейнере. В репозиторий он не попадает.
Но и тут есть нюансы. Не стоит:
- Хардкодить дефолтные значения:
os.environ.get("DB_PASSWORD", "admin123") — это та же дыра
- Логировать переменные окружения при старте приложения
- Передавать секреты через аргументы командной строки (они видны в
ps aux)
.env-файлы: удобно, но осторожно
Для локальной разработки удобен .env-файл:
DB_HOST=localhost
DB_PORT=5432
DB_PASSWORD=local_dev_password
SMTP_API_KEY=key_here
Приложение читает его через библиотеки вроде python-dotenv или dotenv для Node.js. Удобно — не надо каждый раз экспортировать переменные вручную.
Главное правило: .env никогда не попадает в git. Для этого — в .gitignore:
.env
.env.local
.env.production
Вместо него в репозитории хранится .env.example с заглушками:
DB_HOST=
DB_PORT=5432
DB_PASSWORD=
SMTP_API_KEY=
Это шаблон для новых разработчиков — понятно, какие переменные нужны, но без реальных значений.
Для production .env-файлы — не лучший вариант. Их надо как-то доставить на сервер, где-то хранить, синхронизировать. Лучше использовать специализированные инструменты.
Vault от HashiCorp — промышленный стандарт
HashiCorp Vault — это централизованное хранилище секретов. Принцип работы:
- Секреты хранятся в Vault, зашифрованные
- Приложение при старте запрашивает нужные секреты по токену или через сервисный аккаунт
- Vault отдаёт секреты в runtime, а не при деплое
- Все обращения логируются
- Секреты можно ротировать без перезапуска приложения (dynamic secrets)
Пример: Vault умеет сам создавать временные credentials к PostgreSQL с ограниченным сроком жизни. Приложение получает логин и пароль, которые действуют, скажем, 1 час. После этого они автоматически удаляются. Даже если credentials утекут — они уже недействительны.
Интеграция с приложением выглядит примерно так:
import hvac
client = hvac.Client(url='https://vault.internal:8200', token=os.environ['VAULT_TOKEN'])
secret = client.secrets.kv.read_secret_version(path='myapp/db')
db_password = secret['data']['data']['password']
Vault — серьёзный инструмент с кривой обучения. Для небольших проектов он избыточен, но для enterprise или нагруженных систем с командой из нескольких разработчиков — оправдан.
Облачные решения: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault
Если инфраструктура уже в облаке — проще использовать нативные инструменты.
AWS Secrets Manager хранит секреты и умеет их ротировать. Например, автоматически меняет пароль к RDS-базе и обновляет секрет. Приложение получает секрет через SDK:
import boto3
import json
client = boto3.client('secretsmanager', region_name='eu-central-1')
response = client.get_secret_value(SecretId='prod/myapp/db')
secret = json.loads(response['SecretString'])
db_password = secret['password']
Стоимость: около $0.40 в месяц за один секрет плюс $0.05 за 10 000 API-запросов. Для большинства проектов — копейки.
GCP Secret Manager работает аналогично. Удобен тем, что права доступа управляются через IAM — сервисный аккаунт получает только нужные секреты.
Azure Key Vault — то же самое в экосистеме Microsoft, плюс умеет хранить сертификаты и ключи шифрования.
Общий принцип облачных хранилищ: приложение авторизуется не через пароль, а через IAM-роль или Workload Identity. Таким образом, нет никакого «ключа от ключей» — доступ определяется тем, под каким аккаунтом запущен сервис.
Kubernetes Secrets — и почему их недостаточно
В Kubernetes есть встроенный механизм — объект Secret:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
password: c3VwZXJfc2VjcmV0 # base64
Под монтирует секрет как переменную окружения или файл:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
Проблема: Kubernetes Secrets хранятся в etcd в base64, а не зашифрованными. Base64 — это не шифрование, это кодирование. Любой, у кого есть доступ к etcd или права kubectl get secret, видит значение.
Чтобы исправить это:
- Encryption at rest — включить шифрование секретов в etcd через
EncryptionConfiguration
- External Secrets Operator — оператор, который тянет секреты из Vault, AWS Secrets Manager или других хранилищ и создаёт Kubernetes Secrets на лету
- Sealed Secrets от Bitnami — секреты шифруются публичным ключом кластера и безопасно хранятся в git
External Secrets Operator — пожалуй, лучший подход для production Kubernetes-кластеров. Он связывает внешнее хранилище (Vault, AWS SM) с нативными Kubernetes Secrets, и разработчику не нужно думать о механизме получения секретов.
Секреты в CI/CD
CI/CD-пайплайны тоже нуждаются в секретах — для деплоя, отправки уведомлений, доступа к registry.
Все современные системы поддерживают хранение секретов:
- GitHub Actions: Settings → Secrets and Variables → Actions. Секреты доступны как
${{ secrets.MY_SECRET }}
- GitLab CI: Settings → CI/CD → Variables. Можно ограничить по окружению (только для
production)
- Jenkins: Credentials Manager
Важные правила:
- Не передавай секреты через артефакты или кэш — только через защищённые переменные
- Маскируй секреты в логах (большинство систем делает это автоматически, но проверь)
- Раздели секреты для разных окружений — staging и production не должны иметь одинаковые ключи
- Используй OIDC-аутентификацию вместо долгоживущих токенов, где возможно. GitHub Actions умеет получать временные AWS credentials через OIDC без хранения ключей
Аудит: что уже утекло
Если ты читаешь это и думаешь «а вдруг у нас уже что-то в репозитории» — это хороший вопрос.
Инструменты для поиска секретов в коде:
Gitleaks — сканирует git-историю и находит паттерны, похожие на секреты (API-ключи, пароли, токены):
gitleaks detect --source . --verbose
TruffleHog — аналогичный инструмент, умеет проверять не только локальные репозитории, но и GitHub/GitLab организации:
trufflehog git file://. --only-verified
detect-secrets от Yelp — можно встроить в pre-commit хуки, чтобы блокировать коммиты с потенциальными секретами:
pip install detect-secrets
detect-secrets scan > .secrets.baseline
Если нашёл секрет в истории — плохая новость: просто удалить файл не поможет. Нужно использовать git filter-repo или BFG Repo Cleaner для перезаписи истории. И, конечно, немедленно ротировать скомпрометированный секрет — считай его утекшим.
Чек-лист для production
Короткий список того, что стоит проверить прямо сейчас:
- В
.gitignore есть .env, .env.local, *.pem, *.key, config/secrets.yml и подобные файлы
- В репозитории нет строк с
password =, api_key =, secret = с реальными значениями
- Для каждого окружения (dev, staging, production) — отдельные секреты
- Секреты ротируются хотя бы раз в год, а лучше — автоматически
- В CI/CD нет секретов, захардкоженных в
.gitlab-ci.yml или Jenkinsfile
- Есть мониторинг аномального использования ключей (например, AWS CloudTrail или аналоги)
- Настроен pre-commit хук или CI-шаг с gitleaks/detect-secrets
Практика vs теория
Теория говорит «используй Vault». Практика — маленький стартап с одним бэкендером часто живёт на .env-файлах, которые передаются по Telegram. Это плохо, но это реальность.
Градация по сложности:
- Минимум: переменные окружения +
.gitignore + .env.example в репозитории
- Лучше: секреты в GitHub/GitLab CI Variables, разделённые по окружениям
- Хорошо: облачное хранилище (AWS Secrets Manager, GCP Secret Manager) с доступом через IAM-роли
- Enterprise: HashiCorp Vault с dynamic secrets, аудит-логами и автоматической ротацией
Даже переход с первого уровня на второй уже закрывает большинство типичных проблем.
Когда мы в REEXY делаем проекты с production-деплоем, базовые правила по секретам — часть стандартного процесса разработки. Это не опция, это гигиена. Настроить нормальное управление секретами при старте проекта занимает пару часов, а потом экономит нервы и деньги.
Главное правило одно: если секрет попал в git — считай, что он публичный, независимо от видимости репозитория. Строй процессы исходя из этого.