ESO Concepts
The External Secrets Operator solves a fundamental Kubernetes problem: how do you securely inject secrets from an external secret manager without storing them in Git or manually creating Kubernetes Secrets?
- SecretStore — Defines how to connect to an external provider (credentials, endpoint, auth method)
- ClusterSecretStore — Cluster-wide version of SecretStore, accessible from any namespace
- ExternalSecret — Declares what secrets to fetch and where to store them (creates a K8s Secret)
- Reconciliation Loop — ESO periodically syncs secrets based on
refreshInterval, keeping K8s Secrets up to date - Supported Providers — Vault, AWS Secrets Manager, AWS SSM Parameter Store, GCP Secret Manager, Azure Key Vault, 1Password, and many more
Install ESO
# Add the External Secrets Helm chart repository
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
# Install ESO into its own namespace
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--set installCRDs=true
# Verify installation
kubectl get pods -n external-secrets
# NAME READY STATUS AGE
# external-secrets-6b7d8c9f4-abc12 1/1 Running 1m
# external-secrets-cert-controller-5f6d7e8a9-def34 1/1 Running 1m
# external-secrets-webhook-7c8d9e0f1-ghi56 1/1 Running 1m
# Verify CRDs
kubectl get crd | grep external-secrets
# clustersecretstores.external-secrets.io
# externalsecrets.external-secrets.io
# secretstores.external-secrets.io
SecretStore CRD
Cluster vs Namespace Scoped
ESO provides two scopes for defining provider connections:
- SecretStore — Namespace-scoped. Only ExternalSecrets in the same namespace can reference it. Best for team-owned provider access.
- ClusterSecretStore — Cluster-wide. Any ExternalSecret in any namespace can reference it. Best for platform teams providing centralized secret access.
Provider Configurations
# AWS Secrets Manager SecretStore (namespace-scoped)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secretsmanager
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
key: secret-access-key
# GCP Secret Manager ClusterSecretStore (cluster-wide)
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: gcp-secretmanager
spec:
provider:
gcpsm:
projectID: my-gcp-project-123
auth:
secretRef:
secretAccessKeySecretRef:
name: gcp-sa-credentials
key: sa-key.json
namespace: external-secrets
# HashiCorp Vault SecretStore (Kubernetes auth)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "production-app"
serviceAccountRef:
name: vault-auth-sa
ExternalSecret CRD
The ExternalSecret declares which secrets to fetch from the provider and how to map them into a Kubernetes Secret.
# Basic ExternalSecret — fetch individual keys
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h # How often to re-sync from provider
secretStoreRef:
name: aws-secretsmanager # Reference to SecretStore
kind: SecretStore
target:
name: app-secrets # Name of the K8s Secret to create
creationPolicy: Owner # ESO owns the Secret (deletes when ExternalSecret is deleted)
data:
- secretKey: db-password # Key in the K8s Secret
remoteRef:
key: production/database # Path in AWS Secrets Manager
property: password # JSON property within the secret
- secretKey: api-key
remoteRef:
key: production/api-keys
property: stripe-key
- secretKey: redis-url
remoteRef:
key: production/redis
property: connection-string
# Verify the ExternalSecret synced successfully
kubectl get externalsecret app-secrets -n production
# NAME STORE REFRESH STATUS READY
# app-secrets aws-secretsmanager 1h SecretSynced True
# The created Kubernetes Secret
kubectl get secret app-secrets -n production -o yaml
# apiVersion: v1
# kind: Secret
# metadata:
# name: app-secrets
# namespace: production
# ownerReferences:
# - apiVersion: external-secrets.io/v1beta1
# kind: ExternalSecret
# name: app-secrets
# data:
# db-password: cGFzc3dvcmQxMjM= (base64 encoded)
# api-key: c2tfdGVzdF8xMjM=
# redis-url: cmVkaXM6Ly8uLi4=
1h. For static API keys, 24h is sufficient. Set to 0 to disable auto-refresh (manual only).
AWS Secrets Manager Example
Complete end-to-end example: create an IAM policy, set up credentials, create SecretStore, and sync secrets.
# 1. Create IAM policy for ESO (least privilege)
cat <<EOF > eso-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:production/*"
}
]
}
EOF
aws iam create-policy \
--policy-name ESO-SecretsReadOnly \
--policy-document file://eso-policy.json
# 2. Create IAM user and attach policy
aws iam create-user --user-name eso-service-account
aws iam attach-user-policy \
--user-name eso-service-account \
--policy-arn arn:aws:iam::123456789012:policy/ESO-SecretsReadOnly
# 3. Create access keys and store in Kubernetes
aws iam create-access-key --user-name eso-service-account
kubectl create secret generic aws-credentials \
--namespace production \
--from-literal=access-key-id=AKIAIOSFODNN7EXAMPLE \
--from-literal=secret-access-key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# 4. Complete SecretStore + ExternalSecret for AWS
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-sm
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
key: secret-access-key
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: production-db-creds
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-sm
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: production/rds-postgres
property: username
- secretKey: password
remoteRef:
key: production/rds-postgres
property: password
- secretKey: host
remoteRef:
key: production/rds-postgres
property: host
Vault Example
Using Vault with Kubernetes authentication — the most secure pattern since no static credentials are stored in the cluster.
# ServiceAccount for Vault authentication
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-sa
namespace: production
---
# SecretStore using Vault Kubernetes auth
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-kv
namespace: production
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "production-reader"
serviceAccountRef:
name: vault-auth-sa
---
# ExternalSecret fetching from Vault KV v2
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: vault-app-secrets
namespace: production
spec:
refreshInterval: 30m
secretStoreRef:
name: vault-kv
kind: SecretStore
target:
name: app-config
creationPolicy: Owner
data:
- secretKey: db-url
remoteRef:
key: production/database
property: connection_url
- secretKey: jwt-signing-key
remoteRef:
key: production/auth
property: jwt_secret
- secretKey: encryption-key
remoteRef:
key: production/encryption
property: aes_256_key
Exercises
fake provider (built-in for testing) and an ExternalSecret that syncs a test value. Verify the Kubernetes Secret is created with the correct data.
refreshInterval to verify the Kubernetes Secret updates automatically.
Key Takeaways & Next Steps
- ESO syncs external secrets into native Kubernetes Secrets — no manual
kubectl create secretneeded - SecretStore defines the provider connection; ExternalSecret defines what to fetch
- ClusterSecretStore provides cluster-wide access; SecretStore is namespace-scoped
refreshIntervalcontrols sync frequency — secrets auto-update when the external value changes- Vault with Kubernetes auth is the most secure pattern (no static credentials)
- ESO supports 20+ providers including AWS, GCP, Azure, Vault, 1Password, and Doppler
Next in the Series
In Part 2: PushSecret & Generators, we'll explore pushing Kubernetes Secrets to external providers, generating dynamic secrets (passwords, UUIDs), syncing secrets across multiple namespaces with ClusterExternalSecret, and templating transformations.