БезопасностьDevOpsДеплой

Секреты и .env в продакшене: как не потерять ключи и где их хранить

5 мин чтения

Токен бота, пароль базы, API-ключ платёжной системы — секрет, попавший в исходный код, это утечка, которая ждёт своего часа. Достаточно открытого репозитория или одного скриншота, и ключ скомпрометирован. Разберём, где хранить секреты приложения и что делать, если ключ уже утёк.

Почему .env не коммитят

Файл .env с секретами добавляют в .gitignore и никогда не коммитят. Причины три:

  • История Git помнит всё. Удалить файл следующим коммитом недостаточно — секрет остаётся в истории и доступен в любом старом коммите. Чтобы вычистить его полностью, нужно переписывать историю (git filter-repo, BFG) и инвалидировать форки и клоны.
  • Публичные репозитории сканируются. Боты находят ключи в открытых репозиториях за секунды после пуша. Многие сервисы (включая GitHub) сами сканируют коммиты на утёкшие токены.
  • Секрет в коде нельзя сменить без релиза. Конфигурация, вшитая в исходники, меняется только новым деплоем — это неудобно и опасно.

Проверить, не попал ли .env под версионный контроль:

git ls-files | grep -E '\.env$'

Пустой вывод — хорошо.

Переменные окружения — базовый способ

Стандарт хранения конфигурации — переменные окружения (принцип twelve-factor). Приложение читает их из окружения процесса:

import os
DATABASE_URL = os.environ["DATABASE_URL"]  # явная ошибка, если не задано

Локально переменные удобно держать в .env (через python-dotenv, dotenv для Node.js или встроенный --env-file), а на сервере — задавать в окружении процесса. Файл .env остаётся только на машине разработчика и в .gitignore.

Шаблон без секретов: .env.example

Чтобы новый разработчик знал, какие переменные нужны проекту, в репозиторий кладут .env.example — список ключей без значений:

DATABASE_URL=
SECRET_KEY=
BOT_TOKEN=

Сам .env со значениями остаётся в .gitignore. Так состав конфигурации задокументирован и виден в репозитории, а реальные секреты в него не попадают. На сервере значения подставляются из окружения процесса.

Если секрет уже утёк

Главное правило: утёкший секрет считается скомпрометированным навсегда. «Удалить коммит» проблему не решает — ключ мог быть склонирован, закэширован и проиндексирован. Единственный надёжный путь — ротация:

  1. Выпустить новый ключ или токен.
  2. Отозвать старый (для Telegram-бота — через @BotFather, для БД — сменить пароль, для API — revoke в панели сервиса).
  3. Обновить переменную окружения в проде.
  4. Отдельно — вычистить секрет из истории Git, чтобы он не светился дальше.

Порядок важен: сначала ротация, потом чистка истории. Чистка без ротации бесполезна.

Где задавать секреты на сервере

  • systemd — через EnvironmentFile=/path/.env. Файл читается как KEY=value буквально, без шелл-синтаксиса.
  • Docker — через переменные окружения контейнера; для build-time секретов — --secret, а не ARG.
  • Managed-платформа — в панели проекта; платформа сама прокидывает переменные в процесс.

Чего делать не стоит — передавать секрет аргументом командной строки (--token=...): он виден в списке процессов (ps) и в истории shell.

Секреты в CI/CD

Пайплайн сборки и деплоя тоже не должен содержать ключей в коде. Их кладут в секреты CI (GitHub Actions secrets и аналоги) и прокидывают в окружение нужного шага. Две частые ошибки: вывести секрет в лог сборки (логи CI доступны команде, а иногда и публично — секрет в логе считается утёкшим) и прокинуть прод-секреты в пайплайны, которые запускаются на pull request из форков.

Когда .env перерос: менеджеры секретов

Для команды и нескольких окружений переменных окружения становится мало: нужны аудит доступа, ротация и единое хранилище. Тогда подключают менеджер секретов (HashiCorp Vault, секрет-хранилища облаков). Приложение получает секреты из него на старте, а не из файла. Для одного проекта это избыточно — переменных окружения достаточно.

Частые грабли

  • .env всё-таки закоммичен. Проверять git ls-files; при утечке — ротация, не просто удаление.
  • Секрет в логах. Печать конфигурации или объекта запроса в лог уносит туда и ключи. Логи тоже хранятся и пересылаются.
  • Секрет в слое Docker-образа. ENV SECRET=... и ARG в Dockerfile остаются в слоях образа и видны при его инспекции. Для сборки используют монтируемые секреты.
  • Секрет в клиентском коде. Ключ, попавший во фронтенд-бандл, виден любому в браузере. Секреты живут только на сервере.

Чеклист

  • .env в .gitignore, под версионным контролем секретов нет (git ls-files).
  • Конфигурация читается из переменных окружения, а не из кода.
  • На сервере секреты заданы через окружение, не аргументом командной строки.
  • При утечке — ключ ротируется, а не «удаляется коммитом».
  • Секреты не печатаются в логи и не попадают в Docker-слои и клиентский бандл.

Managed-платформа упрощает работу с секретами: переменные окружения задаются в панели проекта и прокидываются в процесс при деплое — без .env на сервере, ручного EnvironmentFile и риска закоммитить ключ. На Hostim секреты и DATABASE_URL хранятся в настройках проекта и доступны приложению как обычные переменные окружения.

Читать дальше