Encrypt Secrets in an etcd Database

Page last updated:

This topic describes how to create and use a Kubernetes profile to encrypt a cluster’s etcd database.

For more information and other uses of Kubernetes profiles, see Using Kubernetes Profiles.

Create Kubernetes Profile

To create a new create Kubernetes profile:

  1. Create a new a base64-encoded, 32-byte random key string to use as a secret.

    • On Linux or MacOS, you can generate the secret by running:

      head -c 32 /dev/urandom | base64
      

      This command returns a base64-encoded, 32-byte key string, for example: jHc3NMp7s7T7JoJuZF7NUSkHVYCSikJCNJ+LrltbkJk=

  2. To create an encryption provider configuration file, create a file named encryption-provider-config.yml containing the following content:

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

    Where BASE-64-ENCODED-SECRET is the secret key string created in the last step.

    Note: To read unencrypted secrets, include the identity provider as the last provider as shown above.

  3. To create a Kubernetes profile configuration file that customizes the kube-apiserver using your encryption provider configuration file, create a JSON file containing the following content:

    {
      "name": "PROFILE-TITLE",
      "description": "PROFILE-DESC",
      "customizations": [
        {
          "component": "kube-apiserver",
          "arguments": {
          },
          "file-arguments": {
            "encryption-provider-config": "/LOCAL-DIR/encryption-provider-config.yml"
          }
        }
      ]
    }
    

    Where:

    • PROFILE-TITLE is the name of your profile, for example profile1.
    • PROFILE-DESC is the description you want to use for the profile.
    • LOCAL-DIR is the directory containing your encryption provider configuration file.
  4. To create a Kubernetes profile based on your profile configuration file, use the TKGI CLI:

    tkgi create-k8s-profile PROFILE-PATH
    

    Where PROFILE-PATH is the path and filename of the JSON profile file you created in the step above.

    For example:

    $ tkgi create-k8s-profile /tmp/profile1.json
    Kubernetes profile profile1 successfully created
    

Create Kubernetes Cluster

To create a Kubernetes cluster based on a Kubernetes profile:

  1. To create a cluster based on a Kubernetes profile, use the TKGI CLI:

    tkgi create-cluster CLUSTER-NAME -e EXTERNAL-HOSTNAME -p small -n 1 --kubernetes-profile K8S-PROFILE
    

    Where:

    • CLUSTER-NAME is the name to apply to the new cluster.

      Note: Use only lowercase characters when naming your cluster if you manage your clusters with Tanzu Mission Control (TMC). Clusters with names that include an uppercase character cannot be attached to TMC.

    • EXTERNAL-HOSTNAME is the address to use to access Kubernetes API.
    • K8S-PROFILE is the Kubernetes profile filename. For more information, see tkgi create-cluster in TKGI CLI.

    Running this command restarts your kube-apiserver with your encryption provider configuration file set as the --encryption-provider-config parameter.

    For example:

    $ tkgi create-cluster cluster1 -e cluster1-internal.com -p small -n 1 --kubernetes-profile profile1
    TKGI Version:              1.9.0-build.1
    Name:                     cluster1
    K8s Version:              1.18.8
    Plan Name:                small
    UUID:                     22f78823-0b70-4684-be2f-8457d6f3b1f1
    Last Action:              CREATE
    Last Action State:        in progress
    Last Action Description:  Creating cluster
    Kubernetes Master Host:   cluster1-internal.com
    Kubernetes Master Port:   8443
    Worker Nodes:             1
    Kubernetes Master IP(s):  In Progress
    Network Profile Name:
    Kubernetes Profile Name:  profile1
    Tags:
    

Ensure Existing Data is Encrypted

If you have existing data, the data has been stored without being encrypted.

To encrypt your existing data:

  1. Run the following:

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

    This command reads all secrets, applies encryption, and saves the data encrypted. For more information, see Ensure all secrets are encrypted in the Kubernetes documentation.

  2. Be sure to complete the steps in Verify Your Data is Encrypted below.

Verify Your Data is Encrypted

After the kube-apiserver restarts, all existing data in its etcd database should be encrypted, and it should encrypt any new data that it stores.

To ensure the etcd data has been encrypted, create and store a test secret and then retrieve it:

  1. To create and store a test secret:

    kubectl create secret generic SECRET-NAME -n NAMESPACE --from-literal=KEY-NAME=KEY-VALUE
    

    Where:

    • SECRET-NAME is the name to apply to the secret.
    • NAMESPACE is the namespace.
    • KEY-NAME is the name of the key.
    • KEY-VALUE is the value to encrypt.

    For example:

    kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    

    For more information, see create secret generic in the Kubernetes documentation.

  2. To read the test secret out of etcd, use the etcdctl command line:

    ETCDCTL_API=3 etcdctl get /registry/secrets/default/SECRET-NAME [ETCD-ARGS] | hexdump -C
    

    Where:

    • SECRET-NAME is the name to apply to the secret.
    • ETCD-ARGS are the additional arguments for connecting to the etcd server.

    For example, if you SSH into your master node and with admin privileges you could run the following command to get the secret and view it.

    ETCDCTL_API=3 /var/vcap/jobs/etcd/bin/etcdctl get /registry/secrets/default/secret1
    

  3. Review the output and verify the stored test secret is prefixed with k8s:enc:aescbc:v1:. For example:

    master/3f9c5ca9-a2b1-469c-9007-6fdd0844a5ec:/var/vcap/bosh_ssh/bosh_2f4aaa514474422# ETCDCTL_API=3 /var/vcap/jobs/etcd/bin/etcdctl get /registry/secrets/default/secret1
    
    /registry/secrets/default/secret1
    k8s:enc:aescbc:v1:key1:����@�8�����A������2cM7������uL�/
    

    The k8s:enc:aescbc:v1: prefix indicates the aescbc provider has encrypted the resulting data.

  4. To retrieve the stored test secret:

    kubectl describe secret secret1 -n default
    

    Where: SECRET-NAME is the name of the secret.

    For example:

    kubectl describe secret secret1 -n default
    

  5. Review the output and verify the secret is correctly decrypted. The mydata content is returned encoded. For information on how to decode mydata, see decoding a secret.

Rotate Encryption Key for Secrets in etcd Database

This section describes how to use encryption provider config to rotate a key. For highly-available deployments running multiple kube-apiserver processes, changing a secret without incurring downtime requires a multi-step process.

To rotate an encryption key for a secret in an etcd database:

  1. Generate a new key. For more information, see Create Kubernetes Profile above.
  2. Add the new key as the second key entry for the current provider in your encryption-provider-config.yml keys: list.

    For example:

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
        - secrets
        providers:
        - aescbc:
            keys:
            - name: key1
              secret: jHc3NMp7s7T7JoJuZF7NUSkHVYCSikJCNJ+LrltbkJk=
            - name: key2
              secret: MNI7xeE48/1dH+pE5LLHblhId6AjbzdN2I6rubh8AfE=
        - identity: {}
    

    Note: To read unencrypted secrets, include the identity provider as the last provider, as shown above.

  3. Create a Kubernetes profile configuration file that references the modified encryption-provider-config.yml.

    For example:

    {
      "name": "profile2",
      "description": "Testing profile two",
      "customizations": [
        {
          "component": "kube-apiserver",
          "arguments": { },
          "file-arguments": {
            "encryption-provider-config": "/tmp/encryption-provider-config.yml"
          }
        }
      ]
    }
    

    For information on how to create a Kubernetes profile configuration file, see Create Kubernetes Profile above.

  4. Use the TKGI CLI to create a profile based on the configuration file.

    For example:

    $ tkgi create-k8s-profile /tmp/profile2.json
    Kubernetes profile profile2 successfully created
    

    For information on how to use the TKGI CLI to create a Kubernetes profile, see Create Kubernetes Profile above.

  5. Update the cluster with this new profile.

    tkgi update-cluster CLUSTER-NAME --kubernetes-profile PROFILE-TITLE
    

    Where:

    • CLUSTER-NAME is the name to apply to the new cluster.
    • PROFILE-TITLE is the name of your profile, for example profile2.

    For example:

    $ tkgi update-cluster cluster1 --kubernetes-profile profile2
    
    Update summary for cluster cluster1:
    Kubernetes Profile Name: profile2
    Are you sure you want to continue? (y/n): y
    Use 'tkgi cluster cluster1' to monitor the state of your cluster
    

    Running this command restarts your kube-apiserver with your encryption provider configuration file set as the --encryption-provider-config parameter. This ensures that each server can decrypt using the new key.

    Warning: Do not use tkgi update-cluster on clusters configured with a network profile CNI configuration. For more information, see The Resize, Update and Upgrade TKGI CLI Cluster Commands Remove the Network Profile CNI Configuration from a Cluster in the Release Notes.

  6. Edit the encryption provider configuration file so that it lists the new key as the first entry in the keys: property, swapping its position with the old key.

    For example:

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
        - secrets
        providers:
        - aescbc:
            keys:
            - name: key2
              secret: MNI7xeE48/1dH+pE5LLHblhId6AjbzdN2I6rubh8AfE=
            - name: key1
              secret: jHc3NMp7s7T7JoJuZF7NUSkHVYCSikJCNJ+LrltbkJk=
        - identity: {}
    
  7. Create a Kubernetes profile configuration file that references the modified encryption-provider-config.yml.

    For example:

    {
      "name": "profile3",
      "description": "Testing profile three",
      "customizations": [
        {
          "component": "kube-apiserver",
          "arguments": { },
          "file-arguments": {
            "encryption-provider-config": "/tmp/encryption-provider-config.yml"
          }
        }
      ]
    }
    
  8. Use the TKGI CLI to create a profile based on the new Kubernetes profile configuration file.

    For example:

    $ tkgi create-k8s-profile /tmp/profile3.json
    Kubernetes profile profile2 successfully created
    
  9. Update the cluster with the new profile.

    For example:

    $ tkgi update-cluster cluster1 --kubernetes-profile profile3
    
    Update summary for cluster cluster1:
    Kubernetes Profile Name: profile3
    Are you sure you want to continue? (y/n): y
    Use 'tkgi cluster cluster1' to monitor the state of your cluster
    

    This restarts the kube-apiserver processes to ensure that each server now encrypts using the new key.

    Warning: Do not use tkgi update-cluster on clusters configured with a network profile CNI configuration. For more information, see The Resize, Update and Upgrade TKGI CLI Cluster Commands Remove the Network Profile CNI Configuration from a Cluster in the Release Notes.

  10. To encrypt all existing secrets with the new key:

    kubectl get secrets --all-namespaces -o json | kubectl replace -f - 
    
  11. Back up etcd, snapshotting it with the new key.

  12. Edit the encryption-provider-config.yml again to remove the old decryption key from the config keys: list.

    For example:

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
        - secrets
        providers:
        - aescbc:
            Keys:
            - name: key2
              secret: MNI7xeE48/1dH+pE5LLHblhId6AjbzdN2I6rubh8AfE=
        - identity: {}
    
  13. Create a new Kubernetes profile configuration file that references the modified encryption-provider-config.yml.

    For example:

    {
      "name": "profile4",
      "description": "Testing profile four",
      "customizations": [
        {
          "component": "kube-apiserver",
          "arguments": { },
          "file-arguments": {
            "encryption-provider-config": "/tmp/encryption-provider-config.yml"
          }
        }
      ]
    }
    

    For information on how to create a Kubernetes profile configuration file, see Create Kubernetes Profile above.

  14. Use the TKGI CLI to create a profile based on the new Kubernetes profile configuration file.

    For example:

    $ tkgi create-k8s-profile /tmp/profile4.json
    Kubernetes profile profile4 successfully created
    
  15. Update the cluster with the new profile.

    For example:

    $ tkgi update-cluster cluster1 --kubernetes-profile profile4
    
    Update summary for cluster cluster1:
    Kubernetes Profile Name: profile4
    Are you sure you want to continue? (y/n): y
    Use 'tkgi cluster cluster1' to monitor the state of your cluster
    

    This restarts the kube-apiserver processes to ensure that each server now encrypts using the new key.

    Warning: Do not use tkgi update-cluster on clusters configured with a network profile CNI configuration. For more information, see The Resize, Update and Upgrade TKGI CLI Cluster Commands Remove the Network Profile CNI Configuration from a Cluster in the Release Notes.

  16. Verify the stored secret is prefixed with k8s:enc:aescbc:v1:key2 which indicates the aescbc provider has encrypted the resulting data.

    For example:

    master/3f9c5ca9-a2b1-469c-9007-6fdd0844a5ec:/var/vcap/bosh_ssh/bosh_2f4aaa514474422\#
    ETCDCTL_API=3 /var/vcap/packages/etcdctl/etcdctl --endpoints
    https://master-0.etcd.cfcr.internal:2379 --cert
    /var/vcap/jobs/etcd/config/etcdctl.crt --key
    /var/vcap/jobs/etcd/config/etcdctl.key --cacert
    /var/vcap/jobs/etcd/config/etcdctl-ca.crt get
    /registry/secrets/default/secret1
    
    /registry/secrets/default/secret1
    
    k8s:enc:aescbc:v1:key2:����@�8�����A������2cM7������uL�/
    

Decrypt Secrets in the etcd Database

  1. To disable encryption of data at rest, place the identity provider as the first entry in the encryption provider configuration file.

    For example:

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
        - secrets
        providers:
        - identity: {}
        - aescbc:
            keys:
            - name: key1
              secret: <BASE 64 ENCODED SECRET>
    
    
  2. Create a new Kubernetes profile configuration file that references the modified encryption-provider-config.yml.

    For example:

    {
      "name": "profile5",
      "description": "Testing profile five",
      "customizations": [
        {
          "component": "kube-apiserver",
          "arguments": { },
          "file-arguments": {
            "encryption-provider-config": "/tmp/encryption-provider-config.yml"
          }
        }
      ]
    }
    

    For information on how to create a Kubernetes profile configuration file, see Create Kubernetes Profile above.

  3. Use the TKGI CLI to create a profile based on the modified Kubernetes profile configuration file.

    For example:

    $ tkgi create-k8s-profile /tmp/profile5.json
    Kubernetes profile profile5 successfully created
    
  4. Update the cluster with the new profile.

    For example:

    $ tkgi update-cluster cluster1 --kubernetes-profile profile5
    
    Update summary for cluster cluster1:
    Kubernetes Profile Name: profile5
    Are you sure you want to continue? (y/n): y
    Use 'tkgi cluster cluster1' to monitor the state of your cluster
    

    For information on how to use update-cluster, see Rotate Encryption Key for Secrets in etcd Database above.

  5. To force all secrets to be decrypted:

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

Please send any feedback you have to pks-feedback@pivotal.io.