Команды выносят секреты из кластера во внешний store — и первым делом создают новый секрет: токен для доступа к этому стору. Vault-токен в ConfigMap, AWS-ключ в переменной оператора — «секрет для доступа к секретам» возвращает задачу на исходную. По Verizon DBIR 2025, больше 60 % облачных инцидентов начинаются с утёкших credentials. Рабочая связка 2026 года закрывает петлю целиком: External Secrets Operator синхронизирует секреты из внешнего стора, а workload identity убирает последний статический ключ — тот, которым оператор аутентифицируется в сторе.
Почему native Secret — не source of truth
Kubernetes Secret в etcd хранится как base64 — это кодирование, а не шифрование. Пока encryption at rest не включён явно (--encryption-provider-config, в проде — KMS-провайдер), прямое чтение etcd или его бэкапа обходит весь RBAC. Но даже зашифрованный native Secret плох как первоисточник: у него нет версионирования, аудита и автоматической ротации, он привязан к кластеру — multi-cluster превращается в копипасту, — а класть его в Git при GitOps нельзя вовсе: plaintext в истории коммитов живёт вечно.
Вывод: «истинный» секрет живёт во внешнем сторе. Default — Vault или его BSL-free форк OpenBao (API-совместимый, под Linux Foundation); альтернативы — AWS Secrets Manager, GCP Secret Manager, Azure Key Vault. У cloud-managed сторов есть ценовая ловушка: $0.40 за секрет в месяц превращают 500 секретов примерно в $2 400 в год ещё до платы за API-вызовы — self-hosted Vault at scale может оказаться дешевле.
ESO: один CRD-фасад на 50+ бэкендов
External Secrets Operator (проект CNCF) — универсальный «клей» между стором и кластером. ClusterSecretStore описывает подключение к бэкенду, ExternalSecret — декларативный запрос «синхронизируй этот путь в обычный K8s Secret». Приложение ничего не знает про Vault: оно читает секрет через привычный valueFrom: secretKeyRef. Смена бэкенда — правка SecretStore, а не приложений.
Поверх базовой пары — ClusterExternalSecret (fan-out одного секрета сразу в несколько namespace для multi-tenant платформ) и PushSecret (обратное направление: из кластера в стор). В Git при этом лежит только ExternalSecret — ссылка на путь, без значения.
Workload identity: нет учёток для доступа к учёткам
Остался bootstrap-секрет: чем сам оператор аутентифицируется в сторе? Ответ — ничем статическим. Механизм во всех облаках одинаков: pod получает projected ServiceAccount token, привязанный audience к OIDC-эндпоинту облака, и IAM валидирует его напрямую. На EKS это IRSA — аннотация eks.amazonaws.com/role-arn на ServiceAccount, а IAM trust policy привязана к конкретной паре namespace и serviceaccount — или более современный EKS Pod Identity. В GKE — Workload Identity, в AKS — Azure Workload Identity: тот же паттерн с другой аннотацией.
Для Vault ту же роль играет kubernetes auth method: Vault-роль жёстко привязана к имени ServiceAccount и namespace (bound_service_account_names + bound_service_account_namespaces), токен живёт час. Компрометация пода из соседнего namespace ничего не даёт. Единственное «но» — федеративная роль обязана быть least-privilege: выдать ей AdministratorAccess «для демо» — значит собрать все риски статического ключа обратно, только без самого ключа.
Ротация — процесс, а не событие
refreshInterval в ExternalSecret задаёт частоту опроса стора: 1–5 минут — быстрая ротация ценой нагрузки на бэкенд, 1–24 часа — щадяще, но секрет устаревает. Смена значения в сторе доезжает до K8s Secret автоматически, однако pod читает секрет при старте — нужен рестарт. Это закрывает stakater/reloader: аннотация reloader.stakater.com/auto на Deployment, и рестарт происходит сам при изменении Secret.
Верхняя ступень — dynamic secrets в Vault: креды базы данных генерируются на каждый запрос со своим TTL и авто-отзывом. Ротация как проблема исчезает — у каждого экземпляра приложения свои короткоживущие креды.
Чеклист и грабли
Vault KV v2: в remoteRef.key обязателен сегмент /data/ — например secret/data/prod/app/db. Без него ESO покажет SecretSynced: True, а Secret будет пустым: самые неприятные ошибки те, что выглядят как успех.
creationPolicy: Owner в target — иначе конфликт ownership при ручном kubectl edit. Долгий refreshInterval для короткоживущих кредов обессмысливает ротацию. Один shared Secret на двадцать приложений делает RBAC грубым: отозвать доступ одному сервису невозможно.
Env vars против файлов: переменные окружения видны в /proc, crash-dump и kubectl describe; тренд 2026 — file-mounted секреты через CSI driver для самого чувствительного. Encryption at rest в etcd — базовый слой, который ESO не заменяет: синхронизированный секрет всё равно лежит в etcd.
Итог: стор отвечает «где живут секреты», ESO — «как они доезжают до пода», workload identity — «как всё это работает без единого долгоживущего ключа». Убирать слои снизу нельзя: без первых двух workload identity нечего защищать. А как секрет не попадает в репозиторий по дороге — от secret-scan до admission-политик.