Skip to main content
This guide demonstrates how to enable encryption-at-rest for Kubernetes Secret objects stored in etcd on a kubeadm single-node control plane. You’ll see how Secrets are stored by default, how to enable API server encryption, and how to ensure existing Secrets become encrypted. Prerequisites
  • A single-node kubeadm cluster (control plane) with terminal access on the control plane node.
  • Root or sudo access to edit static pod manifests and move files on the host.
Keywords: Kubernetes secrets encryption, etcd encryption, kube-apiserver encryption-provider-config, EncryptionConfiguration, aescbc.

1) Create a Secret and inspect how it is stored

Create a Secret from a literal value:
Confirm the Secret exists:
Describe the Secret metadata and data size:
View the Secret manifest (note: Secret data is Base64-encoded, not encrypted):
Decode to verify the original value:
Base64 encoding is not encryption. Anyone with read access to etcd or Secret manifests can decode the values. Never store raw Secret YAMLs with encoded data in public repositories.

2) Inspect how the Secret is stored in etcd (raw view)

etcd stores Kubernetes objects under keys such as /registry/secrets/<namespace>/<name>. To read that raw key you need etcdctl configured to use the v3 API and valid etcd client TLS certificates. Install etcdctl (Debian/Ubuntu example):
Set ETCDCTL_API=3 and read the raw key (adjust paths and secret name). On kubeadm clusters, the apiserver-etcd-client certs are commonly accepted by etcd:
If encryption at rest is not enabled, the hexdump or raw output will include the plaintext value (supersecret), demonstrating that direct etcd access with valid certs reveals Secret contents. Reference: etcdctl v3 usage.

3) Check whether the API server is configured for encryption

The kube-apiserver enables encryption-at-rest using the --encryption-provider-config flag. On kubeadm single-node control planes the API server runs as a static pod; inspect the static pod manifest:
Look for --encryption-provider-config=/path/to/enc.yaml in the command args. If it’s absent, encryption is not enabled. Tip: You can also inspect the running process args after kube-apiserver is started:

4) Create an EncryptionConfiguration file

Create an EncryptionConfiguration file (example enc.yaml). Only resources listed under resources will be affected—here we list secrets. Provider order matters: the first provider is used for encrypting new writes; later providers are used for decryption of older records. Example enc.yaml using aescbc plus an identity fallback. Replace the example base64 key with a securely generated 32-byte key encoded in base64:
Notes:
  • identity: {} means “no encryption” as a fallback for decryption. If identity is listed first, data will not be encrypted.
  • aescbc requires a 32-byte key; provide the key base64-encoded.
Generate a secure 32-byte key example:
Save enc.yaml locally and move it to a directory the kube-apiserver static pod can mount (example /etc/kubernetes/enc):
Reference: Kubernetes Encryption at Rest docs.

5) Configure the kube-apiserver static pod to use the configuration

Edit /etc/kubernetes/manifests/kube-apiserver.yaml and make three changes:
  1. Add the --encryption-provider-config flag in the command args:
  1. Mount the host directory into the kube-apiserver container by adding a volumeMount under containers[0].volumeMounts:
  1. Add a matching volumes entry at the pod spec level:
When you save the manifest, the kubelet will recreate the static pod and restart the kube-apiserver process with the new configuration.
Modifying the kube-apiserver manifest will restart the API server. On single-node clusters this causes a brief outage. Plan for downtime or perform changes during a maintenance window.

6) Verify the API server restarted with the encryption flag

After saving the manifest, verify the API server is running and that the --encryption-provider-config flag is present:
Alternatively, watch the static pod status using the container runtime or kubelet tooling (e.g., crictl ps, docker ps) and kubectl -n kube-system get pods after the API server is ready.

7) Create a new Secret (will be encrypted) and inspect etcd

Create a new Secret after the API server is configured for encryption:
Read the raw etcd key for this new Secret (use the same etcdctl invocation as before):
You should now observe an encrypted blob in etcd. The stored key prefix may indicate something like k8s:enc:aescbc:v1:key1: followed by ciphertext bytes. The plaintext topsecret will not appear in the raw etcd output. Example (truncated hexdump):

8) Re-encrypt existing Secrets

Enabling encryption affects new writes only. Secrets created or written before enabling encryption remain in their original form in etcd until the resource is written again. Options to re-encrypt existing Secrets:
  • Reapply or replace a specific Secret to force a rewrite:
  • Force an update with an annotation (no data change required):
  • Reapply/replace all Secrets across all namespaces (use carefully — this rewrites all secret objects):
After rewriting, verify the etcd entry for the previously unencrypted Secret now contains encrypted data using the etcdctl method shown earlier.

Quick reference table


Summary & best practices

  • Kubernetes Secrets are Base64-encoded by default (not encrypted). Protect etcd access and avoid committing Secrets into public repositories.
  • Use the kube-apiserver --encryption-provider-config flag with an EncryptionConfiguration to enable encryption-at-rest for resources such as secrets.
  • Provider order matters: the first provider is used for encrypting new writes; later providers are used for decryption.
  • After enabling encryption, re-write existing objects to ensure they are stored encrypted in etcd.
  • Securely store and rotate encryption keys regularly; follow your organization’s key management policies.
Further reading: Thank you.

Watch Video