How Does Secret Encryption at Rest Work in Kubernetes?

intermediate|securitydevopssreplatform engineerCKA
TL;DR

By default, Kubernetes stores Secrets as base64-encoded plaintext in etcd. Encryption at rest adds a layer of encryption so that Secrets stored in etcd are encrypted using AES-CBC, AES-GCM, or an external KMS provider.

Detailed Answer

Kubernetes Secrets are not encrypted by default. They are stored as base64-encoded values in etcd, which means anyone with direct etcd access can read every Secret in the cluster. Encryption at rest protects Secrets by encrypting them before they are written to etcd.

The Default (Insecure) State

Without encryption at rest, Secrets are stored like this in etcd:

# Reading a Secret directly from etcd (base64 encoded, NOT encrypted)
ETCDCTL_API=3 etcdctl get /registry/secrets/default/my-secret | hexdump -C
# You can see the Secret values in plaintext

Enabling Encryption at Rest

1. Create an EncryptionConfiguration

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
    providers:
      # The first provider is used for encryption
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      # Fallback: read unencrypted Secrets written before encryption was enabled
      - identity: {}

Generate a random encryption key:

head -c 32 /dev/urandom | base64
# Outputs a 32-byte key suitable for AES-256

2. Configure the API Server

# Add to kube-apiserver flags
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml

3. Restart the API Server

After restart, all new Secrets are encrypted. Existing Secrets remain unencrypted until you re-encrypt them.

4. Re-encrypt Existing Secrets

# This reads every Secret and writes it back, triggering encryption
kubectl get secrets -A -o json | kubectl replace -f -

Encryption Providers

| Provider | Algorithm | Key Management | Recommended For | |----------|-----------|----------------|-----------------| | identity | None (plaintext) | N/A | Never in production | | aescbc | AES-CBC (padding) | Local file | Development/testing | | aesgcm | AES-GCM (authenticated) | Local file | Better than CBC but still local keys | | secretbox | XSalsa20+Poly1305 | Local file | Strong encryption, local keys | | kms v1/v2 | External KMS | External service | Production |

KMS Provider (Production Recommended)

KMS providers delegate encryption to an external key management service. The API server sends the data encryption key (DEK) to the KMS for wrapping/unwrapping, so the master key never leaves the KMS.

AWS KMS Example

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - kms:
          apiVersion: v2
          name: aws-encryption-provider
          endpoint: unix:///var/run/kmsplugin/socket.sock
          timeout: 3s
      - identity: {}

The KMS plugin runs as a sidecar or DaemonSet and communicates with AWS KMS:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: aws-encryption-provider
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: aws-encryption-provider
  template:
    metadata:
      labels:
        app: aws-encryption-provider
    spec:
      containers:
        - name: provider
          image: aws-encryption-provider:latest
          args:
            - --key=arn:aws:kms:us-east-1:123456789:key/abcd-1234
            - --listen=/var/run/kmsplugin/socket.sock
            - --region=us-east-1
          volumeMounts:
            - name: socket
              mountPath: /var/run/kmsplugin
          resources:
            requests:
              cpu: "50m"
              memory: "64Mi"
      volumes:
        - name: socket
          hostPath:
            path: /var/run/kmsplugin

Key Rotation

Local Key Rotation

# Step 1: Add new key as the first entry
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key2              # New key (used for encryption)
              secret: <new-key>
            - name: key1              # Old key (used for decryption)
              secret: <old-key>
      - identity: {}
# Step 2: Restart API server to pick up new config

# Step 3: Re-encrypt all Secrets with the new key
kubectl get secrets -A -o json | kubectl replace -f -

# Step 4: Remove old key from config (after verifying all Secrets are re-encrypted)

KMS Key Rotation

With KMS, you rotate the master key in the KMS service (e.g., AWS KMS automatic rotation). The DEK cache in the API server is refreshed automatically.

Verifying Encryption

# Check if a Secret is encrypted in etcd
ETCDCTL_API=3 etcdctl get /registry/secrets/default/my-secret \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key | hexdump -C

# Encrypted Secrets start with "k8s:enc:aescbc:" or "k8s:enc:kms:"
# Unencrypted Secrets show the JSON plaintext

Managed Kubernetes

| Provider | Encryption at Rest | Default | |----------|-------------------|---------| | EKS | AWS KMS envelope encryption | Optional (must enable) | | GKE | Google-managed encryption | Enabled by default | | AKS | Azure Key Vault | Optional |

# Enable on EKS
aws eks associate-encryption-config \
  --cluster-name my-cluster \
  --encryption-config '[{
    "resources": ["secrets"],
    "provider": {
      "keyArn": "arn:aws:kms:us-east-1:123456789:key/abcd"
    }
  }]'

Common Mistakes

  1. Assuming Secrets are encrypted by default — they are not
  2. Storing the encryption key in the same place as etcd — if an attacker gets etcd, they get the key too
  3. Not re-encrypting existing Secrets — old Secrets remain unencrypted after enabling encryption
  4. Using identity provider first — the first provider is used for encryption, so identity first means no encryption
  5. Not backing up the encryption key — if the key is lost, all encrypted Secrets become unrecoverable

Why Interviewers Ask This

Many teams mistakenly believe Secrets are encrypted by default. This question tests your understanding of Kubernetes security gaps and how to close them for production environments.

Common Follow-Up Questions

Are Kubernetes Secrets encrypted by default?
No — Secrets are base64-encoded, not encrypted. Anyone with access to etcd can read all Secrets in plaintext. Encryption at rest must be explicitly configured.
What is the difference between identity, aescbc, aesgcm, and kms providers?
Identity is plaintext (no encryption). aescbc and aesgcm use local keys. KMS delegates encryption to an external key management service (AWS KMS, HashiCorp Vault) for better key lifecycle management.
How do you rotate encryption keys?
Add the new key as the first provider in the config, restart the API server, then re-encrypt all Secrets with kubectl get secrets -A -o json | kubectl replace -f -.

Key Takeaways

  • Secrets are NOT encrypted by default — they are base64-encoded plaintext in etcd.
  • Enable EncryptionConfiguration on the API server to encrypt Secrets at rest.
  • Use a KMS provider (AWS KMS, GCP KMS, Vault) for production to avoid managing encryption keys locally.

Related Questions

You Might Also Like