External Secrets Operator + HashiCorp Vault: аутентификация через Kubernetes
Published: 2026-03-19
SealedSecrets хорошо работают для большинства кластерных учётных данных. Но некоторые секреты — например, общие учётные данные сервисов — должны жить в Vault и автоматически синхронизироваться в Kubernetes. External Secrets Operator (ESO) соединяет эти два мира.
Компоненты
- HashiCorp Vault — работает на кластере
infra, развёртывается через кастомный Helm-чартcharts/vault/. - External Secrets Operator — развёртывается через
base/external-secrets/helmrelease.yamlна infra. - ClusterSecretStore — cluster-scoped ресурс ESO, определяющий подключение к Vault.
- ExternalSecret — определяет, какой путь Vault синхронизировать в какой Kubernetes Secret.
HelmRelease ESO
yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: external-secrets
spec:
chart:
spec:
chart: external-secrets
version: "0.14.4"
sourceRef:
kind: HelmRepository
name: external-secrets
namespace: flux-system
values:
installCRDs: true
replicaCount: 1
serviceMonitor:
enabled: true
interval: 60s
labels:
release: kube-prom-stack
Настройка Kubernetes auth в Vault
bashvault auth enable -path=kubernetes-infra kubernetes
vault write auth/kubernetes-infra/config \
kubernetes_host="https://172.16.57.10:6443" \
kubernetes_ca_cert=@/tmp/infra-ca.crt \
token_reviewer_jwt="$(kubectl create token -n external-secrets external-secrets)"
vault policy write infra-default - <<EOF
path "secret/data/infra/*" { capabilities = ["read"] }
EOF
vault write auth/kubernetes-infra/role/external-secrets \
bound_service_account_names=external-secrets \
bound_service_account_namespaces=external-secrets \
policies=infra-default \
ttl=1h
Через token_reviewer_jwt Vault обращается к Kubernetes TokenReview API и валидирует токены.
ClusterSecretStore
yamlapiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-infra
spec:
provider:
vault:
server: "http://vault.vault.svc.cluster.local:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes-infra"
role: "external-secrets"
serviceAccountRef:
name: "external-secrets"
namespace: "external-secrets"
ExternalSecret
yamlapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: reportportal-creds
namespace: infra
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-infra
kind: ClusterSecretStore
target:
name: reportportal-creds
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: secret/infra/reportportal
property: password
ESO синхронизирует значение из Vault каждый час. creationPolicy: Owner — при удалении ExternalSecret удаляется и Secret.
Синхронизация нескольких полей из одного пути
yamlspec:
dataFrom:
- extract:
key: secret/infra/postgres
dataFrom.extract извлекает все поля из пути Vault как отдельные ключи в Secret. Удобно, когда путь содержит username, password, host, port.
Sealed Secrets vs External Secrets
| Случай | Sealed Secrets | External Secrets |
|---|---|---|
| kubeconfig-файлы | ✓ | — |
| Учётные данные Docker registry | ✓ | — |
| TLS-сертификаты | ✓ | — |
| Общие учётные данные (пароли БД) | — | ✓ |
| Секреты, ротируемые командой безопасности | — | ✓ |
| Секреты с аудит-логом | — | ✓ (через Vault audit) |
Проверка статуса синхронизации
bashkubectl get externalsecret -A
kubectl describe externalsecret reportportal-creds -n infra
# Status.Conditions - Ready=True
SecretSyncedError появляется, если Vault запечатан (sealed) или путь не существует.
Диагностика
ESO не может аутентифицироваться в Vault:
bashkubectl logs -n external-secrets deploy/external-secrets
Частая причина: token_reviewer_jwt устарел или SA был пересоздан после настройки auth.
Путь не найден:
bashvault kv get secret/infra/reportportal
Путь в remoteRef.key должен совпадать точно (без префикса data/ для KV v2 — ESO добавляет его автоматически).