Secret Management

External Secrets Operator

Render ExternalSecret objects from image-source env.store entries — and apply ESO manifests from git

External Secrets Operator

The External Secrets Operator (ESO) syncs secrets from external providers into Kubernetes Secrets. Wardn integrates at two levels: env.store entries on image sources render ExternalSecret objects for ESO to resolve, and ExternalSecret/SecretStore manifests committed to git are applied like any other resource.

How It Works

┌────────────────┐   env.store     ┌──────────────┐   renders   ┌───────────────┐
│ App spec       │────────────────▶│   Operator   │────────────▶│ ExternalSecret│
│ (Image source) │  {id,path,key}  │   renderer   │             │  objects      │
└────────────────┘                 └──────────────┘             └───────┬───────┘
                                                                         │
┌────────────────┐                                                       ▼
│ ESO operator   │ ◀─────────────── resolves ──────────────        K8s Secret
│ (in-cluster)   │                                                      │
└────────────────┘                                                      ▼
                                                                  Deployment envFrom
  1. You write env.store on the image workload — {id: <eso store>, path: <remote key>, key: <data key>}
  2. The operator renders an ExternalSecret alongside the Deployment/Service
  3. ESO resolves the remote path from its provider and writes the K8s Secret
  4. Your pod references that secret via envFrom/secretKeyRef as usual

The eso store

An eso-type secret store marks where env.store entries should point:

curl -X POST http://localhost:8080/api/v1/secret-stores -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"name":"prod-eso","type":"eso","provider":"eso"}'

The eso store carries no credentials — ESO authenticates to providers via its own SecretStore/ClusterSecretStore manifests in the cluster. Wardn's Test on an eso store returns untested by design.

App config — image source

spec:
  source:
    type: Image
    image:
      repository: ghcr.io/acme/api
      workload:
        env:
          - name: DB_PASSWORD
            store:
              id: sts_eso…               # the eso store's id
              path: prod/my-app/db        # remote provider key
              key: password               # data key → env value

Rendered output includes an ExternalSecret pointing remoteRef.key at prod/my-app/db and secretKey at password; the Deployment gets DB_PASSWORD via secretKeyRef.

Git-source apps

Commit ESO manifests directly — they apply like any resource:

# external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore
  target:
    name: db-credentials
  data:
    - secretKey: password
      remoteRef:
        key: prod/my-app/db-password

Health caveat — 🚧

ExternalSecret objects get the default verdict — Healthy if they exist, Missing if not. Wardn does not read ESO's Ready/SecretSyncedError conditions today, so a broken ESO sync won't degrade app health unless the object itself is absent. Track kind-aware ESO health on the Roadmap.

Install ESO

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace

ESO's provider list (AWS Secrets Manager, GCP Secret Manager, Vault, Azure KV, Kubernetes, etc.) is ESO's own — Wardn doesn't gate it.

Troubleshooting

ExternalSecret exists but Secret doesn't

kubectl describe externalsecret db-credentials -n <ns>
kubectl logs -n external-secrets deployment/external-secrets --tail=50

Common causes: provider IAM/RBAC, wrong remoteRef.key, misconfigured SecretStore. All ESO-side — Wardn applied the object fine.

env.store entry doesn't render

  • The id must be an eso-type store — vault/sops stores take the vault-fetch path instead (or 400 on create of an invalid ref)
  • Check status.resources[] on the App — the ExternalSecret appears there once rendered

On this page