Managing Amazon EKS at Fleet Scale · Part 11 of 18

EKS Secrets and Supply Chain at Fleet Scale: KMS, ESO, CSI, and the GitOps Question

This closes the security triad. Identity decides who you are; policy-as-code decides what you may run; secrets and supply chain decide what you may read and what you ship. Here is how to encrypt Secrets at rest without ever deleting the KMS key that bricks the cluster, how to choose between External Secrets Operator, the Secrets Store CSI Driver, and Sealed Secrets/SOPS, and how to make all of it fit a GitOps fleet.

TL;DR

  • Two problems, not one. A secret has to be safe at rest in the cluster, and it has to be delivered to a pod. KMS handles the first; ESO, CSI, or Sealed/SOPS handle the second. Don’t conflate them.
  • Encryption at rest: use a customer-managed KMS key on clusters older than 1.28. EKS 1.28 and later ship default envelope encryption for API data at rest; before that, add a customer-managed key yourself — and understand that this is irreversible and that deleting the key bricks the cluster.
  • RBAC is still the primary control. Encryption at rest protects the etcd blobs; it does nothing about who can kubectl get secret. Least-privilege RBAC does that job, and encryption is defence in depth on top.
  • Delivery is a three-way choice. ESO syncs an external manager into native Secrets (source of truth stays in the manager); CSI mounts secrets into a tmpfs volume so they never land in etcd; Sealed Secrets/SOPS keep an encrypted copy in Git for a pure GitOps model.
  • The GitOps tension has a real answer. Argo and Flux want everything in Git — but not plaintext secrets. Sealed/SOPS give you encrypted-in-Git; ESO/CSI keep the truth in a real secrets manager and leave only a reference in Git.
  • Guard the supply chain with the same enforcement layer. Scan images, enforce the Pod Security Standards baseline (that is the policy layer from Part 6), and prefer signed images with provenance — applied fleet-wide as admission policy, not per team.
  • Version and default-behaviour facts here are current as of 2026-06-30; re-verify the KMS default-encryption boundary at publish.

Do this

  • Add a customer-managed KMS key on any cluster older than 1.28, and keep that key off every deletion path.
  • Pick a delivery pattern by where the secret must live — a manager, memory-only, or encrypted-in-Git.
  • Keep RBAC least-privilege as the primary control; treat encryption as defence in depth.
  • Enforce image scanning and the PSS baseline as policy, fanned out through GitOps.

Don’t do this

  • Don’t schedule the KMS key for deletion — there is no undo, and the cluster can no longer read its own Secrets.
  • Don’t commit plaintext secrets to Git to satisfy GitOps — encrypt them or keep a reference.
  • Don’t assume encryption at rest replaces RBAC — it doesn’t touch who can read a Secret.
  • Don’t hand-apply a delivery pattern per cluster — make it part of the golden path.

Why secrets are a different problem at fleet scale — three things you have to get right

At one cluster, secrets are a chore you can do by hand. At hundreds, they become a design decision, because every shortcut you take multiplies by the size of the fleet. This is the third leg of the security triad the series has been building: identity (Part 10) decides who you are, policy-as-code (Part 6) decides what you may run, and this article decides what you may read and what you ship.

There are two distinct problems hiding under the word “secret,” and most confusion comes from mixing them up.

KMS answers the first. External Secrets Operator, the Secrets Store CSI Driver, and Sealed Secrets/SOPS answer the second, each with a different tradeoff. On top of both sits the supply chain: the images your pods run and the provenance you can prove. Get all three consistent across the fleet and you have a real secrets posture; get any one of them wrong on a few clusters and you have a quiet liability you will find during an audit. The rest of this article takes them in that order — at rest, then delivery, then the GitOps question, then the supply chain.

Locking secrets at rest — envelope encryption and the KMS key you must never delete

Start with the good news: on recent clusters this is largely handled for you. EKS version 1.28 and later apply default envelope encryption to API data at rest, Secrets included, so the bytes in etcd are encrypted without you configuring anything.1 On any cluster older than 1.28, that is not automatic, and you should add a customer-managed KMS key yourself. This is current as of 2026-06-30 — re-verify the version boundary at publish, because default-behaviour facts like this roll forward.

Envelope encryption means the Secret is not encrypted directly by the KMS key. Instead a local data key encrypts each Secret, and the KMS key encrypts — wraps — that data key (Figure 1). It is efficient, and it means the KMS key never has to touch the plaintext. But it also creates a hard dependency: without the KMS key, the wrapped data key cannot be unwrapped, and the Secret cannot be read.

Figure 1 · Envelope encryption and the key-deletion cliff One data key per Secret, wrapped by one KMS key you control encryption chain delete = bricked etcd API data at rest Secret stored as ciphertext D Data key (DEK) one per Secret encrypts the Secret itself stored wrapped K Customer-managed KMS key (CMK) wraps the data key never sees plaintext encrypts wraps Adding a CMK needs kms:DescribeKey + kms:CreateGrant. Default envelope encryption ships on EKS 1.28+. ! THE KEY-DELETION CLIFF — IRREVERSIBLE Delete or disable the CMK and every data key becomes unwrappable — the API server can no longer read its own Secrets. The cluster is bricked. There is no recovery. Keep this key off every delete path.
The key is load-bearing infrastructure, not a rotating credential: it wraps the data keys that decrypt every Secret, so deleting it is indistinguishable from destroying the cluster’s data — and unlike most mistakes, this one has no undo.

Adding a customer-managed key to an older cluster is a small IAM exercise with an outsized failure mode. The cluster’s role needs kms:DescribeKey and kms:CreateGrant on the key, the change is a one-way door — you can enable KMS encryption but not turn it back off — and the key becomes part of the cluster’s critical path.1 The headline caveat is worth stating bluntly: if you ever schedule that key for deletion, you have bricked the cluster. AWS enforces a waiting period before a KMS key is destroyed, which is your one chance to cancel; treat any deletion request against a cluster’s key as an incident.

One more thing that trips people up: encryption at rest is not access control. It protects the bytes if someone gets at etcd or a backup; it does nothing about who can run kubectl get secret and read the value in plaintext. RBAC is the primary control for that — least-privilege roles that keep Secret access down to the workloads and people who need it. Envelope encryption is defence in depth layered underneath, not a substitute.2

How a secret reaches a pod — three delivery patterns, three blast radii

Now the runtime question: a native Kubernetes Secret is fine for a single cluster, but on a fleet you usually keep the source of truth somewhere central and deliver from there. Three patterns dominate, and they differ mostly in where the secret ends up living — which is the same as asking what an attacker gets if they compromise that place (Figure 2).

Figure 2 · Three ways a secret reaches a pod Same goal, different resting place — and different blast radius E External Secrets Op. syncs into a native Secret Reads AWS Secrets Manager Writes a native K8s Secret Truth stays in the manager Touches etcd: yes C Secrets Store CSI tmpfs mount, no etcd Mounts into the pod Lives in tmpfs memory Never written to etcd Touches etcd: no S Sealed Secrets / SOPS encrypted-in-Git Ciphertext committed to Git Controller decrypts in-cluster Source of truth: Git Lives in Git (encrypted) All three keep plaintext out of your repo and off disk — they differ in where the secret ultimately lives.
Choose by resting place: ESO leaves the value in a native Secret (encrypted at rest by KMS), CSI keeps it in pod memory and out of etcd entirely, and Sealed/SOPS keep an encrypted copy in Git — three different answers to “what does an attacker get if they reach this store?”

External Secrets Operator (ESO) keeps the source of truth in a real secrets manager. A controller watches an ExternalSecret resource, reads the value from AWS Secrets Manager (or Parameter Store, Vault, and others), and writes a normal Kubernetes Secret into the cluster.3 Your workloads consume a plain Secret and don’t know the difference, and rotation in the manager syncs down automatically. The tradeoff is that the value does land in etcd — which is fine, because that is exactly what KMS envelope encryption from the previous section protects. This is the default I reach for when a central manager is already the system of record.

The Secrets Store CSI Driver keeps the secret out of etcd altogether. It mounts secrets into the pod as a volume backed by tmpfs — memory, not disk — so the value is present only for the pods that mount it and is never written to the cluster datastore.4 That is the pattern to reach for when a compliance rule or threat model says the secret must never be persisted in etcd. The cost is a little more moving machinery (the driver plus a provider) and the fact that a mounted file is a slightly different consumption model than an environment variable or a native Secret.

Sealed Secrets and SOPS keep an encrypted copy in Git. Sealed Secrets encrypts a Secret against a controller’s public key so only the in-cluster controller can decrypt it;5 SOPS encrypts the values in a YAML/JSON file (commonly against a KMS key) so the file is safe to commit and gets decrypted at deploy time.6 Both let you put the whole desired state, secrets included, in the repo without ever committing plaintext — which is precisely the property a pure GitOps workflow wants, and the subject of the next section.

DimensionESOSecrets Store CSISealed Secrets / SOPS
Source of truthExternal managerExternal managerGit (encrypted)
Lands in etcd?Yes (KMS-encrypted)No (tmpfs)Yes, after decrypt
GitOps-friendlyReference in GitReference in GitSecret itself in Git
RotationSyncs from managerRe-mount from managerRe-seal / re-encrypt
Reach for it whenA manager is the system of recordSecret must never hit etcdGitOps needs secrets-in-Git

The GitOps question — secrets in Git without plaintext

Here is the tension the fleet’s golden path forces. The whole point of GitOps (Part 4) is that Git is the single source of truth: Argo CD or Flux reconcile every cluster to what the repo says, so a change is a reviewed commit and a rollback is a revert.7 That model wants everything in Git — and a secret is part of a workload’s desired state. But a plaintext secret in Git is a plaintext secret in every clone, every fork, and every backup of that repo, forever. So you cannot just commit it, and you cannot leave it out without breaking the “Git is the truth” promise.

There are two honest ways to resolve it, and they map onto the patterns you just saw.

The deciding question is where you want the system of record for secret values to be. If it is Git, reach for Sealed/SOPS. If it is a real secrets manager, reach for ESO or CSI and keep only the reference in Git. Both are legitimate; what is not legitimate is a plaintext Secret manifest committed to satisfy the tooling. That is the one move this section exists to rule out.

Guarding the supply chain — scan the image, enforce the baseline, verify provenance

Secrets protect what a workload can read. The supply chain protects what a workload is — the image it runs and whether you can trust where it came from. At fleet scale this has to be enforcement, not guidance, and it rides the same admission-policy layer as everything else in Part 6. Three controls carry most of the weight.

The connecting idea is that none of this should live in a per-team pipeline that a team can skip. Scanning thresholds, the PSS baseline, and signature requirements are admission policy, expressed once and fanned out to every cluster through GitOps — the same audit-then-enforce discipline Part 6 uses for any new rule. That is what makes a supply-chain guarantee actually hold across hundreds of clusters instead of on the ones that remembered to opt in.

Which pattern to reach for — the decision, and the one key you must never delete

Put the pieces together and the whole article collapses into a short set of decisions (Figure 3). Encryption at rest and delivery are separate choices; make both deliberately.

Figure 3 · The reach-for decision Pick delivery by requirement — then hold the two constants below E Reach for ESO when a central secrets manager is the source of truth — sync it down. native Secret, KMS at rest C Reach for CSI when secrets must never touch etcd — mount them in memory. tmpfs volume, no datastore S Reach for Sealed/SOPS when GitOps demands secrets-in-Git — commit the ciphertext safely. Git is the source of truth ALWAYS — THE TWO CONSTANTS Use a customer-managed KMS key on clusters older than 1.28 — and never delete it. Enforce image scanning and the Pod Security Standards baseline as fleet-wide admission policy, not per team.
Delivery flexes with the requirement; the constants don’t: whichever pattern you pick, the KMS key stays undeletable and the supply-chain guardrails stay enforced fleet-wide.

The delivery choice comes down to one question — where should the secret live? If the answer is a central manager you already trust, use ESO and let it sync into native Secrets. If the answer is “nowhere on disk, ever,” use the CSI driver and keep it in tmpfs. If the answer is “in Git with everything else,” use Sealed Secrets or SOPS so the copy in the repo is ciphertext. There is no single winner; there is a fit for each requirement, and mature fleets often run more than one.

What does not flex are the two constants. On any cluster older than 1.28, add a customer-managed KMS key so Secrets are encrypted at rest — and keep least-privilege RBAC as the primary control, because encryption protects the bytes, not the read path. And put the supply-chain guardrails — image scanning, the PSS baseline, signed provenance — on the same fleet-wide admission gate as your policies, so they hold on every cluster and not just the diligent ones.

If you carry one sentence out of this article, make it this: choose a delivery pattern that fits your GitOps model, and never, under any circumstances, put a cluster’s KMS key on a deletion path — that mistake has no undo. Do both, and secrets stop being the quiet liability in a fleet and become just another part of the golden path you can hand to hundreds of teams.

Sources

Grounding for the claims above. Default-behaviour and version facts (the KMS default-encryption boundary in particular) are current as of 2026-06-30 and should be re-verified at publish.

  1. AWS — Enable KMS envelope encryption for EKS Secrets: default envelope encryption on 1.28+, adding a customer-managed key (kms:DescribeKey + kms:CreateGrant), the one-way nature of enabling it, and the consequence of deleting the key. docs.aws.amazon.com/…/enable-kms.html
  2. AWS — EKS Best Practices (security / secrets management): encryption at rest as defence in depth with RBAC as the primary control, and the trade space between external-manager and in-cluster secret patterns. docs.aws.amazon.com/eks/latest/best-practices
  3. External Secrets Operator — syncs values from an external manager (AWS Secrets Manager, Parameter Store, Vault, and others) into native Kubernetes Secrets via the ExternalSecret resource. external-secrets.io
  4. Kubernetes SIG — Secrets Store CSI Driver: mounts external secrets into pods as a tmpfs volume so values are not written to etcd. secrets-store-csi-driver.sigs.k8s.io
  5. Sealed Secrets (Bitnami Labs) — encrypts a Secret against a controller public key so the ciphertext is safe to commit to Git; only the in-cluster controller can decrypt it. github.com/bitnami-labs/sealed-secrets
  6. SOPS (getsops) — encrypts the values in YAML/JSON files (commonly against a KMS key) so the file can live in Git and is decrypted at deploy time. github.com/getsops/sops
  7. Argo CD — GitOps reconciliation makes Git the source of truth for the fleet (the model secrets must fit; cross-reference Part 4). argo-cd.readthedocs.io
  8. AWS — Amazon ECR image scanning: scan-on-push and continuous re-scanning so newly disclosed CVEs surface against images already stored. docs.aws.amazon.com/…/image-scanning.html
  9. Kubernetes — Pod Security Standards: the baseline tier (no privileged containers, no host namespaces, no privilege escalation) enforced fleet-wide as admission policy (cross-reference Part 6). kubernetes.io/…/pod-security-standards