Production-grade platform engineering handbook — Kubernetes, Terraform, Flux CD, GitHub Actions, AWS, and more.
77
97%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Structured guidance for secrets strategy, External Secrets Operator, Sealed Secrets, rotation, and Kubernetes-side secrets audit.
/platform-skills:secrets design # ESO vs Sealed Secrets decision; choose a strategy for the cluster
/platform-skills:secrets eso # ExternalSecret/SecretStore scaffold; debug sync errors
/platform-skills:secrets sealed # seal, rotate, backup master key; troubleshoot decryption failures
/platform-skills:secrets rotate # rotation runbook — update provider value, force sync, verify pods reload
/platform-skills:secrets audit # find bad SA tokens, unhealthy ExternalSecrets, missing secretsWhen invoked with no arguments, ask before proceeding:
Q1 — Mode?
What do you need?
1. design — choose ESO vs Sealed Secrets for a cluster or workload
2. eso — scaffold ExternalSecret/SecretStore, or debug a sync error
3. sealed — seal a new secret, rotate an existing one, or back up the master key
4. rotate — end-to-end rotation runbook for a live secret
5. audit — find service account token secrets, unhealthy ExternalSecrets, plain secrets in Git
Enter 1–5 or mode name:Q2 — Context (after mode selected):
What is the backend — AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, or no cloud provider?Paste the ExternalSecret or SecretStore YAML, or describe the sync error.New secret or rotating an existing one? Which namespace?Which secret is rotating — database password, API key, TLS cert? Which backend?Cluster name and namespace scope. Any specific concern — Git scan, SA tokens, ESO health?Triggers: which pattern, ESO vs Sealed Secrets, choose secrets strategy, what should I use
Read references/secrets.md before responding.
| Pattern | Best for | Tradeoff |
|---|---|---|
| External Secrets Operator (ESO) | Cloud-native backends: AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault | Runtime dependency on provider; requires workload identity or static credentials |
| Sealed Secrets | Air-gapped or GitOps-first teams; no external runtime dependency | Rotation requires re-sealing and a Git commit; master key backup is critical |
Pick ESO when:
Pick Sealed Secrets when:
Handoffs:
/platform-skills:trivy (--scanners secret)/platform-skills:azure identity/platform-skills:awsTriggers: ExternalSecret, SecretStore, ClusterSecretStore, sync error, ESO, external secrets operator, SecretSyncError
Read references/secrets.md → External Secrets Operator section before responding.
Before generating commands, confirm:
- ESO namespace — common values:
external-secrets,platform-system,kube-system. Discover with:kubectl get pods -A | grep external-secrets- ESO deploy name — common values:
external-secrets,external-secrets-controller. Discover with:kubectl get deploy -n <eso-namespace>- ESO service account name — the SA annotated with IRSA or Workload Identity. Common:
external-secrets-sa, but teams name it differently.Substitute these into all namespace flags,
kubectl logs, andserviceAccountReffields below.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: app-team
spec:
refreshInterval: 1h
secretStoreRef:
name: my-secret-store
kind: SecretStore
target:
name: database-credentials
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: prod/app-team/db
property: passwordAWS Secrets Manager (IRSA):
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: my-secret-store
namespace: app-team
spec:
provider:
aws:
service: SecretsManager
region: eu-north-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-saRequired IAM (least privilege):
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:<region>:<account>:secret:prod/app-team/*"
}Azure Key Vault (Workload Identity):
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: my-secret-store
namespace: app-team
spec:
provider:
azurekv:
authType: WorkloadIdentity
vaultUrl: "https://my-keyvault.vault.azure.net"
serviceAccountRef:
name: external-secrets-saThe managed identity needs Key Vault Secrets User on the Key Vault.
HashiCorp Vault (Kubernetes auth):
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: my-secret-store
namespace: app-team
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "app-team-role"
serviceAccountRef:
name: external-secrets-sa# Sync status and last sync time
kubectl get externalsecret -n app-team database-credentials
# Full status including error message
kubectl describe externalsecret -n app-team database-credentials
# Operator logs for this secret
kubectl logs -n external-secrets deploy/external-secrets \
| grep -i "database-credentials"
# SecretStore health
kubectl get secretstore -n app-team my-secret-store
kubectl describe secretstore -n app-team my-secret-store| Symptom | Cause | Fix |
|---|---|---|
SecretSyncError: unauthorized / AccessDenied | Identity missing the required permission | Check provider access policy; verify IRSA/Workload Identity annotation |
SecretSyncError: not found | Secret path wrong or deleted in provider | Verify remoteRef.key matches the exact name in the backend |
| Secret exists but data is stale | refreshInterval too long | Reduce interval or force sync: kubectl annotate externalsecret database-credentials force-sync=$(date +%s) -n app-team |
SecretStore shows NotReady | Provider unreachable or auth misconfigured | Check network connectivity to the provider; re-validate identity setup |
| Pod can't read the Secret | Pod references wrong Secret name or key | Verify envFrom/secretKeyRef in pod spec matches target.name and secretKey |
Triggers: SealedSecret, kubeseal, seal, sealed secrets, no key could decrypt, master key, backup
Read references/secrets.md → Sealed Secrets section before responding.
Before writing any kubeseal or kubectl commands, collect the following. Different organisations deploy the controller with different names and namespaces.
Q1 — Controller namespace:
Where is the sealed-secrets controller running?
(common: sealed-secrets, kube-system, platform-system — check with: kubectl get pods -A | grep sealed)
Q2 — Controller Service name:
What is the controller Service name? (used by kubeseal --controller-name — this is the Service, not the Deployment)
(common: sealed-secrets, sealed-secrets-controller — check with: kubectl get svc -n <namespace> | grep sealed)
Q3 — Secret namespace:
Which namespace will the unsealed Secret live in?
Q4 — Secret name:
What is the name of the secret?
Q5 — Action:
1. Seal a new secret
2. Rotate an existing sealed secret
3. Back up the master key
4. Restore the master key
5. Troubleshoot a decryption failureUse the answers to substitute <controller-namespace>, <controller-name>, and <secret-namespace> in all commands below. Never output hardcoded namespace or deployment names — always use the values the user provided.
Note:
<controller-name>means the Service name when used withkubeseal --controller-name, and the Deployment name when used withkubectl rollout restart deploy/<controller-name>. If they differ in your cluster, substitute accordingly.
# Fetch the cluster public key — run once per cluster or after key rotation
kubeseal --fetch-cert \
--controller-name=<controller-name> \
--controller-namespace=<controller-namespace> \
> pub-cert.pem
# Create a plain Secret and immediately seal it — never commit the plain Secret
kubectl create secret generic <secret-name> \
--namespace=<secret-namespace> \
--from-literal=KEY=value \
--dry-run=client -o yaml \
| kubeseal --cert pub-cert.pem --format yaml \
> <secret-name>-sealed.yaml
git add <secret-name>-sealed.yaml
git commit -m "chore: seal <secret-name> for <secret-namespace>"kubectl create secret generic <secret-name> \
--namespace=<secret-namespace> \
--from-literal=KEY=newvalue \
--dry-run=client -o yaml \
| kubeseal --cert pub-cert.pem --format yaml \
> <secret-name>-sealed.yaml
git add <secret-name>-sealed.yaml
git commit -m "chore: rotate <secret-name>"After GitOps applies the updated SealedSecret:
kubectl rollout restart deployment/<name> -n <secret-namespace>kubectl get secret -n <controller-namespace> \
-l sealedsecrets.bitnami.com/sealed-secrets-key \
-o yaml > sealed-secrets-master-key-backup.yamlStore this file outside the cluster (password manager, vault, encrypted object store). Without it, sealed secrets cannot be decrypted after cluster recreation.
kubectl apply -f sealed-secrets-master-key-backup.yaml
kubectl rollout restart deploy/<controller-name> -n <controller-namespace># Confirm the controller is running
kubectl get pods -n <controller-namespace>
# Inspect SealedSecret status
kubectl describe sealedsecret <secret-name> -n <secret-namespace>
# Controller logs for decryption errors
kubectl logs -n <controller-namespace> deploy/<controller-name> | tail -50| Symptom | Cause | Fix |
|---|---|---|
no key could decrypt secret | Sealed with an old key after rotation | Re-seal with current cluster public key using --fetch-cert |
| Secret not created after GitOps sync | metadata.namespace mismatch | Ensure namespace in the sealed YAML matches where it will be applied |
cannot fetch key: secret not found | Controller lost its own key Secret | Restore master key from backup before applying any sealed secrets |
Triggers: rotate, rotation runbook, change password, new token, expired credential, update secret
Apply this runbook for any secret rotation:
AWS Secrets Manager:
aws secretsmanager put-secret-value \
--secret-id prod/app-team/db \
--secret-string '{"password":"new-password"}' \
--profile <aws-profile>Azure Key Vault:
az keyvault secret set \
--vault-name my-keyvault \
--name db-password \
--value "new-password"Sealed Secrets: Re-seal with the new value (see sealed mode).
kubectl annotate externalsecret database-credentials \
force-sync=$(date +%s) \
-n app-team# Check the last sync time
kubectl get externalsecret database-credentials -n app-team
# Confirm the Secret data changed (check hash, not the value)
kubectl get secret database-credentials -n app-team \
-o jsonpath='{.data.DB_PASSWORD}' | base64 -d | sha256sum# Pods using env vars require a restart to pick up the new Secret value
kubectl rollout restart deployment/<name> -n app-team
kubectl rollout status deployment/<name> -n app-teamPods using mounted volumes reload automatically within the kubelet sync period (default 60s) — no restart needed unless the app reads the value at startup only.
# Application-specific validation — confirm connectivity to the backend
# Do NOT print secret values; test the connection functionally instead
kubectl exec -n app-team deploy/<name> -- \
sh -c 'wget -qO- http://localhost:8080/healthz || curl -sf http://localhost:8080/healthz'
# For a database: test the connection, not the credential value
kubectl exec -n app-team deploy/<name> -- \
sh -c 'pg_isready -h $DB_HOST -p $DB_PORT' 2>/dev/null || echo "adjust command for your backend"Triggers: audit, find plain secrets, SA token, kubernetes.io/service-account-token, secrets health, hygiene
This mode covers Kubernetes-side audit only. For filesystem and Git history scanning for leaked secrets → /platform-skills:trivy (trivy fs --scanners secret or trivy repo --scanners secret).
# Find all kubernetes.io/service-account-token type secrets
kubectl get secrets -A \
--field-selector type=kubernetes.io/service-account-token \
-o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.metadata.annotations.kubernetes\.io/service-account\.name'Legacy manually-created SA token secrets do not auto-rotate. Migrate workloads to projected tokens (automatic since Kubernetes 1.24).
# All ExternalSecrets and their status
kubectl get externalsecrets -A \
-o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,STATUS:.status.conditions[0].reason,LAST:.status.refreshTime'
# Find any not in Ready state
kubectl get externalsecrets -A \
-o json \
| jq -r '.items[] | select(.status.conditions[]?.type=="Ready" and .status.conditions[]?.status!="True") | "\(.metadata.namespace)/\(.metadata.name): \(.status.conditions[]?.message)"'# Trivy handles this — hand off explicitly
# trivy repo --scanners secret <repo-url>
# or for a local clone:
# trivy fs --scanners secret .For a Git history and filesystem scan → /platform-skills:trivy
kubectl get secretstores -A
kubectl get clustersecretstores
# Describe any not-ready stores
kubectl get secretstores -A -o json \
| jq -r '.items[] | select(.status.conditions[]?.status!="True") | "\(.metadata.namespace)/\(.metadata.name): \(.status.conditions[]?.message)"'Secret manifests committed to Git — verify with git log --all -- '*.yaml' | xargs grep -l 'kind: Secret'defaultkubernetes.io/service-account-token type secrets for application API authClusterSecretStore used only for platform-wide secrets; namespace-scoped SecretStore for everything elseResource: "*" or all secrets in the vaultSecret manifests to Git — use ESO, Sealed Secrets, or Vault. Rotate any secret that was ever in Git history.secretsmanager:GetSecretValue with Resource: "*" — scope to the specific path or secret name. Wildcard grants access to all secrets in the account.default service account for ESO — creates implicit access. Create a dedicated external-secrets-sa per namespace.--fetch-cert — if the controller key rotated since the last seal, the new SealedSecret cannot be decrypted. Always fetch the current cert before sealing.Full guidance: references/secrets.md
For filesystem and Git history secret scanning: /platform-skills:trivy
For Azure Key Vault identity setup: /platform-skills:azure identity
For AWS IRSA setup: /platform-skills:aws
.claude-plugin
.github
assets
commands
docs
examples
agent-self-improve
argocd
awesome-docs
aws
cloudfront
functions
lambda-edge
functions
azure
compliance
conventional-commits
datadog
llm-observability
demo
documentation
dora
dynatrace
fluxcd
github-actions
composite-actions
configure-cloud
db-migrate
docker-build-push
k8s-deploy
notify-slack
pr-comment
release-tag
security-scan
setup-env
setup-terraform
terraform-plan
helm
web-service
templates
karpenter
kubernetes
kyverno
mcp
observability
openshift
pr-review
ownership
runtime-security
setup-agents
terraform
references
skills
platform-skills
tests
website