Как хранить API-ключи и секреты: не только .env-файлы
Практика4 минРедакция Innovialabs
Коротко
Секреты не должны попадать в код и репозиторий: приложение получает их из переменных окружения, а сами значения хранятся в секрет-хранилище CI/CD или облака, отдельно для каждой среды, с минимальными правами и регулярной ротацией.
Коротко
Секрет — всё, что даёт доступ: API-ключи, пароли от баз данных, токены ботов, ключи подписи. Правильная схема выглядит так:
- В коде — только имя переменной, например
DATABASE_URL. - Значения хранятся в секрет-хранилище: в настройках CI/CD, хостинга или в менеджере секретов облака.
- У каждой среды — свои ключи: разработка, staging и production не делят один ключ.
- У каждого ключа — минимум прав и понятный порядок ротации.
Файл .env — удобный инструмент для локальной разработки, но не способ хранения секретов в команде.
Почему секреты утекают
- Ключ в коде. Кто-то вставил его «временно», и он ушёл в коммит. История Git хранит его даже после удаления строки.
.envв репозитории. Забыли про.gitignore— файл виден всем, у кого есть доступ к коду, включая подрядчиков и форки.- Ключ во фронтенде. Всё, что попадает в браузер или мобильное приложение, можно извлечь. Секретный ключ платёжной системы в JavaScript-бандле — уже утечка.
- Логи и ошибки. Приложение печатает конфигурацию при старте или трекер ошибок сохраняет заголовки запросов.
- Пересылка в мессенджерах. Ключ отправили коллеге в чат, и он остался там навсегда.
Публичные репозитории постоянно сканируют боты, поэтому ключ в открытом коде нужно считать скомпрометированным сразу.
Переменные окружения — правильно
Приложение читает конфигурацию из окружения и падает при старте, если обязательного значения нет. Так ошибка видна сразу, а не в момент первого платежа.
const required = ["DATABASE_URL", "PAYMENT_API_KEY"] as const;
for (const name of required) {
if (!process.env[name]) {
throw new Error(`Missing env variable: ${name}`);
}
}
Правила для .env:
- Добавьте
.envи.env.*в.gitignoreдо первого коммита. - Храните в репозитории
.env.example— только имена переменных и безопасные заглушки. - Не логируйте
process.envцеликом. - В Next.js и похожих фреймворках переменные с публичным префиксом (например,
NEXT_PUBLIC_) попадают в браузер — не кладите туда секреты.
Секреты в CI/CD и на хостинге
В GitHub Actions, GitLab CI и других системах есть встроенное хранилище секретов. Значения шифруются, маскируются в логах и подставляются только в нужные задачи.
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Привязывайте секреты к окружениям (environments) и защищайте production: деплой только из основной ветки, подтверждение ответственным человеком.
Когда сервисов становится много, переходите на менеджер секретов — облачный или self-hosted. Он даёт централизованный аудит, версии и ротацию.
Отдельные ключи для каждой среды
| Среда | Какие ключи | Кто имеет доступ |
|---|---|---|
| Локальная разработка | Тестовые ключи, sandbox платёжных систем | Разработчики |
| Staging | Отдельные ключи с ограниченными данными | Команда и CI |
| Production | Боевые ключи | Только CI/CD и несколько ответственных |
Если ключ разработчика утечёт, production не пострадает. А по логам сразу видно, из какой среды пришёл запрос.
Ротация и минимальные права
Минимальные права (least privilege): ключ для отправки писем не должен уметь удалять домены, а пользователю базы для отчётов не нужно право на запись. Многие сервисы позволяют ограничить ключ по действиям, IP-адресам или ресурсам — пользуйтесь этим.
Ротация — плановая замена ключа. Чтобы она не была болезненной:
- Приложение должно уметь работать с новым ключом без пересборки — достаточно обновить секрет и перезапустить.
- Порядок: создать новый ключ, обновить хранилище, задеплоить, убедиться, что всё работает, отозвать старый.
- Обязательно меняйте ключи при уходе сотрудника или подрядчика, у которого был к ним доступ.
Частые ошибки
- Один «универсальный» ключ с полными правами на все проекты.
- Боевые ключи на ноутбуках разработчиков.
- Секреты в Docker-образе через
ENVили скопированный.env. - Никто не знает, где и какие ключи используются, — ротацию невозможно провести.