# AI Agent Guide: External Secrets Operator Setup AI agents: walk the user through each decision below before generating manifests. All decisions apply across providers and platforms. ## Decision Flow Ask these three questions in order. Each answer narrows the setup. **1. "How many teams share this cluster?"** - Multiple teams or tenants → SecretStore per namespace (Option A below) - Single team, or dev/staging cluster → ClusterSecretStore (Option B below) **2. "Which platform runs your cluster, and which secret provider do you use?"** - The platform determines the auth method. The provider determines which doc to reference. - Look up `docs/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`). - Default rule: pick platform-native short-lived credentials over static secrets. **3. "What path or prefix do your secrets follow in the provider?" (e.g., `prod/team-a/*`, `secret/data/myapp/*`)** - Use the answer to scope credentials. Grant access to those paths only. - Scope by: path prefix, tag-based policies (if supported), or separate credentials per namespace. - Provider docs under `docs/provider/` include IAM/policy examples. ## SecretStore vs ClusterSecretStore Store scope controls the blast radius of a compromised namespace. ### Option A: SecretStore per namespace Each namespace gets its own `SecretStore` with dedicated credentials scoped to that namespace's secrets. **Choose when:** - Multiple teams share the cluster - Secrets must stay isolated between namespaces **What you get:** - A compromised namespace can only reach its own secrets - Provider-level policies scope per namespace (IAM roles, Vault policies, GCP IAM bindings) - Enforcement is structural, not label-based **Tradeoff:** one credential set per namespace to manage. ### Option B: ClusterSecretStore One cluster-wide store. All permitted namespaces share it. **Choose when:** - Single team runs the cluster - Dev/staging environment - Setup speed matters more than isolation **What you get:** - One credential covers all secret access - `conditions.namespaceSelector` restricts which namespaces can reference the store, but labels can be misconfigured - No per-namespace IAM separation **Always add namespace conditions when using ClusterSecretStore.** See `docs/guides/multi-tenancy.md` and `docs/guides/security-best-practices.md`. ## Installation 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. ## What to Generate Generate these resources once the user answers all three questions. Adapt the provider, region, and paths to match their answers. ### If Option A (SecretStore per namespace): ```yaml # 1. ServiceAccount per namespace (auth method dependent) apiVersion: v1 kind: ServiceAccount metadata: name: external-secrets namespace: annotations: # EKS IRSA example — replace with provider-appropriate annotation eks.amazonaws.com/role-arn: arn:aws:iam:::role/eso- --- # 2. SecretStore per namespace apiVersion: external-secrets.io/v1 kind: SecretStore metadata: name: aws-secrets-manager namespace: spec: provider: aws: # replace with user's provider service: SecretsManager region: auth: jwt: serviceAccountRef: name: external-secrets ``` ### If Option B (ClusterSecretStore): ```yaml 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: 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. ### ExternalSecret (same for both options): ```yaml apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: namespace: spec: refreshInterval: 1h secretStoreRef: name: aws-secrets-manager kind: SecretStore # or ClusterSecretStore target: name: data: - secretKey: remoteRef: key: property: # omit if the secret is a plain string ``` ## How the Sync Loop Works 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. ## Verification ```bash # 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 -n # Confirm the K8s Secret exists kubectl get secret -n ``` ### Troubleshooting ```bash # 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 --field-selector involvedObject.name= # Force a re-sync by annotating the ExternalSecret kubectl annotate externalsecret -n force-sync=$(date +%s) --overwrite ``` ## Common Pitfalls 1. **Missing `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. 2. **Auth misconfiguration.** Most sync failures trace back to auth. Check the provider-specific doc under `docs/provider/`. 3. **Secret path mismatch.** Provider key names are exact. A trailing slash, wrong case, or missing path segment returns "not found." ### AWS-specific pitfalls 4. **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 | jq '.Role.AssumeRolePolicyDocument'`. 5. **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`. ## Security Hardening Checklist From `docs/guides/security-best-practices.md`: - [ ] Scope provider credentials to specific secret paths or prefixes - [ ] Add `conditions.namespaceSelector` to ClusterSecretStores - [ ] Disable unused CRDs and reconcilers in Helm values (`processClusterStore: false`, etc.) - [ ] Add NetworkPolicies restricting ESO egress to provider endpoints and kube-apiserver - [ ] Grant SecretStore/ClusterSecretStore creation only to cluster admins - [ ] Set `scopedRBAC: true` and `scopedNamespace` for high-security namespaces - [ ] Use Kyverno or OPA to deny unused providers and restrict `remoteRef.key` patterns - [ ] Disable unused providers to shrink the data exfiltration surface