Articles / Secrets Management in Kubernetes: etcd Encryption and External Stores
Kubernetes · Secrets

Secrets Management in Kubernetes: etcd Encryption and External Stores

The single most common misconception in Kubernetes security is that a Secret object keeps a secret. It does not, not on its own. By default a Secret is stored in etcd exactly as you supplied it, merely base64-encoded — which is an encoding, not encryption, reversible by anyone in seconds. Securing secrets in Kubernetes means understanding where they actually live, who can reach them, and when to stop keeping them in the cluster at all.

When you create a Secret, it lands in etcd, the cluster's key-value datastore. Out of the box that value is plaintext-equivalent: base64 decode and read. So the real questions are about access to etcd and to the Secrets API — anyone who can read the etcd data files, snapshot a backup, or is granted get on secrets through RBAC can read every credential those Secrets hold.

First fix: encrypt secrets at rest

Secret objectbase64, not encryptedetcd datastoreRead pathsetcd files, backups, RBACgetAttacker readscleartext credentialsBase64 decodes toplaintext; backupstravel
A Secret is base64 in etcd — anyone who reaches etcd or a backup reads it.

Kubernetes supports encryption at rest for etcd via an EncryptionConfiguration on the API server. Enable it and Secrets are encrypted before being written to etcd, so an attacker with the etcd data or a backup snapshot gets ciphertext. Crucially, prefer a KMS provider (a cloud KMS or Vault) over a locally-stored key: with the raw aescbc/secretbox providers the encryption key sits on the API server host, so an attacker who owns that host owns the key too. A KMS keeps the key material off the node.

Why it matters: Base64 is not a security control. Until you enable encryption at rest — ideally KMS-backed — an etcd backup is a plaintext dump of every credential in the cluster, and backups travel to places the cluster's RBAC never reaches.

Second fix: control who can read them

  • Lock down get/list on secrets. This is RBAC's highest-value target. Scope it to the specific service accounts that need specific secrets, never namespace-wide by default.
  • Remember pod-create implies secret-read. Anyone who can schedule a pod in a namespace can mount its secrets. Guard pod creation as tightly as secret access.
  • Disable unnecessary token automounting. The service-account token is itself a secret; don't mount it into pods that never call the API.
  • Protect etcd directly. Encrypt etcd's peer and client traffic, restrict node access, and treat etcd backups as crown-jewel data — encrypted, access-logged, and short-lived.

Third fix: keep secrets outside the cluster

For higher-value secrets, the strongest move is not to store them in Kubernetes at all. External secret stores — HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager — hold the source of truth outside etcd, with their own audit logs, fine-grained policy, rotation, and dynamic short-lived credentials. Two patterns bring them into the cluster:

  • External Secrets Operator. Syncs values from an external store into Kubernetes Secrets. Convenient and store-agnostic — but note the secret still materialises in etcd, so keep encryption at rest on.
  • Secrets Store CSI Driver. Mounts secrets from the external store straight into the pod filesystem at runtime, so they need not be persisted as Kubernetes Secrets at all — tighter, at some operational cost.
  • Dynamic secrets. Vault can issue short-lived, per-workload database credentials that expire in minutes, so a leaked value is worthless almost immediately — the strongest option where the app supports it.
Enabling KMS-backed encryption at rest for Secretskind: EncryptionConfiguration
resources:
- resources: ["secrets"]
  providers:
  - kms: { name: cloudkms, endpoint: ... }  # key stays in KMS, not on the node
  - identity: {}  # fallback to read pre-existing plaintext during migration

A defensible secrets posture layers all three: encryption at rest so etcd and its backups are ciphertext, tight RBAC so few principals can read secrets at all, and an external store for the credentials that matter most. Rotation ties it together — assume anything unrotated for a long time has been exposed somewhere in a log, a backup, or an image layer.

And all of it hinges on knowing where your secrets are. A credential hardcoded into an image, sitting in a ConfigMap 'temporarily', or held by a workload nobody remembers deploying is outside every one of these controls. You can't rotate, encrypt, or restrict access to a secret you haven't discovered.

Test for it — in practice
  • Check whether encryption at rest is enabled for Secrets on your API server — without an EncryptionConfiguration, an etcd backup is a plaintext dump.
  • Skim RBAC for who holds get/list on secrets in each namespace; broad access is the highest-value target in the cluster.

Those two checks cover the basics; tracing where every secret actually lives — including the ones in ConfigMaps and images — is what our assessment runs for you.

Keep reading
Kubernetes RBAC Done Right