Challenge
DifficultyHard
Learner activity No attempts yet

Encrypt Kubernetes Secrets at Rest in etcd

Configure an EncryptionConfiguration to encrypt Secret data at rest in etcd, apply it to the API server, and verify that existing and newly created Secrets are actually stored encrypted on disk.

Scenario

By default, Kubernetes stores all object data — including Secrets — as plaintext inside etcd. Anyone with access to etcd's data directory or an etcd backup file can read every Secret value directly, without ever going through the Kubernetes API or RBAC.

A Secret named db-credentials already exists in the secure namespace, and right now its username and password values are sitting in etcd completely unencrypted. Confirm this yourself:

sudo ETCDCTL_API=3 etcdctl get /registry/secrets/secure/db-credentials \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

You will see the raw admin / SuperSecret123 values readable in the output. Your job is to fix this by enabling encryption at rest for Secrets.


Task

  1. Create an EncryptionConfiguration file at /etc/kubernetes/enc/encryption-config.yaml that encrypts the secrets resource using a real encryption provider (aescbc, aesgcm, or secretbox) — not just identity
  2. Update the kube-apiserver static pod manifest to use this configuration via --encryption-provider-config, and mount the config directory into the pod
  3. Recreate the existing db-credentials Secret so it picks up the new encryption — encryption only applies to writes that happen after it's enabled, so the Secret must be rewritten to actually become encrypted in etcd
  4. Confirm db-credentials is now stored encrypted in etcd, and that it's still readable correctly through kubectl get secret — proving the API server can both encrypt and decrypt transparently

Hint 1 — Create the EncryptionConfiguration

An EncryptionConfiguration is a YAML file that tells the API server which resources to encrypt and which provider to use. Create the directory and file:

sudo mkdir -p /etc/kubernetes/enc
sudo vi /etc/kubernetes/enc/encryption-config.yaml

A minimal working example:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      - identity: {}

Generate a valid key:

head -c 32 /dev/urandom | base64

The identity: {} provider must come last — it is the fallback that reads unencrypted data already in etcd. List your real provider first so new writes use it.

Documentation

Hint 2 — Update the kube-apiserver Manifest

Edit the kube-apiserver static pod manifest to point to the new config and mount the directory containing it:

sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml

Add the flag to the command args:

- --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml

Add a volume mount and a matching hostPath volume so the container can actually read the file:

volumeMounts:
- mountPath: /etc/kubernetes/enc
  name: enc
  readOnly: true

volumes:
- hostPath:
    path: /etc/kubernetes/enc
    type: DirectoryOrCreate
  name: enc

The kubelet detects the manifest change automatically and restarts the apiserver pod. Wait for it to come back before continuing:

kubectl get pod -n kube-system -l component=kube-apiserver -w

Documentation

Hint 3 — Recreate the Existing Secret and Verify

Enabling encryption only affects future writes — Secrets already in etcd remain unencrypted until they are rewritten. Force a rewrite without changing the data:

kubectl get secret db-credentials -n secure -o json | kubectl replace -f -

Now check etcd directly to confirm encryption is working — look for the k8s:enc: prefix, which marks encrypted data:

sudo ETCDCTL_API=3 etcdctl get /registry/secrets/secure/db-credentials \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

Finally confirm the API server can still decrypt it correctly when read normally — the actual username and password values should come back unchanged:

kubectl get secret db-credentials -n secure -o jsonpath='{.data.username}' | base64 -d
kubectl get secret db-credentials -n secure -o jsonpath='{.data.password}' | base64 -d

Documentation


Test Cases

Problem
Solution
Start