How-To Guide

Configure External Secret Stores

This guide shows you how to configure External Secrets Operator (ESO) to sync secrets from external sources into Kubernetes.

Prerequisites

  • External Secrets Operator installed (included in infrastructure tier)

  • kubectl access to the cluster

  • Credentials for your external secret backend

Create a SecretStore

A SecretStore defines how to access an external secret backend.

Example: Kubernetes Secret Store (Cross-Cluster Sync)

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: kubernetes-backend
  namespace: my-app
spec:
  provider:
    kubernetes:
      # For remote cluster access
      remoteNamespace: source-namespace
      server:
        url: "https://other-cluster.example.com"
        caProvider:
          type: ConfigMap
          name: kube-root-ca.crt
          key: ca.crt
      auth:
        token:
          bearerToken:
            name: kubernetes-token
            key: token

Example: Webhook Provider (Generic Backend)

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: webhook-backend
  namespace: my-app
spec:
  provider:
    webhook:
      url: "https://secret-api.example.com/secrets/{{ .remoteRef.key }}"
      result:
        jsonPath: "$.data"
      headers:
        Authorization:
          secretRef:
            name: webhook-credentials
            key: token

Create an ExternalSecret

An ExternalSecret defines which secrets to fetch and how to create Kubernetes secrets.

Basic Example

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: my-app-secrets
  namespace: my-app
spec:
  # Refresh interval
  refreshInterval: 1h

  # Reference to SecretStore
  secretStoreRef:
    name: kubernetes-backend
    kind: SecretStore

  # Target Kubernetes secret
  target:
    name: my-app-secrets
    creationPolicy: Owner

  # Data to fetch
  data:
    - secretKey: database-password
      remoteRef:
        key: db-credentials
        property: password

    - secretKey: api-key
      remoteRef:
        key: api-credentials
        property: key

Template Example (Generate Secrets)

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: connection-string
  namespace: my-app
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: kubernetes-backend
    kind: SecretStore

  target:
    name: postgres-connection
    template:
      type: Opaque
      data:
        # Template a connection string from multiple secrets
        connection-string: |
          postgresql://{{ .username }}:{{ .password }}@{{ .host }}:5432/{{ .database }}

  dataFrom:
    - extract:
        key: postgres-credentials

Create a ClusterSecretStore

Use ClusterSecretStore for cluster-wide secret stores (accessible from all namespaces).

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: global-secrets
spec:
  provider:
    kubernetes:
      remoteNamespace: global-secrets
      auth:
        serviceAccount:
          name: external-secrets

Verify Secret Synchronization

Check ExternalSecret status:

# Check if ExternalSecret is syncing
kubectl get externalsecret my-app-secrets -n my-app

# View detailed status
kubectl describe externalsecret my-app-secrets -n my-app

# Check generated Kubernetes secret
kubectl get secret my-app-secrets -n my-app -o yaml

Expected output for healthy ExternalSecret:

NAME              STORE                REFRESH INTERVAL   STATUS         READY
my-app-secrets    kubernetes-backend   1h                 SecretSynced   True

Troubleshooting

ExternalSecret shows “SecretSyncedError”

Check SecretStore configuration:

kubectl describe secretstore kubernetes-backend -n my-app

Common issues:

  • Invalid credentials in authentication secret

  • Network connectivity to external backend

  • Incorrect remote key path

Secret not updating after external change

Check refresh interval:

kubectl get externalsecret my-app-secrets -n my-app -o jsonpath='{.spec.refreshInterval}'

Force immediate refresh by deleting and recreating ExternalSecret (secret will be regenerated).

Security Best Practices

  1. Namespace Isolation: Use SecretStore (namespaced) instead of ClusterSecretStore unless cluster-wide access is required

  2. Least Privilege: Grant minimal permissions to ESO service account for accessing external backends

  3. Refresh Intervals: Balance between freshness and API rate limits (recommended: 1h-24h)

  4. Secret Rotation: Use short-lived tokens when possible, leverage ESO’s refresh to propagate rotations

  5. Audit Logs: Monitor ExternalSecret events for unauthorized access attempts

Common Patterns

Pattern: Environment-Specific Secrets

# dev-secrets (SecretStore)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: dev-secrets
  namespace: my-app
spec:
  provider:
    kubernetes:
      remoteNamespace: dev-environment

---
# prod-secrets (SecretStore)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: prod-secrets
  namespace: my-app
spec:
  provider:
    kubernetes:
      remoteNamespace: prod-environment

Reference by environment in deployment:

spec:
  secretStoreRef:
    name: {{ .Values.environment }}-secrets

Pattern: Multi-Source Secret Aggregation

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: aggregated-config
spec:
  refreshInterval: 30m
  secretStoreRef:
    name: kubernetes-backend

  target:
    name: app-config

  # Fetch from multiple sources
  dataFrom:
    - extract:
        key: common-config
    - extract:
        key: app-specific-config

Next Steps