Skip to content

Secrets Encryption


Introducción

Todas las APIs en Kubernetes que permiten escribir datos persistentes de recursos API admiten el cifrado via REST. Por ejemplo, puedes habilitar el cifrado via REST para los recursos tipo Secrets. Este cifrado es adicional a cualquier cifrado a nivel de sistema para el clúster de etcd o para los sistemas de archivos en los hosts donde se ejecuta el kube-apiserver.

La protección de datos sensibles es un pilar fundamental en la administración de clústeres Kubernetes. En este contexto, el cifrado en reposo se presenta como una capa adicional de seguridad que protege recursos críticos como los Secrets, garantizando que los datos almacenados en el clúster estén cifrados y seguros frente a accesos no autorizados.

En este tema, nos enfocaremos en cómo habilitar, validar y gestionar el cifrado de recursos tipo Secret en RKE2. También abordaremos la rotación de claves de cifrado, una práctica crucial para mantener la integridad y seguridad de los datos a lo largo del tiempo. A través de teoría y práctica, los estudiantes podrán adquirir habilidades esenciales para implementar estas medidas de seguridad en sus entornos.


Objetivo

Objetivo General:

  • En esta guía aprenderemos cómo habilitar y configurar el cifrado de datos de la API via REST.

Desarrollo del Tema

En la versión utilizada en este curso, esta activo por defecto el cifrado para este tipo de recurso. El contenido del recurso tipo secret, estará encriptado en etcd, esto nos ayudaréa a proteger información sensible de alguna extracción de una copia de este componente.

Laboratorio para el Estudiante

Indicaciones:

En el siguiente laboratorio, se validará el estado del cifrado de recursos via API Rest y se rotará la llave. Este procedimiento incluye reinicios del servicio rke2-server, en un orden en particular luego de la ejecución del subcomando secrets-encrypt.

Validando cifrado para el recurso tipo secret en RKE2

Pasos:

  1. Establecer una sesión como el usuario student a lab-#-master
    export LAB=X
    
    ssh student@lab-${LAB}-master 
    
  2. Cambiar al usuario root
    sudo -i
    
  3. Habilitar PATH y KUBECONFIG para administrar el cluster.
    export KUBECONFIG=/etc/rancher/rke2/rke2.yaml
    
    export PATH=$PATH:/var/lib/rancher/rke2/bin
    
  4. Obtener el estado de cifrado en el cluster.
    rke2 secrets-encrypt status
    
    Salida de ejemplo:
    Encryption Status: Enabled
    Current Rotation Stage: start
    Server Encryption Hashes: All hashes match
    
    Active  Key Type  Name
    ------  --------  ----
    *      AES-CBC   aescbckey   
    
    Notar Encryption Status: Enabled y Name aescbckey.
  5. Procederemos a crear un proyecto llamado test-secrets.
    kubectl create ns test-secrets
    
  6. Procederemos a crear 1000 recursos de tipo secret en el proyecto test-secrets. Esto puede tardar unos minutos.
    for i in {1..1000}; do kubectl create secret generic secret${i} --from-literal=password=mypassword -n test-secrets; done
    
    La creación de esta cantidad de recursos, nos ayuda a validar el proceso al rotar la llave y el proceso de cifrado.
  7. El nombre de la llave implementada en el cluster actualmente es aescbckey. Podemos confirmar el valor de esta llave consultando el siguiente archivo de configuración en el 1 nodo master.
    jq < /var/lib/rancher/rke2/server/cred/encryption-config.json
    
    Salida de ejemplo:
    {
    "kind": "EncryptionConfiguration",
    "apiVersion": "apiserver.config.k8s.io/v1",
    "resources": [
        {
        "resources": [
            "secrets"
        ],
        "providers": [
            {
            "aescbc": {
                "keys": [
                {
                    "name": "aescbckey",
                    "secret": "<BASE 64 ENCODED SECRET>"
                }
                ]
            }
            },
            {
            "identity": {}
            }
        ]
        }
    ]
    }
    
  8. Validaremos la versión de etcd en ejecución en el cluster.
    POD_ETCD=$(kubectl get pods -n kube-system --selector=component=etcd -o name |grep `hostname -f`)
    kubectl exec -it  -n kube-system ${POD_ETCD} -- etcdctl version
    
    Salida e ejemplo:
    etcdctl version: 3.5.13
    API version: 3.5
    
  9. Descargaremos la versión de etcd, para obtener acceso al comando `etcdctl.
    ETCD_VERSION=3.5.13 # Utiliza la versión obtenida anteriormente
    curl -LO https://github.com/etcd-io/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-amd64.tar.gz
    mkdir etcd-test
    tar -xvzf etcd-v${ETCD_VERSION}-linux-amd64.tar.gz -C etcd-test  --strip-components=1
    
  10. Crearemos un archivo con las variables para poder utilizar etcd, con el mismo certificado que utiliza RKE2.
    cat << EOF |tee etcd-test/vars.sh
    export ETCDCTL_ENDPOINTS='https://127.0.0.1:2379'
    export ETCDCTL_CACERT='/var/lib/rancher/rke2/server/tls/etcd/server-ca.crt'
    export ETCDCTL_CERT='/var/lib/rancher/rke2/server/tls/etcd/server-client.crt'
    export ETCDCTL_KEY='/var/lib/rancher/rke2/server/tls/etcd/server-client.key'
    export ETCDCTL_API=3
    EOF
    
  11. Validaremos la conexión a etcd.

    source etcd-test/vars.sh
    ./etcd-test/etcdctl endpoint status
    
    Salida de ejemplo:
    https://127.0.0.1:2379, 1d387b6842a8e192, 3.5.13, 18 MB, true, false, 2, 16653, 16653,
    

    ⚠ IMPORTANTE: ESTO PUEDE SER PELIGROSO PARA SU CLUSTER, PROCEDA CON CAUTELA. ESTO SE REALIZA CON FINES DIDACTICOS.

  12. Obtendremos el valor de una llave utilizando etcd.

    ./etcd-test/etcdctl get /registry/secrets/test-secrets/secret1|hexdump -C
    
    Salide de ejemplo:
    00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
    00000010  73 2f 74 65 73 74 2d 73  65 63 72 65 74 73 2f 73  |s/test-secrets/s|
    00000020  65 63 72 65 74 31 0a 6b  38 73 3a 65 6e 63 3a 61  |ecret1.k8s:enc:a|
    00000030  65 73 63 62 63 3a 76 31  3a 61 65 73 63 62 63 6b  |escbc:v1:aescbck|
    00000040  65 79 3a 85 3e 0e 14 60  28 76 04 12 58 3b 7a 04  |ey:.>..`(v..X;z.|
    00000050  24 f2 9d a8 bb e0 fe 10  88 d6 55 55 e2 2c 7d b2  |$.........UU.,}.|
    00000060  5f 66 a3 f6 67 40 74 6e  15 f2 db 74 1d 8f 53 6c  |_f..g@tn...t..Sl|
    00000070  6e 41 76 7b 8a 8c 3c d3  bc cc 92 11 ba 08 9e 73  |nAv{..<........s|
    00000080  88 da 5a 73 5c 9a 62 fd  c9 74 e2 57 1e e2 72 3d  |..Zs\.b..t.W..r=|
    00000090  42 2f 67 eb 6b b2 ce c6  8f 0c ac a7 33 25 70 e0  |B/g.k.......3%p.|
    000000a0  65 af 98 63 f9 72 cb c0  b6 7e f0 94 16 f2 63 c3  |e..c.r...~....c.|
    [...]
    000000e0  9f f0 73 8a 2b 2e 82 ef  ce 19 1a 2b 85 b3 1d 38  |..s.+......+...8|
    000000f0  01 6d 63 14 17 f3 cf 4a  35 4f 21 4a af 39 b3 9f  |.mc....J5O!J.9..|
    00000100  77 8d 06 71 2d 67 0c 50  aa fe c7 82 72 72 c3 38  |w..q-g.P....rr.8|
    00000110  c9 3f ac 3f 23 d6 d1 97  24 4c d6 9d cf c2 d5 f3  |.?.?#...$L......|
    00000120  12 66 f7 3a cb 2b 41 7a  8a 8b a8 bc 21 de c2 8c  |.f.:.+Az....!...|
    00000130  c4 84 8a 52 3d 45 fd 25  44 ab f6 26 c7 8b e9 b0  |...R=E.%D..&....|
    00000140  0f e6 76 3b f2 e5 9a 28  84 09 0d 0f ec 6d 9b 65  |..v;...(.....m.e|
    00000150  05 93 1d 0a                                       |....|
    00000154
    
    Note en la salida de su comando que el recurso almacenado inicie con el nombre k8s:enc:aescbc:v1:aescbckey. Lo que nos indica que el data ha sido cifrada utilizando esa llave. El nombre de la llave, debe coincidir con el resultado del comando del paso (4).

  13. Confirmaremos que el valor puede ser obtenido por medio del Cluster de RKE2.

    kubectl get secret/secret1 -n test-secrets -o yaml | yq .data.password|base64 -d
    
    Salida ejemplo:
    mypassword
    

    Hemos validado el funcionamiento del cifrado por defecto en RKE2 para el recurso tipo secret.

Rotación de llave de cifrado para recurso tipo secret en RKE2

Pasos:

📝 NOTA: Debe haber relizado la guía anterior "Validando cifrado para el recurso tipo secret en RKE2".

⚠ ALERTA: La secuencia de los pasos es importante o puede causar daño en su cluster.

  1. Obtener el estado de cifrado en el cluster.
    rke2 secrets-encrypt status
    
    Salida de ejemplo:
    Encryption Status: Enabled
    Current Rotation Stage: start
    Server Encryption Hashes: All hashes match
    
    Active  Key Type  Name
    ------  --------  ----
    *      AES-CBC   aescbckey   
    
    Notar Encryption Status: Enabled y Name aescbckey.
  2. Realizaremos una copia de seguridad del componenete etcd. Si falla su procedimiento, siga la guía del tema Backup / Restore para recuperar su ambiente.
    rke2 etcd-snapshot save --name pre-secret-key-rotation
    
  3. En un entorno de Alta Disponibilidad deberá seleccionar 1 servidor tipo master para ejecutar estas tareas.
    # Se ha seleccionado el mismo nodo en el que hemos creado el backup
    hostname -f
    
  4. Prepararemos la nueva llave de cifrado.
    rke2 secrets-encrypt prepare
    
    Salida ejemplo:
    prepare completed successfully
    
  5. Identificaremos la nueva llave y su valor generada en el paso anterior.
    jq < /var/lib/rancher/rke2/server/cred/encryption-config.json
    
    Salida de ejemplo:
    {
    "kind": "EncryptionConfiguration",
    "apiVersion": "apiserver.config.k8s.io/v1",
    "resources": [
        {
        "resources": [
            "secrets"
        ],
        "providers": [
            {
            "aescbc": {
                "keys": [
                {
                    "name": "aescbckey",
                    "secret": "<BASE 64 ENCODED SECRET>"
                },
                {
                    "name": "aescbckey-YYY-MM-DDT20:33:52Z",
                    "secret": "<BASE 64 ENCODED SECRET>"
                }
                ]
            }
            },
            {
            "identity": {}
            }
        ]
        }
    ]
    }
    
    Note que existen dos llaves, la generado por la instalación de RKE2 y la nueva llave con el formato aescbckey-YYY-MM-DDT20:33:52Z.
  6. Reiniciaremos de forma secuencial en cada nodo master del cluster el servicio de rke2-server. En nuesto ambiente solo contamos con un nodos master.

    systemctl restart rke2-server.service
    
    Espere a que finalice este proceso, para continar.

  7. Ejecutar el proceso de rotación de llave de cifrado (en el nodo selecionado).

    rke2 secrets-encrypt rotate
    
    Salida de ejemplo:
    rotate completed successfully
    

  8. Reiniciaremos el servicio de rke2-server (en el nodo selecionado).
    systemctl restart rke2-server.service
    
    Espere a que finalice este proceso, para continar.
  9. Si se cuenta con más nodos master, se debe de reiniciar el servicio rke2-server de forma secuancial.
  10. Ejecutar el proceso de recifrado de RKE2 (en el nodo selecionado).
    rke2 secrets-encrypt reencrypt
    
    Salida de ejemplo:
    reencryption started
    
  11. Consultaremos la bitácora del servicio rke2-server.
    journalctl  -u rke2-server -n 10  --no-pager
    
    Salida ejemplo:
    Nov 10 20:41:06 lab-1-master rke2[8985]: time="2024-11-20T20:41:06Z" level=info msg="Encryption keys right rotated"
    Nov 10 20:41:06 lab-1-master rke2[8985]: time="2024-11-20T20:41:06Z" level=info msg="Saving cluster bootstrap data to datastore"
    Nov 10 20:43:25 lab-1-master rke2[8985]: time="2024-11-20T20:43:25Z" level=info msg="Creating secrets-reencrypt event broadcaster"
    Nov 10 20:43:25 lab-1-master rke2[8985]: time="2024-11-20T20:43:25Z" level=info msg="Updating TLS secret for kube-system/rke2-serving (count: 10): map[listener.cattle.io/cn-10.142.15.231:10.142.15.231 listener.cattle.io/cn-10.39.0.1:10.39.0.1 listener.cattle.io/cn-127.0.0.1:127.0.0.1 listener.cattle.io/cn-__1-f16284:::1 listener.cattle.io/cn-kubernetes:kubernetes listener.cattle.io/cn-kubernetes.default:kubernetes.default listener.cattle.io/cn-kubernetes.default.svc:kubernetes.default.svc listener.cattle.io/cn-kubernetes.default.svc.cluster.local:kubernetes.default.svc.cluster.local listener.cattle.io/cn-lab-1-master.c.mx-g01.internal:lab-1-master.c.mx-g01.internal listener.cattle.io/cn-localhost:localhost listener.cattle.io/fingerprint:SHA1=DD24B5B14FCB56EF9E4AE111FE2458CDB84D24C9]"
    Nov 10 20:43:25 lab-1-master rke2[8985]: time="2024-11-20T20:43:25Z" level=info msg="Active TLS secret kube-system/rke2-serving (ver=31161) (count 10): map[listener.cattle.io/cn-10.142.15.231:10.142.15.231 listener.cattle.io/cn-10.39.0.1:10.39.0.1 listener.cattle.io/cn-127.0.0.1:127.0.0.1 listener.cattle.io/cn-__1-f16284:::1 listener.cattle.io/cn-kubernetes:kubernetes listener.cattle.io/cn-kubernetes.default:kubernetes.default listener.cattle.io/cn-kubernetes.default.svc:kubernetes.default.svc listener.cattle.io/cn-kubernetes.default.svc.cluster.local:kubernetes.default.svc.cluster.local listener.cattle.io/cn-lab-1-master.c.mx-g01.internal:lab-1-master.c.mx-g01.internal listener.cattle.io/cn-localhost:localhost listener.cattle.io/fingerprint:SHA1=DD24B5B14FCB56EF9E4AE111FE2458CDB84D24C9]"
    Nov 10 20:43:33 lab-1-master rke2[8985]: time="2024-11-20T20:43:33Z" level=info msg="Removing key:  Name: aescbckey, Secret: [REDACTED]"
    Nov 10 20:43:33 lab-1-master rke2[8985]: time="2024-11-20T20:43:33Z" level=info msg="Saving cluster bootstrap data to datastore"
    
    Note las lineas con el contenido Creating secrets-reencrypt event broadcasted, Removing key: Name: aescbckey.
  12. Identificaremos la nueva llave y su valor generada en el paso anterior.
    jq < /var/lib/rancher/rke2/server/cred/encryption-config.json
    
    Salida de ejemplo:
    {
    "kind": "EncryptionConfiguration",
    "apiVersion": "apiserver.config.k8s.io/v1",
    "resources": [
        {
        "resources": [
            "secrets"
        ],
        "providers": [
            {
            "aescbc": {
                "keys": [
                {
                    "name": "aescbckey-YYY-MM-DDT20:33:52Z",
                    "secret": "<BASE 64 ENCODED SECRET>"
                }
                ]
            }
            },
            {
            "identity": {}
            }
        ]
        }
    ]
    }
    
    Note que esta presente únicamente la nueva llave de cifrado aescbckey-YYY-MM-DDT20:33:52Z.
  13. Antes te continuar, verifique el estado del proceso de re cifrado.

    rke2 secrets-encrypt status
    
    Salida ejemplo:
    Encryption Status: Enabled
    Current Rotation Stage: reencrypt_finished
    Server Encryption Hashes: All hashes match
    
    Active  Key Type  Name
    ------  --------  ----
    *      AES-CBC   aescbckey-YYY-MM-DDT20:33:52Z
    
    Deberá aparecer únicaente la nueva llave y el estado Current Rotation Stage: reencrypt_finished.

  14. Reiniciaremos el servicio de rke2-server (en el nodo selecionado).

    systemctl restart rke2-server.service
    

  15. Confirmaremos que el valor puede ser obtenido por medio del Cluster de RKE2.
    kubectl get secret/secret1 -n test-secrets -o yaml | yq .data.password|base64 -d
    
    Salida ejemplo:
    mypassword
    
  16. Obtendremos el valor de una llave utilizando etcd.
    ./etcd-test/etcdctl get /registry/secrets/test-secrets/secret1|hexdump -C
    
    Salide de ejemplo:
    00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
    00000010  73 2f 74 65 73 74 2d 73  65 63 72 65 74 73 2f 73  |s/test-secrets/s|
    00000020  65 63 72 65 74 31 0a 6b  38 73 3a 65 6e 63 3a 61  |ecret1.k8s:enc:a|
    00000030  65 73 63 62 63 3a 76 31  3a 61 65 73 63 62 63 6b  |escbc:v1:aescbck|
    00000040  65 79 2d 32 30 32 34 2d  31 31 2d 32 30 54 32 30  |ey-YYYY-MM-DDT20|
    00000050  3a 33 33 3a 35 32 5a 3a  bb 18 c1 48 27 e9 7f a0  |:33:52Z:...H'...|
    00000060  73 22 d2 9f 02 ed 3c 56  8a 32 54 74 e7 cd 7a 2f  |s"....<V.2Tt..z/|
    00000070  10 e9 b8 c4 ef 9b 74 0f  0b c3 c3 4b cd b3 0f 0a  |......t....K....|
    00000080  87 6e 2d bc 5a 6c ee 05  5f fa 73 38 89 f8 a2 63  |.n-.Zl.._.s8...c|
    00000090  c8 11 4a a6 7c f7 3d 77  3e b8 e3 94 4c c9 f6 f3  |..J.|.=w>...L...|
    [...]
    000000d0  33 4a 6d 56 23 83 0f e1  ed e9 92 93 b7 c4 c8 9c  |3JmV#...........|
    000000e0  62 4c 6e 56 24 f6 9d 1b  8f c0 09 04 2e b9 8b 1b  |bLnV$...........|
    000000f0  d2 f0 e0 01 13 bd 93 ad  95 81 6c 94 7e c7 43 90  |..........l.~.C.|
    00000100  20 da 60 06 5b bf b4 50  9a ae da b5 a3 0c 1f d1  | .`.[..P........|
    00000110  ad 18 e7 dd 84 00 7e db  36 b5 a5 13 f6 50 7f ad  |......~.6....P..|
    00000120  6e 21 27 4e 4e af e0 64  90 89 38 01 79 aa 95 6d  |n!'NN..d..8.y..m|
    00000130  74 bd 5e b7 3a e9 55 67  1f a2 7e f7 27 35 64 49  |t.^.:.Ug..~.'5dI|
    00000140  7b 54 19 9f 4d eb 29 5c  98 31 f2 71 bd 3d 4e 27  |{T..M.)\.1.q.=N'|
    00000150  b7 92 d0 78 16 02 c4 8d  7d c3 f6 65 d1 86 00 96  |...x....}..e....|
    00000160  5c 34 96 f6 91 a7 95 71  0a                       |\4.....q.|
    00000169
    
    Note en la salida de su comando que el recurso almacenado inicie con el nombre aescbckey-YYY-MM-DDT20:33:52Z. Lo que nos indica que el data ha sido re cifrada utilizando esa llave. El nombre de la llave, debe coincidir con el resultado del comando del paso (5).

Recursos Adicionales


Conclusión

El cifrado de datos confidenciales via REST es una práctica fundamental para proteger la información sensible dentro de un clúster de Kubernetes, como los recursos de tipo Secret. A lo largo de este tema, hemos explorado cómo validar el cifrado de estos recursos en RKE2 y cómo realizar la rotación de las claves de cifrado para mantener la seguridad del clúster frente a posibles riesgos.

El laboratorio proporcionó una experiencia práctica que permitió a los estudiantes no solo validar la configuración del cifrado, sino también comprender los pasos necesarios para la rotación de claves, asegurando que las mejores prácticas de seguridad sean implementadas y mantenidas.

Con estas habilidades, los administradores están mejor preparados para manejar escenarios que requieran la protección de datos críticos, contribuyendo a un entorno más seguro y resiliente. Para profundizar en estos conceptos, recomendamos revisar los recursos adicionales proporcionados.