Секреты в коде: как не слить пароли и токены (переменные окружения, .env, Vault)

Секреты в коде: как не слить пароли и токены (переменные окружения, .env, Vault)

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

Сценариев утечки полно: демонстрация экрана на созвоне или запись урока - и пароль мелькнул при переключении вкладок. Скриншот с куском кода в чате поддержки. И самый частый случай - секрет попал в git. Репозиторий при этом может быть даже приватным: доступ к нему есть у коллег и подрядчиков.

Вывод простой: секреты в коде хранить нельзя. Разберёмся, что считать секретом, чем это грозит и как правильно хранить пароли, токены и ключи, чтобы не спалиться. Способов много - пройдём 13 подходов от простого к продвинутому.

Что вообще считается секретом

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

  • пароли к базам данных и сервисам;
  • токены и ключи API (платёжные системы, маркетплейсы, облака);
  • строки подключения, где пароль зашит внутри;
  • приватные ключи, сертификаты, secret key приложений.

Приватный репозиторий не делает секрет безопасным. Как только пароль попал в коммит, он остаётся в истории git навсегда, даже если вы удалите его следующим коммитом. Поэтому задача - не дать секрету попасть в код вообще.

Чем грозит пароль в коде

Прежде чем перейти к правильным способам, зафиксируем риски. Пароль в коде утекает через:

  • Запись экрана и скриншоты. Стрим, обучающее видео, демонстрация на созвоне - код с паролем попадает в кадр.
  • Git-историю. Даже удалённый позже секрет остаётся во всех прошлых коммитах и у всех, кто клонировал репозиторий.
  • Публикацию репозитория. Приватный проект однажды могут выложить в открытый доступ вместе со всей историей.
  • Логи и бэкапы. Код с секретом расходится по CI, логам сборки и резервным копиям.

Теперь к тому, как делать правильно.

Хорошие практики хранения секретов

Единственно верного способа нет - выбор зависит от того, где и с чем вы работаете, от чего этот пароль и какая у вас инфраструктура. Разберём подходы по группам: от простого выноса секрета из кода до полного отказа от постоянного пароля.

Вынести секрет из кода: простые способы

1. Переменные окружения

Самый простой и популярный способ. Секрет хранится в настройках операционной системы или в CI/CD-платформе (GitHub Actions, GitLab CI, Jenkins), а код читает его оттуда. В самом коде пароля нет - только имя переменной.

На Python читаем переменные окружения так:

import os

password = os.getenv('DB_PASSWORD')
if password is None:
    raise RuntimeError('Не задана переменная окружения DB_PASSWORD')

Если переменная не задана, скрипт упадёт с понятной ошибкой, но сам пароль нигде не засветится - ни в коде, ни в репозитории. Это разумный дефолт для большинства проектов.

2. Файл .env для локальной разработки

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

DB_PASSWORD=super_secret
API_TOKEN=y0_AgAAAA...

Читаем его через библиотеку python-dotenv:

from dotenv import load_dotenv
import os

load_dotenv()
password = os.getenv('DB_PASSWORD')

Ставится обычным pip:

pip install python-dotenv

Ключевое правило: файл .env всегда добавляется в .gitignore, чтобы он не попал в коммит. А в репозиторий кладётся шаблон .env.example с именами переменных без значений:

DB_PASSWORD=
API_TOKEN=

Новый разработчик клонирует проект, копирует .env.example в .env и вписывает свои значения локально. Так все знают, какие переменные нужны, но реальные секреты остаются только на машинах.

3. Конфиг вне репозитория с правами доступа

Секреты можно держать в отдельном конфиге, который лежит вне папки проекта - например, в ~/.config/myapp/secrets.ini или /etc/myapp/. В репозиторий такой файл не попадёт физически, а код читает его по абсолютному пути.

Важный нюанс - права доступа. Выставьте файлу режим, при котором его читает только владелец:

chmod 600 ~/.config/myapp/secrets.ini

Так секрет не увидят другие пользователи системы. Подход выручает на серверах и в скриптах, где не хочется тянуть лишние зависимости.

4. Хранилище секретов операционной системы (keyring)

У каждой ОС есть встроенное защищённое хранилище: Диспетчер учётных данных в Windows, Keychain в macOS, Secret Service в Linux. Пароль лежит в зашифрованном виде на уровне системы, а не в файле проекта.

В Python с ним работает библиотека keyring:

import keyring

# один раз сохранить
keyring.set_password('mydb', 'admin', 'super_secret')

# дальше только читать
password = keyring.get_password('mydb', 'admin')

Отлично подходит для локальных и десктопных приложений: пароль вводится один раз и дальше берётся из системного хранилища.

Централизованные менеджеры секретов

Когда секретов много и с ними работает команда, файлы и переменные окружения перестают масштабироваться. Нужен единый источник с правами доступа, аудитом и ротацией.

5. HashiCorp Vault

Корпоративный стандарт для хранения паролей и любых кредов. Внутри простой принцип: создаётся переменная, ей задаётся значение, а доступ раздаётся по правам - у одних сервисов есть права на чтение или изменение, у других нет.

Главная фишка Vault - динамические секреты. Он умеет генерировать пароли на лету и выдавать их на ограниченное время. Если злоумышленник украдёт такой пароль, через час тот уже истечёт. Vault берут, когда секретов много, ими пользуется команда и нужен аудит доступа.

6. Облачные менеджеры секретов

Если проект живёт в облаке, у провайдера обычно есть свой сервис для секретов: AWS Secrets Manager, Azure Key Vault, Google Secret Manager, Yandex Lockbox. Логика та же, что у Vault: секрет хранится в защищённом хранилище, приложение получает его по правам доступа (IAM), а не из файла. Плюс - тесная интеграция с остальной инфраструктурой облака и автоматическая ротация.

Рядом стоит KMS (Key Management Service) - сервис управления ключами шифрования. По схеме envelope-шифрования сам секрет шифруется ключом, который никогда не покидает KMS. Так устроено шифрование в облачных менеджерах под капотом.

7. SaaS-менеджеры секретов

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

Сюда же - CLI пароль-менеджеров (1Password CLI, Bitwarden CLI): секрет лежит в вашем менеджере паролей, а скрипт подтягивает его в переменную окружения в момент запуска.

Секреты в контейнерах и оркестрации

8. Секреты Docker

Docker и Docker Compose умеют пробрасывать секреты в контейнер отдельным механизмом (docker secrets), а не через переменные образа. Секрет монтируется как файл во время работы контейнера и не остаётся в слоях образа - в отличие от ENV, значение которого видно в истории образа.

9. Секреты Kubernetes

В Kubernetes есть объект Secret, но по умолчанию его значения хранятся в etcd лишь в base64 (это кодировка, а не шифрование). Поэтому в проде включают шифрование etcd через KMS и настраивают RBAC по принципу минимальных прав.

Для команд удобнее не хранить секреты в кластере вовсе, а подтягивать из внешнего менеджера: External Secrets Operator синхронизирует их из Vault или облака, а Sealed Secrets позволяет держать зашифрованный секрет прямо в git и расшифровывать только внутри кластера.

Шифрование секретов рядом с кодом

10. Шифрование конфигурационных файлов

Компромисс между удобством и безопасностью. Конфиг с паролями лежит прямо в репозитории, но в зашифрованном виде, а расшифровывается только при деплое мастер-ключом, который хранится отдельно. Для этого есть SOPS, git-crypt и Ansible Vault. Удобно, когда хочется держать конфигурацию рядом с кодом, но не светить значения.

11. Подмена секретов на этапе сборки

В некоторых инструментах (Docker BuildKit, Webpack) секрет можно подставить только во время сборки образа, не зашивая его внутрь. Например, пароль нужен, чтобы поставить зависимости из приватного репозитория, а после сборки он удаляется и в финальный контейнер не попадает.

Лучший вариант - вообще без пароля

Самый надёжный секрет - тот, которого нет. Два подхода позволяют не хранить постоянный пароль в принципе.

12. IAM-роли, workload identity и короткоживущие токены

В облаке приложению можно не выдавать пароль вообще, а привязать к серверу или контейнеру роль доступа (IAM-роль, service account, workload identity). Тогда SDK сам получает временные креды, которые живут минуты и обновляются автоматически.

Например, при работе с AWS через boto3 на сервере с привязанной ролью ключи прописывать не нужно:

import boto3

# ключи не заданы: boto3 берёт временные креды из IAM-роли сервера
s3 = boto3.client('s3')

Красть тут нечего: постоянного секрета нет, а временный протухает сам. Это современный стандарт для облачных сервисов.

13. Интерактивный ввод в рантайме

Если скрипт запускает человек, пароль можно вообще не хранить, а спрашивать при запуске. Модуль getpass вводит его скрыто, без отображения на экране:

from getpass import getpass

password = getpass('Пароль к БД: ')

Секрет живёт только в оперативной памяти на время работы программы и никуда не записывается. Подходит для ручных операций и разовых скриптов.

Как не закоммитить секрет случайно

Даже с переменными окружения легко один раз забыться и вписать пароль прямо в код. Чтобы этого не случилось, есть автоматическая защита.

Pre-commit хуки. Инструменты gitleaks, trufflehog, git-secrets и detect-secrets встраиваются в git и проверяют каждый коммит на наличие похожих на секреты строк. Нашли токен - коммит не пройдёт. Настраивается один раз через pre-commit:

pip install pre-commit

Дальше в файле .pre-commit-config.yaml подключаете нужный сканер, и он срабатывает автоматически перед каждым коммитом.

Secret scanning на стороне хостинга. GitHub и GitLab сами сканируют репозитории на утёкшие ключи известных сервисов и присылают предупреждения. Это не заменяет pre-commit, но ловит то, что проскочило.

Правильный .gitignore. Добавьте туда .env, файлы с ключами (*.pem, *.key), локальные конфиги. Проще всего - взять готовый шаблон gitignore для своего языка.

Что делать, если секрет уже утёк

Если пароль или токен попал в git или куда-то ещё, главное правило одно: секрет нужно немедленно сменить (ротировать).

Многие думают, что достаточно удалить строку из кода или переписать историю git. Это ошибка. Удалить секрет из истории (через git filter-repo или BFG) полезно, но недостаточно: копии уже могли утечь, а сам пароль остаётся действующим. Поэтому порядок такой:

  1. Отзовите или смените утёкший секрет в сервисе - это обязательный шаг.
  2. Выпустите новый и положите его в переменные окружения или менеджер секретов.
  3. Только потом при желании чистите git-историю, чтобы старое значение не смущало.

Ротация закрывает проблему по существу: даже если старый секрет где-то всплывёт, он уже не работает.

Чек-лист

Коротко, что должно быть в проекте:

  • В коде нет ни одного пароля, токена или ключа.
  • Секреты читаются из переменных окружения или менеджера секретов.
  • Файл .env в .gitignore, в репозитории только .env.example.
  • Настроен pre-commit хук со сканером секретов.
  • Для команды и продакшена - Vault, облачный менеджер или SaaS-сервис секретов.
  • Где возможно - вместо пароля используется IAM-роль с временными кредами.
  • Есть понимание, что делать при утечке: сначала ротация, потом всё остальное.

FAQ

Сколько всего способов хранить секреты?

Подходов много: переменные окружения, файл .env, конфиг вне репозитория, хранилище ОС (keyring), HashiCorp Vault, облачные менеджеры (AWS, Azure, GCP, Yandex Lockbox), SaaS-сервисы (Doppler, Infisical), секреты Docker и Kubernetes, шифрование конфигов (SOPS, git-crypt), подмена на этапе сборки, IAM-роли и интерактивный ввод. Выбор зависит от инфраструктуры.

Какой способ хранить секреты выбрать новичку?

Начните с переменных окружения и файла .env (с python-dotenv), добавив .env в .gitignore. Этого достаточно для большинства небольших проектов. Vault, облачные и SaaS-менеджеры нужны, когда над проектом работает команда.

Что такое переменные окружения простыми словами?

Переменные окружения - значения, которые хранятся в настройках операционной системы или CI, а не в коде. Программа читает их при запуске (в Python - через os.getenv), поэтому сам секрет в исходники не попадает.

Можно ли вообще не хранить пароль?

Да. В облаке приложению привязывают IAM-роль, и SDK получает временные креды сам, без постоянного пароля. А для ручных скриптов пароль спрашивают при запуске через getpass - он живёт только в памяти.

Секрет уже попал в git, что делать?

В первую очередь сменить (ротировать) сам секрет в сервисе - это главное. Чистка git-истории вторична: без ротации старый пароль остаётся рабочим, даже если убрать его из истории.

Заключение

Пароль в коде рано или поздно утекает - через запись экрана, скриншот или git. Держать секреты в коде нельзя, но способов обойти это масса: для старта хватит переменных окружения и файла .env, для команды - Vault, облачный или SaaS-менеджер, в контейнерах - секреты Docker и Kubernetes, а лучший вариант - вообще не хранить пароль и использовать IAM-роли с временными кредами. От случайного коммита защитит pre-commit хук со сканером. И помните главное правило на случай утечки: сначала ротация секрета, потом всё остальное.