Encrypting Kubernetes Secrets at Rest

Etcd Encryption
Etcd Encryption

Introduction

Everybody constantly criticises storing passwords in plain text; however, if you are running vanilla K8s clusters, your confidential data, such as Secrets, are unfortunately not encrypted and stored as base64 encoded in etcd, which can be easily decoded.

In this tutorial, I will show you how to encrypt your K8s sensitive data at rest using the secretbox options. It is recommended to use kms v2 for production.

Prerequisites

  • A running K8s cluster.
  • Administrator/root access to the control plane nodes.

Step-by-step Guide

  1. SSH to one of the control plane nodes and create the encryption configuration enc.yaml file:

    sudo mkdir /etc/kubernetes/enc
    sudo touch /etc/kubernetes/enc/enc.yaml
    
  2. Make sure that only the user who runs the kube-apiserver can read the configuration file:

    sudo chmod 600 /etc/kubernetes/enc/enc.yaml
    
  3. Generate a new encryption key:

    head -c 32 /dev/urandom | base64
    
  4. Update the encryption configuration file:

    ---
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
          - secrets
        providers:
          - secretbox:
              keys:
                - name: key1
                  secret: <BASE 64 ENCODED SECRET>
          - identity: {}
    

    Then, securely copy this file to other control plane nodes, if any.

  5. Update the kube-apiserver (all control plane nodes) to use the new encryption configuration file: Edit the manifest /etc/kubernetes/manifests/kube-apiserver.yaml to:

    ---
    #
    # This is a fragment of a manifest for a static Pod.
    # Check whether this is correct for your cluster and for your API server.
    #
    apiVersion: v1
    kind: Pod
    metadata:
      annotations:
        kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.20.30.40:443
      creationTimestamp: null
      labels:
        app.kubernetes.io/component: kube-apiserver
        tier: control-plane
      name: kube-apiserver
      namespace: kube-system
    spec:
      containers:
      - command:
        - kube-apiserver
        ...
        - --encryption-provider-config=/etc/kubernetes/enc/enc.yaml  # add this line
        volumeMounts:
        ...
        - name: enc                           # add this line
          mountPath: /etc/kubernetes/enc      # add this line
          readOnly: true                      # add this line
        ...
      volumes:
      ...
      - name: enc                             # add this line
        hostPath:                             # add this line
          path: /etc/kubernetes/enc           # add this line
          type: DirectoryOrCreate             # add this line
      ...    
    

Testing

From now on, only newly created secrets will be encrypted. Let's create one to test:

  1. Create a test secret:

    kubectl -n default create secret generic testsecret  --from-literal=mykey=mydata
    
  2. Exec into the ETCD pod to view the newly created secret:

    kubectl exec -i -t -n kube-system etcd-$(hostname) -c etcd -- sh -c "sh"
    
    ETCDCTL_API=3 etcdctl \
       --cacert=/etc/kubernetes/pki/etcd/ca.crt   \
       --cert=/etc/kubernetes/pki/etcd/server.crt \
       --key=/etc/kubernetes/pki/etcd/server.key  \
       get /registry/secrets/default/testsecret
    
  3. The output is similar to this (abbreviated):

    /registry/secrets/default/testsecret
    ;????U?:?sd?y~???2???)ey1:?W?wu5,?J???^?v??i?|
    ???                   ???2???|    ???2???
    

    As you can see, the data is encrypted.

  4. Delete the secret:

    kubectl -n default delete secret testsecret
    

Encrypt Secrets

The simplest way to encrypt all current secrets is to delete and recreate them. This command will do the trick:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

Conclusion

Walla! You now have a more secure K8s cluster with all confidential data encrypted at rest.

References