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 добавляет его автоматически).