Skip to content

External Secrets Operator Reference: Vault, AWS SM, GCP, Azure & Secret Sync Patterns

External Secrets Operator (ESO) syncs secrets from external stores (Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) into Kubernetes Secrets automatically — the recommended way to avoid storing sensitive values in K8s or Git.

1. Why ESO vs Sealed Secrets vs Vault Agent

Choosing your K8s secret management strategy
Approach How it works Best for
External Secrets Operator K8s CRD syncs from external store → K8s Secret Centralised secret store already exists (Vault, AWS SM)
Sealed Secrets Encrypt secrets in Git, decrypt in cluster GitOps where secrets live in Git (with Flux/ArgoCD)
Vault Agent Injector Sidecar injects secrets as files in pods Per-pod secret delivery, short-lived credentials
CSI Secrets Store Mount secrets as volumes via CSI driver Compliance requirements for file-mounted secrets
# Install ESO:
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets   --namespace external-secrets --create-namespace

kubectl get pods -n external-secrets
kubectl get crds | grep external-secrets.io   # SecretStore, ExternalSecret, ClusterSecretStore

2. HashiCorp Vault Backend

Sync secrets from Vault KV into Kubernetes Secrets
# 1. Create a SecretStore (namespaced — for per-namespace Vault access):
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: production
spec:
  provider:
    vault:
      server: https://vault.vault.svc.cluster.local:8200
      path: secret                       # KV v2 mount path
      version: v2                        # KV version (v1 or v2)
      auth:
        kubernetes:
          mountPath: kubernetes          # Vault auth method path
          role: production-apps          # Vault Kubernetes role
          serviceAccountRef:
            name: external-secrets-sa   # K8s SA with Vault auth permission

# 2. Create ExternalSecret — defines what to sync:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: my-app-secrets
  namespace: production
spec:
  refreshInterval: 1h                    # re-sync every hour
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: my-app-secrets                 # name of K8s Secret to create
    creationPolicy: Owner                # ESO manages lifecycle (deletes when ExternalSecret deleted)
    template:
      type: Opaque
  data:
    - secretKey: db-password             # key in K8s Secret
      remoteRef:
        key: myapp/production            # Vault path (under secret/data/)
        property: db_password            # field in Vault secret
    - secretKey: api-key
      remoteRef:
        key: myapp/production
        property: api_key

# Check sync status:
kubectl get externalsecret -n production
kubectl describe externalsecret my-app-secrets -n production
# Ready=True = secret successfully synced
# Ready=False → look at Conditions for auth/path errors

3. AWS Secrets Manager & Parameter Store

Sync from AWS SM and SSM Parameter Store
# ClusterSecretStore (cluster-wide — reusable across namespaces):
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets
spec:
  provider:
    aws:
      service: SecretsManager             # or ParameterStore
      region: us-east-1
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets        # IRSA: SA annotated with IAM role ARN
            namespace: external-secrets
            # SA annotation:
            # eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/external-secrets-role

# ExternalSecret pulling from AWS SM:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: rds-credentials
  namespace: production
spec:
  refreshInterval: 30m
  secretStoreRef:
    name: aws-secrets
    kind: ClusterSecretStore
  target:
    name: rds-credentials
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: production/rds/credentials   # AWS SM secret name
        property: password                 # JSON field in the secret
    - secretKey: DB_USERNAME
      remoteRef:
        key: production/rds/credentials
        property: username

# Extract entire secret as multiple K8s keys (dataFrom):
spec:
  dataFrom:
    - extract:
        key: production/myapp              # all key/value pairs become K8s Secret keys

# Parameter Store:
spec:
  provider:
    aws:
      service: ParameterStore
      region: us-east-1
# ExternalSecret remoteRef.key = /production/myapp/db_password (SSM path)

4. GCP & Azure Backends

GCP Secret Manager and Azure Key Vault integration
# GCP Secret Manager:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: gcp-secrets
  namespace: production
spec:
  provider:
    gcpsm:
      projectID: my-gcp-project
      auth:
        workloadIdentity:
          clusterLocation: us-central1
          clusterName: my-cluster
          serviceAccountRef:
            name: external-secrets-sa     # K8s SA with Workload Identity binding

# ExternalSecret:
data:
  - secretKey: api-key
    remoteRef:
      key: my-app-api-key                 # GCP Secret Manager secret name
      version: latest                     # or specific version number

# Azure Key Vault:
spec:
  provider:
    azurekv:
      tenantId: "xxxx-xxxx-xxxx"
      vaultUrl: https://my-vault.vault.azure.net
      authType: WorkloadIdentity           # or ServicePrincipal
      serviceAccountRef:
        name: external-secrets-sa

# ExternalSecret (Key Vault secret):
data:
  - secretKey: db-password
    remoteRef:
      key: production-db-password         # Key Vault secret name (no slashes)

5. Push Secrets & Advanced Patterns

Push secrets to external store, secret templates, and rotation
# PushSecret: write a K8s Secret TO an external store (opposite direction):
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: push-to-vault
spec:
  secretStoreRefs:
    - name: vault-backend
      kind: SecretStore
  selector:
    secret:
      name: my-k8s-secret               # push this K8s Secret to Vault
  data:
    - match:
        secretKey: api-key              # key in K8s Secret
        remoteRef:
          remoteKey: myapp/api          # destination path in Vault
          property: api_key

# Secret template (transform before creating K8s Secret):
target:
  template:
    data:
      DATABASE_URL: "postgresql://{{ .db_user }}:{{ .db_pass }}@db:5432/mydb"
      # Constructs a connection URL from individual Vault fields

# Rotation trigger (force re-sync when upstream secret changes):
# ESO polls at refreshInterval — for immediate rotation, delete ExternalSecret and re-apply
# Or annotate: kubectl annotate externalsecret my-app-secrets #   force-sync=$(date +%s) -n production

# Multi-source (merge from multiple stores):
spec:
  dataFrom:
    - extract: {key: shared/common}       # shared secrets
    - extract: {key: production/myapp}    # app-specific secrets (overwrites shared if collision)

Track External Secrets Operator and security tool releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.

Related: HashiCorp Vault Reference | Kubernetes RBAC Reference | OPA & Gatekeeper Reference | Kubernetes EOL Tracker

🔍 Free tool: K8s YAML Security Linter — check your ExternalSecret and workload manifests for K8s security misconfigurations.

Founded

2023 in London, UK

Contact

hello@releaserun.com