AI agents: walk the user through each decision below before generating manifests. All decisions apply across providers and platforms.
Ask these three questions in order. Each answer narrows the setup.
1. "How many teams share this cluster?"
2. "Which platform runs your cluster, and which secret provider do you use?"
docs/provider/<provider>.md for provider-specific configuration. Exception: AWS uses docs/provider/aws-access.md for auth and separate docs per service (aws-secrets-manager.md, aws-parameter-store.md).3. "What path or prefix do your secrets follow in the provider?" (e.g., prod/team-a/*, secret/data/myapp/*)
docs/provider/ include IAM/policy examples.Store scope controls the blast radius of a compromised namespace.
Each namespace gets its own SecretStore with dedicated credentials scoped to that namespace's secrets.
Choose when:
What you get:
Tradeoff: one credential set per namespace to manage.
One cluster-wide store. All permitted namespaces share it.
Choose when:
What you get:
conditions.namespaceSelector restricts which namespaces can reference the store, but labels can be misconfiguredAlways add namespace conditions when using ClusterSecretStore.
See docs/guides/multi-tenancy.md and docs/guides/security-best-practices.md.
See docs/introduction/getting-started.md for installation instructions. When applying CRDs manually (not via Helm), use kubectl apply --server-side — ESO CRDs exceed the 256KB annotation limit.
Generate these resources once the user answers all three questions. Adapt the provider, region, and paths to match their answers.
# 1. ServiceAccount per namespace (auth method dependent)
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-secrets
namespace: <namespace>
annotations:
# EKS IRSA example — replace with provider-appropriate annotation
eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/eso-<namespace>
---
# 2. SecretStore per namespace
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: <namespace>
spec:
provider:
aws: # replace with user's provider
service: SecretsManager
region: <region>
auth:
jwt:
serviceAccountRef:
name: external-secrets
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
conditions:
- namespaceSelector:
matchLabels:
external-secrets: "enabled"
provider:
aws: # replace with user's provider
service: SecretsManager
region: <region>
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets # recommended for ClusterSecretStore
Key difference: ClusterSecretStore should specify namespace in serviceAccountRef and secretRef. Both fields are optional in the API schema, but omitting them in a cluster-scoped store can cause ambiguous resolution.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: <secret-name>
namespace: <namespace>
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore # or ClusterSecretStore
target:
name: <k8s-secret-name>
data:
- secretKey: <local-key>
remoteRef:
key: <provider-path>
property: <json-field> # omit if the secret is a plain string
The controller polls the external provider on the refreshInterval cadence and writes the result to the target K8s Secret. refreshInterval: 1h polls once per hour. refreshInterval: 0 disables polling; the controller syncs the secret once at creation and never revisits it.
# Check store health
kubectl get secretstore -A # or clustersecretstore
# Sync status should show SecretSynced
kubectl get externalsecret -A
# Inspect errors on a specific ExternalSecret
kubectl describe externalsecret <name> -n <namespace>
# Confirm the K8s Secret exists
kubectl get secret <name> -n <namespace>
# Controller logs show provider errors and sync failures
kubectl logs -n external-secrets deploy/external-secrets -f
# Events surface auth and permission errors
kubectl get events -n <namespace> --field-selector involvedObject.name=<externalsecret-name>
# Force a re-sync by annotating the ExternalSecret
kubectl annotate externalsecret <name> -n <namespace> force-sync=$(date +%s) --overwrite
namespace in refs. ClusterSecretStore should specify namespace in serviceAccountRef and secretRef. Both are +optional in the API schema, but omitting them in a cluster-scoped store can cause ambiguous resolution.docs/provider/.Secret path mismatch. Provider key names are exact. A trailing slash, wrong case, or missing path segment returns "not found."
IRSA OIDC trust policy mismatch. The IAM role's trust policy must reference the correct OIDC provider URL for the EKS cluster. A mismatch causes silent auth failure with "unable to create session." Verify with aws iam get-role --role-name <role> | jq '.Role.AssumeRolePolicyDocument'.
PushSecret needs extra permissions. Syncing secrets back to AWS requires CreateSecret, PutSecretValue, TagResource, and DeleteSecret in addition to read permissions. See docs/provider/aws-secrets-manager.md.
From docs/guides/security-best-practices.md:
conditions.namespaceSelector to ClusterSecretStoresprocessClusterStore: false, etc.)scopedRBAC: true and scopedNamespace for high-security namespacesremoteRef.key patterns