Encrypt Kubernetes secrets at rest
/etc/kubernetes/enc/encryption-config.yamlapiVersion: apiserver.config.k8s.io/v1kind: EncryptionConfigurationresources: - resources: ["secrets"] providers: - kms: # prefer a KMS provider (external key) apiVersion: v2 name: mykms endpoint: unix:///var/run/kmsplugin/socket.sock - aescbc: # fallback: a local key, better than nothing keys: - name: key1 secret: <BASE64_32_BYTE_KEY> - identity: {} # allows reading old, unencrypted secretsThen reference it: --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml
on the API server. On managed clusters this is often on by default — verify
rather than configure.
The misconception this page exists to correct
Section titled “The misconception this page exists to correct”Kubernetes Secrets are not encrypted. They are base64-encoded, and base64 is not encryption — it’s a reversible transform with no key, decodable by anyone, instantly:
kubectl get secret db -o jsonpath='{.data.password}' | base64 -dThat’s the whole “decryption.” A great many people believe that because a Secret
looks like gibberish in kubectl get -o yaml, it’s protected. It isn’t. The
base64 is there so binary values survive YAML, not for confidentiality.
Stored in etcd, a Secret is plaintext behind base64.
Anyone who can read etcd — a backup, a snapshot, a disk, a compromised etcd
node — reads every secret in the cluster with a base64 -d. Database passwords,
TLS keys, API tokens, cloud credentials: all of it.
Encryption at rest, and why KMS is better
Section titled “Encryption at rest, and why KMS is better”Turning on encryption at rest means the API server encrypts Secret values before writing them to etcd, so an etcd read yields ciphertext.
There are two provider styles, and the difference matters:
aescbc/aesgcm(local keys) — the encryption key lives in theEncryptionConfigurationfile on the control plane node. Better than nothing: an etcd backup alone no longer leaks secrets. But an attacker who compromises the control plane node has both the ciphertext and the key.kms(a KMS provider) — the key lives in an external system (cloud KMS, Vault), and the API server calls out to it. Now the encryption key is not on the node, so an etcd snapshot and a node compromise still don’t yield the plaintext without also compromising the KMS. This is the meaningfully stronger option and what you want in production.
Order matters in the provider list: the first provider encrypts; all of them can
decrypt. Keep identity last during migration so existing unencrypted secrets
remain readable, then remove it once everything is re-encrypted.
Existing secrets aren’t encrypted retroactively
Section titled “Existing secrets aren’t encrypted retroactively”Enabling encryption only affects secrets written after it’s on. Everything already in etcd stays as it was. You have to rewrite them:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -That reads and re-writes every secret, which re-encrypts them with the new provider. Until you do this, your old secrets are still plaintext in etcd no matter what the config says.
Related
Section titled “Related”- Secure etcd — where secrets live; encryption protects the data, etcd access protects the store.
- RBAC least privilege — encryption doesn’t help against a subject that can
get secrets.