Proteger secretos con Mozilla SOPS y Azure Key Vault y descifrarlos desde Flux CD

Cuando te adentras en el mundo del GitOps una de las primeras preocupaciones que tendrás es cómo hacer que tus secretos estén lo más seguros posibles. Existen varias herramientas para hacer esto posible pero quizás Mozilla SOPS es una de la más extendidas. Esta herramienta nos permite cifrar y descifrar archivos con soporte para YAML, JSON, .ENV e incluso binarios, además de integrarse fácilmente con diferentes KMS. En este artículo quiero hacerte una breve introducción a esta y a cómo puedes integrarla con AKS, Azure Key Vault y Flux CD.

Cómo funciona Mozilla SOPS

Para trabajar con Mozilla SOPS, lo primero que necesitas es instalarte la herramienta en tu local. Además, para que entiendas cómo funciona esta vamos a usar también otra llamada GNUPG, que nos va a facilitar la generación de claves criptográficas que usaremos para el cifrado. En el caso de MacOs puedes instalar ambas fácilmente a través de Homebrew:

brew install gnupg sops

En su repositorio de GitHub puedes encontrar las releases para los diferentes sistemas operativos.

Para ponernos en situación, imagínate que tienes el siguiente archivo plain.env con las siguientes credenciales:

username=gis
password=Passw0rd

Lo ideal sería que este contenido o bien se recupere en tiempo de ejecución del flujo que hace uso de ellas o bien mantenerlo cifrado. Para esto segundo, lo primero que necesitas hacer es generar una clave con la que cifrarlo. Para ello usamos la herramient gnugp que acabamos de instalar:

#Genereate a GPG key
gpg --batch --full-generate-key <<EOF
%no-protection
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Expire-Date: 0
Name-Real: test-key
EOF

Ahora que ya tenemos una clave generada vamos a usar la misma con Mozilla SOPS para cifrar nuestro archivo plain.env de la siguiente forma:

  1. Recuperamos la clave pública:
KEY=$(gpg --list-keys "test-key" | grep pub -A 1 | grep -v pub)

Esta tendrá un aspecto como el siguiente:

2F080B0EB5B1A49C5BB72953CBD1E690A2C08255

2. Usamos el comando SOPS para cifrar el archivo:

#Encrypt the file
sops -pgp $KEY -e plain.env > encrypted.env

Los parámetros que recibe dicho comando son: la clave que queremos usar para el cifrado, -e significa encrypt, el nombre del archivo de origen y el de destino. El resultado del cifrado será parecido al siguiente:

username=ENC[AES256_GCM,data:gPXg,iv:6LGMSZm/SgPXg1/kOdpkPp9JjNIPZCoYLBe6delOzLI=,tag:bQI6GuTVGwQv0Hldvg8eog==,type:str]
password=ENC[AES256_GCM,data:56Q/zZL8H/o=,iv:QZGFWTwpWz7W4g4hVmubIBNH81FodCPZUmhJBzlTGBg=,tag:E5XvOiGvywTE+GwW3onM4w==,type:str]
sops_lastmodified=2021-12-31T16:00:35Z
sops_mac=ENC[AES256_GCM,data:eUZ/+NN+Fh9gaWyUnj+xJ4h93I0PFNPd4C5Tf1yaxRsNzpLUjfXYpaN38Yc3SZDoAYaB2Mxc68pghWhFxQlLIWCMJf7Za+BYB+pRP2pzilEmr5vGEaGdPGi9BeJZu6DE9V1zcZXunTm8uhWjNSiuFF8Zt/0ScYYKMXNYkXixW8s=,iv:udX6w2Zl51zSjssjOXnV9SsZqnr/5F00veanEMdn4Cc=,tag:Blmb4NETLK1G/YXNu3dm7A==,type:str]
sops_version=3.7.1
sops_pgp__list_0__map_fp=763BB095A2D76B5B9E98532BA144A9954DBF12A0
sops_pgp__list_0__map_created_at=2021-12-31T16:00:34Z
sops_pgp__list_0__map_enc=-----BEGIN PGP MESSAGE-----\n\nhQIMAwEWap6cFU8FAQ/+LdIrGcnu1m2Uk1N9cvgAoQETgr1IowN4pELVNGZHDZsU\nWimlCa3WzUfhdLPpM8OCvY3VHhTh3QYiU9m+T+v7Oe3d6uEtAFWxk3XwlDthSKpW\nkGoHKpyg9jpH52N3h3g7iCRck3NxYBPa0QBD8DhybPFa2snvJXU7Mtn9LwcRgJ8W\n9M+m78iPT071qCTy9zrEpXmjRFlLZhn91u6onx28cc8ogkxaxbU9VoomePe/89e+\nkkcbZRCulif5ASJ+twPx4jKPFd/xyFQVFIrMsGb48BM8ke+vlMKk70iNMNQx/F8m\n7/KetwMGhDIBBMTCL7JjWezxyskR0FOut5N5TN8GxY57FHvCx0m/yKxzH6WIN4FT\nwMFeyFis3qXyKIcf/1FMxbD02E5PK+1RpuB3REv8xbCLTFHJp2P8A1LHa6Iydmar\nPffimRZuuULAfRo9Jj9WxOQTbFykiXQmXB/o2F8whtLyNb6HUVWH53FL554R0fQA\n4rGtrG/8nth0fZ+0bAAFSm83/9xJOvOZXfSvJadOEdNQO0QZFWAw/bP490MgMUNY\n7lH2BFeta6EUgU28RKKztoJac9Wjb9Hd3qH1kk/NOnqlH2sIlrWLti/bTO0vf3xH\nnrgFpEvUOjWbPhV4JRKnVre+6WAoRu24g8gfmvnQMgnClxmvJWg6NazQeKeUxU7U\naAEJAhCCCASWZfvXXJJC5VSTaaMQEyxTBfsaRtJowA/3ZCDPYRaqvd1wz+u3e1pa\ny5MWLSF9oyv43eVbqSEaWRxmmASydU+AmkgGzSePJ8AAhtSALR7a2cYHFURn2j93\nbynUcv0HWNYU\n=rCz9\n-----END PGP MESSAGE-----\n
sops_unencrypted_suffix=_unencrypted

Lo guay de esta herramienta es que además podemos añadir ciertas reglas para que solo se cifre parte del archivo, y no en su totalidad, lo cual es necesario para lo que veremos después con Flux CD. Por ejemplo, en el caso de un YAML como este:

credentials:
  username: 'admin'
  password: 'passw0rd'

Podríamos decirle de una forma sencilla que solo queremos cifrar el campo password:

#Encrypt password only
sops -pgp $KEY -e --encrypted-regex 'password' plain.yaml > encrypted-password.yaml

y el resultado sería algo así:

credentials:
    username: admin
    password: ENC[AES256_GCM,data:aTnOmag=,iv:yo7MNih8s4lEbx8MzrtINpPuGmwyPQfD5+VXCf1TQus=,tag:mCSqFp0VD/hrauUJBiNkMw==,type:str]
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age: []
    lastmodified: "2021-12-31T16:12:00Z"
    mac: ENC[AES256_GCM,data:/OlVlXji7csiTVmEnYGUpN9PJl4PgKQsUk+PIVxremxOqvP4xOkmM9MONQyE7Pxslo0rD/p3c1aEnoS2CG7jncJY76C22xaxCSy1nUTl7KFfC6sFSKnyZBSMrP9i62mWWUWlRsbDcj35iyeYFZDmZPX0kdo3SPaa9b/ML6R0X1c=,iv:bdrMvK2F6nmn5HNqgPRan1kZShLmkXN+dC5jnBhtNPs=,tag:FEasQYnZa3nqvVxJSxGYRg==,type:str]
    pgp:
        - created_at: "2021-12-31T16:11:59Z"
          enc: |
            -----BEGIN PGP MESSAGE-----
[...]

Cómo ves, súper sencillo de utilizar. De hecho, en este último ejemplo puedes ver que es posible integrarlo con varios KMS como el de Google, Amazon o Azure. Ahora vamos a ver cómo configurar esta herramienta con AKS, Flux CD y Azure Key Vault.

Configurar Flux CD en un clúster de AKS

Para poder probar este escenario, lo primero que vamos a hacer es configurar un clúster de AKS con Flux CD, como ya hice en el artículo donde te presenté esta herramienta:

#Variables for Azure
RESOURCE_GROUP="fluxcd-demo"
CLUSTER_NAME="fluxcd-aks"
KEY_VAULT_NAME="sops-keys"
LOCATION="northeurope"
#Create a resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
#Create AKS cluster
az aks create --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --node-count 1 --generate-ssh-keys
#Get AKS credentials
az aks get-credentials --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME
# Check the cluster meets the requirements
flux check --pre
# Your GitHub PAT
export GITHUB_TOKEN=<YOUR_GITHUB_PAT>
export GITHUB_USER=<YOUR_GITHUB_USERNAME>
# Initialize Flux
flux bootstrap github \
  --owner=$GITHUB_USER \
  --repository=fluxcd-sops-demo \
  --branch=main \
  --path=./clusters/$CLUSTER_NAME \
  --personal
# Check that the cluster is ready
flux check
# Clone the repo that Flux just created
git clone https://github.com/$GITHUB_USER/fluxcd-sops-demo.git
# Go to the repo
cd fluxcd-sops-demo

Por otro lado, para ilustrar este ejemplo con un caso práctico, voy a utilizar el entorno de WordPress que te mostré en el artículo sobre Kustomize. Lo primero que hago es crear un archivo de configuración para Flux CD, utilizando su herramienta con los siguientes parámetros:

flux create kustomization wordpress \
--namespace=flux-system \
--source=flux-system \
--path="./manifests" \
--prune=true \
--interval=1m \
--export > ./clusters/$CLUSTER_NAME/wordpress-kustomization.yaml

Nota: en este ejemplo estoy usando el mismo repositorio por simplicidad, pero podría ser otro totalmente independiente.

Después creo una carpeta llamada manifests, a la que hace mención esta configuración:

mkdir manifests

y dentro de esta creo el Namespace:

cat <<EOF > manifests/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: wordpress
EOF

El despliegue para el MySQL que necesita WordPress:

cat <<EOF > ./manifests/mysql.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  namespace: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
        - image: mysql:5.6
          name: mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: wp_password
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: mysql-storage
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-storage
          emptyDir: {}
EOF

El servicio asociado a este:

cat <<EOF > ./manifests/mysql-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  namespace: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
EOF

El despliegue para el propio WordPress:

cat <<EOF > ./manifests/wordpress.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  replicas: 2
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
        - image: wordpress:4.8-apache
          name: wordpress
          env:
            - name: WORDPRESS_DB_HOST
              value: wordpress-mysql
            - name: WORDPRESS_DB_PASSWORD
              value: wp_password
          ports:
            - containerPort: 80
              name: wordpress
          volumeMounts:
            - name: wordpress-storage
              mountPath: /var/www/html
      volumes:
        - name: wordpress-storage
          emptyDir: {}
EOF

Y el servicio asociado al mismo con el tipo LoadBalancer para poder acceder desde fuera a la instalación, y comprobar así que todo funciona correctamente:

cat <<EOF > ./manifests/wordpress-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  namespace: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
EOF

Ahora haz commit de todos estos archivos y comprueba que el despliegue en tu clúster ha sido satisfactorio:

#Commit all
git add .
git commit -m "Add WordPress demo"
git push origin main
#Check the deployment
flux get kustomizations -w
kubectl get all -n wordpress

Configurar Azure AD Pod Identity y Azure Key Vault

Para que nuestro clúster pueda acceder a Azure Key Vault podemos hacerlo utilizando un Service Principal o a través de identidades manejadas. En el caso de Kubernetes podemos hacer uso de esto último a través de Azure AD Pod Identity, lo cual es mucho más cómodo. Para configurarlo puedes seguir estos pasos:

#Configure AAD Pod Identity
RESOURCE_GROUP_ID=$(az group show --name $RESOURCE_GROUP --query id -o tsv)
AKS_RESOURCE_GROUP=$(az aks show -g $RESOURCE_GROUP -n $CLUSTER_NAME -o tsv --query nodeResourceGroup)
AKS_RESOURCE_GROUP_ID=$(az group show -n $AKS_RESOURCE_GROUP -o tsv --query id)
KUBELET_CLIENT_ID=$(az aks show -g $RESOURCE_GROUP -n $CLUSTER_NAME -o tsv --query identityProfile.kubeletidentity.clientId)
# Your cluster will need the correct role assignment configuration to perform Azure-related operations 
# such as assigning and un-assigning the identity on the underlying VM/VMSS.
#https://azure.github.io/aad-pod-identity/docs/getting-started/role-assignment/
az role assignment create --role "Virtual Machine Contributor" --assignee $KUBELET_CLIENT_ID --scope $AKS_RESOURCE_GROUP_ID
az role assignment create --role "Managed Identity Operator" --assignee $KUBELET_CLIENT_ID --scope $RESOURCE_GROUP_ID
#Create a managed identity
IDENTITY_NAME="fluxcd-aks-identity"
az identity create -n $IDENTITY_NAME -g $RESOURCE_GROUP -l $LOCATION
#Obtaine the client id, client secret and resource id
CLIENT_ID=$(az identity show -n $IDENTITY_NAME -g $RESOURCE_GROUP -o tsv --query "clientId")
OBJECT_ID=$(az identity show -n $IDENTITY_NAME -g $RESOURCE_GROUP -o tsv --query "principalId")
RESOURCE_ID=$(az identity show -n $IDENTITY_NAME -g $RESOURCE_GROUP -o tsv --query "id")

Con ello recuperamos los ID necesarios para asignarle al clúster los roles que necesita para poder asignar la identidad a las VMs que corren por debajo y se crea una identidad de la que recuperamos también sus valores. Ahroa creamos un recurso de Azure Key Vault:

#Create a key vault
az keyvault create --name $KEY_VAULT_NAME --resource-group $RESOURCE_GROUP --location $LOCATION

Y generamos una clave que utilizaremos más tarde para cifrar nuestros secretos, de la misma forma que lo hicimos con GPG:

#Create a key in the key vault
az keyvault key create --vault-name $KEY_VAULT_NAME --name sops-key --protection software --ops encrypt decrypt

Por último le asignamos los permisos de cifrado y descifrado a la identidad que usará el pod de kustomization-controller, para poder hacer dichas operaciones cuando se le requieran:

#Add permissions to the identity
az keyvault set-policy --name $KEY_VAULT_NAME --object-id $OBJECT_ID --key-permissions encrypt decrypt

Para seguir con la filosofía de GitOps, voy a registrar el chart de AAD Pod Identity para instalarlo en el clúster:

# Add Azure AD Pod Identity Helm chart
cat <<EOF > ./manifests/azure-ad-pod-identity.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: aad-pod-identity
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
  name: aad-pod-identity
  namespace: aad-pod-identity
spec:
  url: https://raw.githubusercontent.com/Azure/aad-pod-identity/master/charts
  interval: 10m
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: aad-pod-identity
  namespace: aad-pod-identity
spec:
  interval: 5m
  chart:
    spec:
      chart: aad-pod-identity
      version: 4.0.0
      sourceRef:
        kind: HelmRepository
        name: aad-pod-identity
        namespace: aad-pod-identity
      interval: 1m
  values:
    nmi:
      allowNetworkPluginKubenet: true
EOF

Configuro la identidad para aquel pod que tenga el selector sops-akv-decryptor:

#Configure in-cluster secrets decryption
cat > ./clusters/$CLUSTER_NAME/sops-identity.yaml <<EOF
---
apiVersion: aadpodidentity.k8s.io/v1
kind: AzureIdentity
metadata:
  name: sops-akv-decryptor
  namespace: flux-system
spec:
  clientID: $CLIENT_ID
  resourceID: $RESOURCE_ID
  type: 0 # user-managed identity
---
apiVersion: aadpodidentity.k8s.io/v1
kind: AzureIdentityBinding
metadata:
  name: sops-akv-decryptor-binding
  namespace: flux-system
spec:
  azureIdentity: sops-akv-decryptor
  selector: sops-akv-decryptor  # kustomize-controller label will match this name
EOF

Creo un patch para el despliegue de kustomize-controller:

#Add a file to patch the Flux system kustomize controller
cat > ./clusters/$CLUSTER_NAME/flux-system/sops-kustomize-patch.yaml <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kustomize-controller
  namespace: flux-system
spec:
  template:
    metadata:
      labels:
        aadpodidbinding: sops-akv-decryptor  # match the AzureIdentityBinding selector
    spec:
      containers:
      - name: manager
        env:
        - name: AZURE_AUTH_METHOD
          value: msi
EOF

Y por último modifico el archivo kustomization.yaml de flux-system para añadir este patch:

#Update kustomization for flux-system
cat > ./clusters/$CLUSTER_NAME/flux-system/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml
patchesStrategicMerge:
  - sops-kustomize-patch.yaml
EOF

Guardo los cambios en el repo y espero a que se apliquen en el clúster:

#Commit all
git add .
git commit -m "Add Azure AD Pod Identity Helm chart"
git push origin main
#Check the deployment
flux get helmreleases -A -w
kubectl get pod -n aad-pod-identity

Ahora ya tenemos nuestro clúster y Flux CD correctamente configurados para desplegar secretos que están previamente cifrados con Azure Key Vault y poder descifrarlos antes de su despliegue por Flux CD.

Desplegar un Secret cifrado con Azure Key Vault

Lo úlltimo que nos queda es pasar a la acción con una demo que nos permita validar que todo funciona como esperamos. Antes de ello necesitamos dar permisos sobre el usuario logado a través de Azure CLI:

# Assign permissions to my user
SIGNED_USER=$(az ad signed-in-user show --query objectId -o tsv)
az keyvault set-policy --name $KEY_VAULT_NAME --object-id $SIGNED_USER --key-permissions list encrypt decrypt get

Ahora seremos capaces de cifrar y descifrar con el recurso de Azure Key Vault que generamos previamente. Un dato que es importante tener en cuenta es que Flux CD no soporta el cifrado total del secreto, sino que debemos cifrar solamente el apartado data o stringData, tal y como se advierte en su documentación. Podríamos hacer uso de expresiones regulares, junto con el comando sops, cada vez que quisieramos cifrar un nuevo secreto o, mejor todavía, podemos configurar sops a través del archivo .sops.yaml de la siguiente manera:

#Create SOPS configuration
cat > .sops.yaml <<EOF
creation_rules:
  - path_regex: .*.yaml
    encrypted_regex: ^(data|stringData)$
    azure_keyvault: $(az keyvault key show --vault-name $KEY_VAULT_NAME --name sops-key --query key.kid -o tsv)
EOF

De esta forma le estamos diciendo a qué tipos de archivos debe aplicar esta regla, qué propiedades son las que debe cifrar, y cuál es la clave en cuestión que queremos usar. Fácil ¿no?

Con la configuración en su sitio creamos un secreto como el siguiente:

# Create a secret
cat > ./secret.yaml <<EOF
---
apiVersion: v1
kind: Secret
metadata:
  name: wordpress-credentials
  namespace: wordpress
type: Opaque
stringData:
  host: wordpress-mysql
  password: passw0rd
EOF

Y ahora, de la misma forma que hicimos al inicio de este artículo (pero con menos parámetros), ciframos el archivo y eliminamos el que tiene las credenciales en plano, para no subirlo al repositorio, que es el objetivo de todo esto 😉:

# Encrypt secret
sops --encrypt secret.yaml > ./manifests/secret.enc.yaml
# Delete plaintext secret
rm secret.yaml

Si ahora comprobaramos el resultado tendríamos algo como lo siguiente:

apiVersion: v1
kind: Secret
metadata:
    name: wordpress-credentials
    namespace: wordpress
type: Opaque
stringData:
    host: ENC[AES256_GCM,data:aCQX6O3sZxlHS5up4yCY,iv:tTRPROZTTeo4ffMpLq7UpffiCEB/vdYVWGPQWPpvI7g=,tag:fOixFKqoPvSvHflW3fSNcQ==,type:str]
    password: ENC[AES256_GCM,data:tVOSzJkqQc4=,iv:rQzE8/XKXB2TOr420BCwl9snWwhddHIWZ8dTPwb4DXY=,tag:WbmK4RrJJjSE5jTsC5NqqQ==,type:str]
sops:
    kms: []
    gcp_kms: []
    azure_kv:
        - vault_url: https://sops-keys.vault.azure.net
          name: sops-key
          version: 91094efdfd414b23a0921748438c9d6a
          created_at: "2022-01-04T18:08:30Z"
          enc: A_SjqMpgDc6nfcCl95req_5H-uiM9DmuBqgpAZzZhib6qEyHJV8QqTkHfNBMRx_cSfNJrOqciptyOok0c2nhtMBcCwddHGhx3riOJd3aw3428e9lk2CmTKZUVnOqj3_KthFmj9zdEPmYdm8qvdVam8dp6PQN6HSA5aU1ZDGg_NJtj5eZ8t8m9Uo65f-o8V8DR_imVK_SfrHtAspjpnjy7aJ--2yC0u2OCCrICBfuRqPs_DvZmT2G4zA-wrPcAKA9UvCqh55YiD1vd736HZVG6In4qFH8UZisjqUE8a4Bqdd0Oe-58B7aU2JMqVIZ5FWg1_O7jBFjDNvV0b8odJrHig
    hc_vault: []
    age: []
    lastmodified: "2022-01-04T18:08:30Z"
    mac: ENC[AES256_GCM,data:xcfDepit828BnjMY/5ThRFsw/k+0Or1RIXUumvjCpqlSHP1UcyMO9g7Ua7hYQWngnofU03eoPAf7aPfGDcrJqeCm3ojK0Ys51FJaFKkLOxUg/0nZ5YmSNlGErpiBfWyPu21oa7Agmff80s5z9a7Fre7zAVa/FKP7nM4CEpO5UPw=,iv:RHUCPe2k8Ups7dr12XuQmtdcVobrstM7CWOEGtRwQa8=,tag:dD+AN6gvQ2IbWxptW+311A==,type:str]
    pgp: []
    encrypted_regex: ^(data|stringData)$
    version: 3.7.1

Haz commit de estos cambios:

# Commit changes
git add .
git commit -m "Add encrypted secret"
git push origin main

Sin embargo, llegados a este punto te darás cuenta de que el proceso todavía no funcionará, lo cual puedes comprobarlo viendo el resultado de este comando:

flux get kustomizations --watch

Aparecerá algo como que no es posible descifrar el Secreto porque está cifrado con SOPS y falta configuración:

wordpress-credentials is SOPS encryted

Para solucionarlo, debemos modificar la configuración del archivo wordpress-kustomization.yaml indicándole como proveedor SOPS:

flux create kustomization wordpress \
--namespace=flux-system \
--source=flux-system \
--path="./manifests" \
--prune=true \
--interval=1m \
--decryption-provider=sops \
--export > ./clusters/$CLUSTER_NAME/wordpress-kustomization.yaml

Vuelve a aplicar los cambios en el repo y verás que ahora todo ha funcionado correctamente y el secreto está en su sitio con el contenido descifrado por Flux CD:

# Check the deployment
flux get kustomizations --watch
# Check if the secret is there
kubectl get secret -n wordpress
#See secret in plaintext
kubectl get secret wordpress-credentials -n wordpress  --template={{.data.password}} | base64 --decode

¡Perfecto! Pues ya tenemos todo correctamente funcionado, vamos a hacer la comprobación final, que es integrarlo con el despliegue de WordPress.

Usar el secreto cifrado para el despliegue con WordPress

Para cerrar el ciclo, lo único que nos queda es generar un par de patches: uno para el despliegue de MySQL, para la variable de entorno que define la contraseña de la base de datos:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  namespace: wordpress
spec:
  template:
    spec:      
      containers:
        - name: mysql
          env:            
            - name: MYSQL_ROOT_PASSWORD
              value: ""
              valueFrom: 
                secretKeyRef:
                  name: wordpress-credentials
                  key: password

y este otro para el despliegue de WordPress, donde se hace mención a esa misma contraseña y al nombre del host:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: wordpress
spec:
  template:
    spec:      
      containers:
        - name: wordpress
          env: 
            - name: WORDPRESS_DB_HOST
              value: ""
              valueFrom: 
                secretKeyRef:
                  name: wordpress-credentials
                  key: host       
            - name: WORDPRESS_DB_PASSWORD
              value: ""
              valueFrom: 
                secretKeyRef:
                  name: wordpress-credentials
                  key: password

Lo único que hago es «vaciar» el contenido de value, que es lo que se usaba inicialmente, para que tome como valor el referenciado en mi secreto wordpress-credentials. Si Flux CD lo ha descifrado correctamente podré integrarlo sin problemas en ambos despliegues.

Para que estos cambios sean aplicados correctamente, en la carpeta manifests añado un nuevo archivo kustomization.yaml para indicar cuáles son recursos y cuáles son mis patches dentro de este directorio:

#Use the secret in the wordpress kustomization
cat > ./manifests/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - namespace.yaml
  - secret.enc.yaml
  - mysql.yaml
  - mysql-svc.yaml
  - wordpress.yaml
  - wordpress-svc.yaml
  - azure-ad-pod-identity.yaml
patchesStrategicMerge:
  - mysql-patch.yaml
  - wordpress-patch.yaml
EOF

Hago commit de los cambios:

# Commit changes
git add .
git commit -m "Add patches and kustomization.yaml"
git push origin main

y una vez finalice podré comprobar que mi WordPress está funcionando correctamente con un secreto cifrado en mi repositorio con Azure Key Vault y que Flux CD es capaz de descifrar.

¡Saludos!