Skip to main content

Encrypt etcd

Inspect 1.36

etcd is the database where Kubernetes stores every object, including Secrets, and by default it stores them in plaintext. Turn on encryption at rest so a copied disk or a leaked backup reveals nothing. You create the key, there is no provider KMS in the path, and the key Secret is yours; see for exactly who holds what.

Create the key Secret first#

The order matters: create the Secret that holds the encryption configuration before you turn the feature on. If you enable etcdEncryptionSecretRef while the Secret is missing, the API server references an encryption file that does not exist and the control plane hangs on start.

Generate a random 32-byte key:

		$ head -c 32 /dev/urandom | base64
	

Then create the Secret in the management cluster, with secretbox as the active provider and identity last so already-plaintext Secrets still read:

etcd-encryption-secret.yamlyaml
		apiVersion: v1
kind: Secret
metadata:
  name: etcd-encryption
type: Opaque
stringData:
  config: |-
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
          - secrets
        providers:
          - secretbox:
              keys:
                - name: key1
                  secret: <secret-key>
          - identity: {}
	
		$ kubectl apply -f etcd-encryption-secret.yaml
	
Note

secretbox is the recommended provider (modern authenticated encryption). aescbc is the alternative for compatibility. There is no in-path KMS provider on this platform: you hold the key material, and its custody is yours.

Turn it on#

Point the cluster at the Secret with the etcdEncryptionSecretRef topology variable, on the Cluster object in the management cluster:

yaml
		spec:
  topology:
    variables:
      - name: etcdEncryptionSecretRef
        value:
          enabled: true
          name: etcd-encryption # the Secret you created above
          key: config # the key inside it
	

name and key are how the API server finds the config: name is the Secret, key is the field inside it. They default to etcd-encryption and config, which match the Secret above, so you can omit them if you use those names. Set them here anyway so the create step and the enable step line up.

This is a control-plane change, so the control-plane nodes roll to pick it up. The config file is written from the Secret when a control plane provisions, which is why every change here, including a rotation, needs a control-plane roll. Set it at cluster config time along with your other topology variables; see for the full list.

Encrypt the Secrets that already exist#

Enabling encryption does not rewrite Secrets stored before it was on; each is encrypted on its next write. Re-encrypt one by triggering a small write, or all of them at once:

		$ kubectl annotate secret <name> -n <namespace> \
  encryption-rewrite="$(date +%s)" --overwrite
$ kubectl get secrets --all-namespaces -o json | kubectl replace -f -
	

The second command reads and rewrites every Secret with the same data, applying encryption to the ones that were previously plaintext.

Verify#

The upstream verification reads the raw bytes of a Secret directly from etcd with etcdctl and confirms they are ciphertext, not plaintext. That requires control-plane access to etcd; follow the Kubernetes verification steps.

Rotate the key#

Rotate when custody changes (someone with access leaves, or you suspect a key was exposed). Add the new key above the old one in the keys list so it becomes the active encryptor while the old key can still decrypt, roll the control plane, re-encrypt all Secrets with the command above, then remove the old key and roll again. To disable encryption safely, move identity to the top of the providers list, roll, re-encrypt (which now writes plaintext), then remove the encryption config.

Important

Encryption at rest protects a copied disk or a leaked backup. It is not access control: anyone who can read a Secret through the API still sees it. Pair it with tight RBAC on Secrets, covered in .