Прямой ответ
Управление секретами — лестница зрелости, и команда должна осознанно стоять на своей ступени: (1) секреты в переменных окружения, не в коде и не в git; (2) зашифрованные файлы секретов в репозитории (SOPS+age — секрет лежит рядом с кодом, но читаем только держателями ключей); (3) централизованное хранилище с аудитом, динамической выдачей и ротацией (Vault/облачные Secrets Manager) — когда сервисов и команд много. Критерий перехода — не «модно», а боль: секреты дублируются в десяти местах и ротация занимает день → SOPS; нужны аудит доступа, краткосрочные credentials и десятки потребителей → Vault. И три неизменных правила на любой ступени: ротация возможна (секрет, который нельзя отозвать, — мина), доступы минимальны (не все секреты нужны всем), утечка обрабатывается по плану (отозвать, выпустить новый, расследовать — а не «будем надеяться»).
Ступень 1: env — база, а не решение
Переменные окружения — интерфейс доставки секрета в процесс (12-factor), и на локальной машине разработчика .env в gitignore + .env.example без значений — правильный минимум (базовые правила). Но как хранилище команды env не масштабируется: «передай новый пароль» — это чат/почта (история переписки — копия секрета), «какие секреты у сервиса» — не отвечает никто, ротация — обход всех машин руками. Признак, что пора на ступень 2: больше трёх окружений/сервисов или больше двух человек, которым нужны секреты. Отдельная дисциплина — git-гигиена: секрет, однажды закоммиченный, живёт в истории навсегда (защита — pre-commit-хуки detect-secrets/gitleaks в pre-commit-фреймворке, сканирование пушей — secret scanning GitHub); «удалили коммит» не помогает — только ротация скомпрометированного значения.
Ступень 2: SOPS+age — секреты в git, но шифрованные
SOPS (Secrets OPeSrationS) шифрует значения в YAML/JSON/env-файлах, оставляя ключи видимыми: secrets.enc.yaml с DATABASE_URL: ENC[AES256_GCM,...] лежит в репозитории, расшифровка — приватным ключом age (или PGP/KMS). Механика команды: sops secrets.enc.yaml (редактирование в расшифрованном виде с шифрованием на сохранении), sops -d (вывод), .sops.yaml в корне задаёт, чьи ключи расшифровывают какие файлы (creation rules по путям — prod-секреты читают не все). Преимущества, которые закрывают боль ступени 1: история изменений секретов — в git (кто, когда, какой ключ менял — видно, значение — нет); ревью изменений конфигурации захватывает и секреты (один PR); новый разработчик получает доступ добавлением его публичного ключа в правило (а не пересылкой паролей); приложение читает через sops -d в startup-скрипте или sops-exec. Ключи age хранятся у людей (бэкап ключа команды — в менеджере паролей), а компрометация ключа = ротация затронутых секретов — план пишется заранее.
Ступень 3: Vault и облачные хранилища
Когда потребителей десятки и нужна политика: HashiCorp Vault (или AWS Secrets Manager/GCP Secret Manager — облачный вариант с меньшей эксплуатацией) даёт централизованное хранение, доступ по идентификации (приложение аутентифицируется ролью/K8s service account — не статическим токеном в env), аудит каждого чтения (кто, когда, какой секрет — вопрос «кто слил?» становится ответимым), динамические секреты (Vault генерирует короткоживущие credentials БД под задачу и отзывает их — красть нечего) и ротацию по расписанию. Цена: Vault — серьёзная система (unseal-процедура, HA, бэкапы — стратегия включает и его), и для команды из пяти человек он часто избыточен — облачный Secrets Manager даёт 80% пользы без эксплуатации. Интеграции: приложение читает секрет в рантайме (SDK с кешем и TTL) или через sidecar/injector (K8s), terraform берёт значения data-источниками (state и секреты), CI — из OIDC-федерации облака без долгоживущих ключей.
Секреты в CI/CD: отдельная территория
CI — любимое место утечек: секреты нужны сборке, а конфигурация публична. Правила: GitHub Secrets со scope по environments (прод-секреты — в production-environment с approval — случайный PR их не получит); fork-PR'ы секретов не видят по построению; логи маскируются автоматически, но echo $SECRET | base64 обходит маскировку — в воркфлоу не импровизируют с секретами; долгоживущие токены заменяются OIDC-федерацией (GitHub → облако без хранящегося ключа — механика trusted publishing из гайда по упаковке — тот же принцип); деплой-секреты выдаются минимальные (деплой не должен читать прод-БД). Ротация CI-секретов — часть общей ротации: значение, живущее только в CI, всё равно компрометируется при утечке прав на репозиторий.
Практический сценарий: ротация пароля БД без паники
План, который стоит написать до первого инцидента и проверить учебным прогоном. Шаг 0 — инвентаризация: где живёт пароль (SOPS-файлы окружений, Vault-путь, CI-секрет деплоя, локальные .env разработчиков — последние вне централизованного контроля и есть главная дыра). Шаг 1 — выпуск нового пароля в БД (PostgreSQL: создать/изменить роль с новым паролем, старый остаётся рабочим — окно двойной валидности). Шаг 2 — обновление хранилища: правка SOPS-файла (git-история фиксирует факт, значение зашифровано) или записи Vault. Шаг 3 — раскатка: рестарт сервисов по окружениям (dev → prod) или TTL-перечитывание (Vault-интеграции подхватывают без рестарта). Шаг 4 — отзыв старого пароля после подтверждения, что все потребители на новом. Шаг 5 — запись в журнал ротаций. Учебный прогон занимает час и обнаруживает забытые потребители («а ещё скрипт бэкапа на соседней машине») — именно их поиск в момент настоящей утечки превращает инцидент в катастрофу. Регулярность: критичные секреты — раз в квартал, остальные — по событиям (увольнение, подозрение).
FAQ
SOPS или Vault для старта? SOPS: нулевая инфраструктура, секреты в git с ревью, достаточно для команды до десятка сервисов. Vault — когда нужен аудит/динамика/политики или SOPS-файлы расплодились по репозиториям. Облачный Secrets Manager — середина, если вы уже в облаке.
Можно ли хранить секреты в Kubernetes Secrets как есть? K8s Secrets — base64, не шифрование: без encryption-at-rest в etcd и RBAC это «env, но в кластере». Приемлемо с включённым шифрованием etcd и строгими RBAC; лучше — внешний хранилище через External Secrets Operator (секреты синхронизируются из Vault/SM, в кластере — копии с политикой).
Как быть с локальной разработкой — всем выдавать прод-секреты? Никогда: локальная разработка — на локальных/dev-секретах (свои БД из docker-стека, тестовые ключи внешних API с песочницами); прод-секреты нужны единицам для специфических операций и выдаются точечно (Vault-политика/SOPS-правило).
Что делать при обнаружении секрета в git-истории? Ротация в тот же день (значение скомпрометировано фактом публикации, «репозиторий приватный» не аргумент — доступы меняются), затем очистка истории (git filter-repo/BFG) для гигиены, и постмортем с действием «pre-commit-сканер на все репозитории». Порядок именно такой: сначала новый секрет, потом косметика истории.
Источники: документация Vault, SOPS (github.com/getsops/sops), age-encryption — ссылки в шапке.