Skip to main content

External Secrets Operator

External Secrets Operator (ESO) watches ExternalSecret resources and materializes ordinary Kubernetes Secret objects from an upstream backend. With HashiCorp Vault as the backend, the same pattern works for any application: you store values in Vault, declare mappings in Git, and mount or env-inject the resulting Secret as usual.

Role in the stack
#

flowchart LR
  subgraph git["Git"]
    CSS[ClusterSecretStore]
    X[ExternalSecret per app]
  end
  subgraph cluster["Cluster"]
    ESO[External Secrets Operator]
    V[Vault KV v2]
    KS[Kubernetes Secret]
    P[Pods]
  end
  CSS --> ESO
  X --> ESO
  ESO -->|token auth| V
  ESO -->|reconcile| KS
  KS --> P
  1. ClusterSecretStore (cluster-scoped): one connection from the cluster to Vault (URL, mount, auth). Every namespace can reference it.
  2. ExternalSecret (namespace-scoped): one per app (or per secret bundle), mapping Vault paths and properties to keys on a target Secret.
  3. ESO reconciles on an interval and when specs change.

Install ESO after Vault is available and unsealable; install before workloads that depend on synced secrets, or ensure those workloads tolerate a short delay until the Secret exists.

Deploying the operator
#

Install from the upstream Helm chart (or your GitOps pipeline). Typical options:

  • installCRDs: true so ExternalSecret and ClusterSecretStore APIs exist.
  • Replicas: one is enough for small clusters; increase for availability if needed.

Version-pin the chart against your Kubernetes version.

ClusterSecretStore: point ESO at Vault
#

Define a single cluster-wide store (name is arbitrary but must match ExternalSecret references). It must match how Vault is deployed:

  • server: Vault API URL reachable from the ESO pods (often in-cluster Service DNS).
  • path and version: KV mount and v2 if you use KV v2.
  • auth: often a token read from a Kubernetes Secret (long-lived token with a tight Vault policy). Other auth methods (Kubernetes auth, AppRole, etc.) are supported by ESO; token auth is a common starting point.

Illustrative structure:

spec:
  provider:
    vault:
      server: http://<vault-service>.<vault-namespace>.svc.cluster.local:8200
      path: secret
      version: v2
      auth:
        tokenSecretRef:
          name: <token-secret-name>
          namespace: <eso-namespace>
          key: token

Adjust server, mount path, and secret reference to your environment.

Bootstrap: Vault token for ESO
#

Create a Kubernetes Secret in the ESO namespace holding the Vault token ESO should use:

kubectl create secret generic <token-secret-name> \
  -n <eso-namespace> \
  --from-literal=token=YOUR_VAULT_TOKEN

The token must be allowed by Vault policy to read every KV path that any ExternalSecret in the cluster will use (or use multiple stores / auth methods per environment if you split access). After the token exists and Vault is unsealed, the ClusterSecretStore should become Ready.

ExternalSecret: per-application pattern
#

For each application (or each distinct secret bundle), add an ExternalSecret in the same namespace as the workload:

FieldPurpose
secretStoreRefkind: ClusterSecretStore, name: <your-store-name>
target.nameKubernetes Secret name to create or update
data[] / dataFromMap Vault remoteRef (path + property) to secretKey on the Secret
refreshIntervalHow often to re-sync from Vault (rotation pickup)
creationPolicyOften Owner so the Secret is owned by the ExternalSecret resource

Path alignment: remoteRef.key is the KV path (for KV v2, the path segment after the mount, e.g. prod/myapp). remoteRef.property is the field name inside that secret. They must match what you wrote with vault kv put.

Workload wiring: reference the generated Secret in your Deployment, StatefulSet, or Helm chart (envFrom, env, volumes) the same way you would for any hand-created secret.

Multiple applications
#

  • One ClusterSecretStore can back many ExternalSecret resources in different namespaces.
  • Each app team only needs RBAC to create ExternalSecret in their namespace; they do not need direct Vault access if operations manage Vault paths and ESO token policy.
  • Tighten Vault policy so the ESO token can only read paths that are actually referenced in Git (or split stores/tokens per environment).

Operations
#

kubectl get clustersecretstore
kubectl get externalsecret -A
kubectl describe externalsecret -n <namespace> <name>

Rotate the Vault token used by ESO
#

  1. Issue a new token (or update policy + token) in Vault.
  2. Update the Kubernetes token Secret ESO uses (same name and key as in tokenSecretRef).
  3. ESO reconciles automatically; restart ESO pods if you need an immediate reconnect.

Common issues
#

SymptomThings to verify
ClusterSecretStore not readyVault reachable and unsealed; token secret present and valid; network from ESO namespace to Vault.
ExternalSecret not syncedremoteRef path and property match KV data; Vault policy allows read; target namespace exists.
Generated Secret empty or stalerefreshInterval; verify with vault kv get on the same path and property.

Related#

  • HashiCorp Vault — KV layout, policies, unseal, adding paths for new apps.

References
#