External Secrets Operator with Vault

Khimananda Oli 9 min read Virtualization
External Secrets Operator with Vault

By Khimananda Oli | Last reviewed: August 2026

Managing static credentials in Kubernetes manifests is a security liability that fails audits and complicates rotation. Integrating External Secrets Operator with Vault solves this by dynamically syncing secrets from HashiCorp Vault into native Kubernetes Secret objects without exposing long-lived tokens in your Git repository. This approach decouples secret storage from application deployment, enabling automated rotation and least-privilege access patterns required for SOC 2 and ISO 27001 compliance. If you are already managing sensitive data, understanding Kubernetes secrets management done right provides the necessary foundation before implementing an external backend.

HashiCorp VaultKV v2 / PKI EngineAuth: K8s SAESO ControllerClusterSecretStoreExternalSecret CRDReconcile LoopKubernetes ClusterNative K8s SecretApp Pod (Mounted)API ReadCreate/Update
External Secrets Operator architecture: ESO authenticates via ServiceAccount, fetches from Vault, and creates native Kubernetes Secrets for pod consumption.

How do you configure External Secrets Operator with Vault authentication?

The most common failure point when deploying External Secrets Operator with Vault is misconfigured authentication. Never use static AppRole credentials or root tokens in production; these violate least-privilege principles and create audit findings. Instead, use Kubernetes ServiceAccount authentication, which binds Vault access directly to pod identity and namespace boundaries.

Enable Kubernetes Auth Method in Vault

First, enable the Kubernetes auth method and configure it to trust your cluster's API server. This allows Vault to validate ServiceAccount tokens presented by ESO.

# Enable K8s auth method
vault auth enable kubernetes

# Configure the connection to your K8s API
vault write auth/kubernetes/config \
    kubernetes_host="https://kubernetes.default.svc:443" \
    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

# Create a policy for reading secrets
vault policy write eso-read-policy -<<EOF
path "secret/data/apps/*" {
  capabilities = ["read"]
}
path "pki/issue/web-certs" {
  capabilities = ["create", "update"]
}
EOF

# Bind the policy to a specific ServiceAccount and namespace
vault write auth/kubernetes/role/eso-role \
    bound_service_account_names=external-secrets \
    bound_service_account_namespaces=external-secrets-system \
    policies=eso-read-policy \
    ttl=1h

This configuration restricts Vault access to only the external-secrets ServiceAccount in the external-secrets-system namespace. For multi-cluster setups, you must repeat this binding for each cluster’s unique CA certificate and service account. Teams managing complex RBAC should review Kubernetes RBAC: Secure Your Cluster to ensure namespace isolation aligns with Vault policies.

Create the ClusterSecretStore Resource

The ClusterSecretStore defines how ESO connects to Vault. Unlike a namespaced SecretStore, this resource is available cluster-wide, reducing duplication across environments.

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.internal.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "eso-role"
          serviceAccountRef:
            name: "external-secrets"
            namespace: "external-secrets-system"

A common mistake here is omitting the namespace field in serviceAccountRef. Even though ClusterSecretStore is cluster-scoped, ESO needs explicit permission to read the ServiceAccount token from its home namespace. Without this, reconciliation fails silently with authentication errors in the controller logs.

How does secret synchronization and templating work in practice?

Once authenticated, External Secrets Operator with Vault translates external data structures into Kubernetes-native formats. Real-world applications rarely consume raw JSON blobs; they expect specific key names, environment variable formats, or PEM-encoded certificates. ESO’s templating engine handles this transformation declaratively.

Basic Secret Sync vs. Advanced Templating

For simple key-value pairs, a direct mapping suffices. However, most production workloads require restructuring Vault output to match application expectations.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-database-creds
  namespace: production-apps
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        DB_HOST: "{{ .db_host }}"
        DB_USER: "{{ .username }}"
        DB_PASS: "{{ .password }}"
        # Transform JSON metadata into a config string
        APP_CONFIG: |
          {"pool_size": {{ .pool_size }}, "timeout": {{ .timeout }}}
  data:
    - secretKey: db_host
      remoteRef:
        key: apps/production/database
        property: host
    - secretKey: username
      remoteRef:
        key: apps/production/database
        property: username
    - secretKey: password
      remoteRef:
        key: apps/production/database
        property: password
    - secretKey: pool_size
      remoteRef:
        key: apps/production/database
        property: metadata.pool_size
    - secretKey: timeout
      remoteRef:
        key: apps/production/database
        property: metadata.timeout

The refreshInterval controls how often ESO polls Vault for changes. Setting this too low increases API load; setting it too high delays credential rotation. In my experience auditing fintech systems, a 1-hour interval balances operational overhead with security requirements for most database credentials. For highly sensitive short-lived tokens, reduce this to 5–15 minutes and ensure your application supports graceful credential reloading.

K8s APIESO ControllerVault APIApp PodWatch ES CRDLogin (SA Token)Client TokenRead secret/data/...Secret PayloadTemplate & DiffCreate/Update SecretVolume Mount UpdateNext Refresh
ESO reconciliation sequence: watch CRD, authenticate to Vault, fetch secrets, template output, update Kubernetes Secret, trigger pod volume refresh.

What are the trade-offs between ESO, CSI Driver, and direct Vault Agent?

Choosing the right integration pattern depends on your operational constraints, compliance requirements, and application architecture. Each approach has distinct trade-offs that affect security posture, complexity, and developer experience.

CriteriaExternal Secrets OperatorVault CSI DriverVault Agent Injector
Secret FormatNative K8s SecretsEphemeral Volume FilesAnnotated Sidecar Files
Rotation HandlingPolling + Secret UpdateVolume Remount (TTL)Sidecar Re-render
App CompatibilityAny (env vars/volumes)File-based onlyFile-based only
Audit TrailK8s Events + Vault AuditVault Audit OnlyVault Audit Only
ComplexityModerate (CRDs + Store)Low (DaemonSet)High (Annotations + Init)
SOC 2 EvidenceStrong (Declarative State)Moderate (Runtime Only)Weak (Implicit Config)

For teams pursuing SOC 2 or ISO 27001 certification, External Secrets Operator with Vault offers superior auditability because every secret sync is represented as a declarative Kubernetes object with status conditions and events. Auditors can inspect ExternalSecret resources to verify that secrets originate from approved Vault paths rather than ad-hoc manual entries. The CSI driver excels for high-security scenarios where secrets must never touch etcd, but it requires applications to read files rather than environment variables. Vault Agent remains useful for legacy migrations but introduces significant annotation complexity and sidecar resource overhead.

How do you handle dynamic secrets and certificate rotation?

Static credentials are a liability. Vault’s dynamic secrets engines generate short-lived credentials on demand, and ESO can consume them natively. This is particularly valuable for databases, cloud providers, and PKI certificates where long-lived credentials increase blast radius during breaches.

Dynamic Database Credentials

Configure Vault’s database secrets engine to generate PostgreSQL credentials with 1-hour TTLs. ESO will automatically request new credentials before expiration and update the Kubernetes Secret.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: dynamic-db-creds
spec:
  refreshInterval: 30m
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: postgres-dynamic-creds
  data:
    - secretKey: username
      remoteRef:
        key: database/creds/app-role
        property: username
    - secretKey: password
      remoteRef:
        key: database/creds/app-role
        property: password

Set refreshInterval to less than half the Vault lease TTL to prevent credential expiration during reconciliation delays. Applications must handle connection resets gracefully when credentials rotate. For PostgreSQL specifically, connection poolers like PgBouncer can abstract credential changes from application code, reducing restart requirements during rotation cycles.

PKI Certificate Automation

For TLS certificates, ESO integrates with Vault’s PKI engine to issue and renew certificates automatically. This eliminates manual cert-manager configurations when Vault is already your certificate authority.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: web-tls-cert
spec:
  refreshInterval: 24h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: web-tls
    template:
      type: kubernetes.io/tls
      data:
        tls.crt: "{{ .certificate }}"
        tls.key: "{{ .private_key }}"
        ca.crt: "{{ .issuing_ca }}"
  data:
    - secretKey: certificate
      remoteRef:
        key: pki/issue/web-certs
        property: certificate
    - secretKey: private_key
      remoteRef:
        key: pki/issue/web-certs
        property: private_key
    - secretKey: issuing_ca
      remoteRef:
        key: pki/issue/web-certs
        property: issuing_ca

This pattern centralizes certificate lifecycle management within Vault while maintaining compatibility with Ingress controllers and service meshes that expect standard TLS secret types. Teams operating observability stacks should correlate certificate expiration metrics with their monitoring platform; see Prometheus Metrics Monitoring Fundamentals for exporting ESO sync status and certificate TTL gauges.

Static Secrets (Anti-Pattern)Manual CreationStored in Git / etcdManual Rotation (Years)High Blast Radius • Audit FailuresDynamic Secrets (ESO + Vault)ESO Requests CredsVault Generates (1h TTL)Auto-Rotate via ESOLeast Privilege • Audit ReadyCompliance Impact MatrixSOC 2 CC6.1Logical Access SecurityISO 27001 A.9.4System AuthenticationPCI DSS Req 8.3Secure AuthenticationDynamic Secrets + ESO = Automated Evidence CollectionEliminates Manual Credential Tracking Spreadsheets
Static versus dynamic secrets comparison: dynamic approach reduces blast radius and automates compliance evidence for SOC 2, ISO 27001, and PCI DSS frameworks.

Securing External Secrets Operator with Vault for Production Compliance

Deploying External Secrets Operator with Vault in production requires hardening beyond default installations. Enable Vault audit logging to capture every secret access request with timestamps, identities, and outcomes—this is non-negotiable for compliance audits. Configure ESO controller replicas with anti-affinity rules to prevent single points of failure during node maintenance. Implement NetworkPolicies restricting ESO pod egress to only Vault endpoints and Kubernetes API servers, blocking lateral movement if the controller is compromised.

Monitor ESO reconciliation failures through Prometheus metrics (externalsecret_status_condition) and alert on sustained sync failures before applications encounter stale credentials. Regularly rotate the Kubernetes ServiceAccount tokens used for Vault authentication and test failover procedures quarterly. For teams building comprehensive observability around secret operations, integrating structured logging practices from Structured Logging Best Practices ensures ESO events correlate with application logs during incident investigation.

If your team needs assistance architecting compliant secret management workflows or validating existing ESO deployments against security frameworks, reach out for a consultation. Proper secrets infrastructure prevents breaches and accelerates audit cycles—invest the engineering time now to avoid remediation costs later.

Frequently Asked Questions

It is a Kubernetes controller that synchronizes secrets from HashiCorp Vault into native Kubernetes Secret objects, enabling applications to consume credentials without direct Vault integration or agent sidecars.

Install via Helm using the external-secrets chart version 0.14 or newer. Configure the Vault provider in a ClusterSecretStore resource with your Vault address, authentication method, and namespace path before creating ExternalSecret manifests.

Yes. Specify the X-Vault-Namespace header in the ClusterSecretStore or SecretStore spec under provider.vault.namespace to target secrets within specific Vault enterprise namespaces correctly during synchronization cycles.

Yes. Configure the Kubernetes auth method by setting auth.kubernetes in the store spec, providing the mount path, role name, and service account reference to enable tokenless secret retrieval from Vault.

Default refresh interval is one hour. Override this per ExternalSecret using spec.refreshInterval to balance API load against credential rotation latency based on your security requirements and Vault rate limits.

Yes. The operator is open source under Apache 2.0 license. Costs only arise from Vault Enterprise licensing, managed Vault services, or cluster compute resources running the controller pods themselves.

No single tool is universally better. Vault Agent Injector suits sidecar patterns, while CSI drivers handle volume mounts. External Secrets Operator excels at native Kubernetes Secret integration for standard application deployments requiring simple environment variable injection.

Check controller logs with kubectl logs deployment/external-secrets-controller. Verify ClusterSecretStore status conditions, validate Vault connectivity and permissions, and confirm the ExternalSecret spec references correct paths and keys in your Vault backend.

No. Secrets exist only as Kubernetes Secret objects in etcd. The operator fetches fresh values from Vault during each refresh cycle but maintains no persistent local cache outside the Kubernetes API server storage layer.

No. The operator only reads secrets. Use Vault's dynamic secrets engines or external automation like Terraform to generate and rotate credentials, then let External Secrets Operator synchronize updated values into Kubernetes on next refresh.

Equally secure for storage since both use Kubernetes Secrets. Advantage lies in centralized lifecycle management through Vault rather than manual secret creation, reducing human error and enabling audit trails for all credential access patterns.

Yes. Set provider.vault.version to v2 in your store configuration. The operator automatically handles metadata wrapping and versioned key paths required by KV v2 secret engines during read operations.

Yes. Create separate SecretStore or ClusterSecretStore resources for each Vault instance or mount path. Reference the appropriate store name in each ExternalSecret to pull credentials from different backends simultaneously.

Grant read policy on specific secret paths only. Avoid wildcard policies. Use dedicated Vault roles with minimal scope matching your ExternalSecret definitions to enforce least privilege access for the operator service account.

Review release notes for breaking changes first. Run helm upgrade with --wait flag, verify controller pod health, check existing ExternalSecret sync statuses, and test secret retrieval in staging before applying to production clusters.