Algunos artículos atrás, te hablé de cómo usar Mozilla SOPS con Azure Key Vault y Sealed Secrets de Bitnami para almacenar de forma segura tus secretos, sobre todo cuando estamos pensando en implementar GitOps para nuestros clústeres de Kubernetes. Hoy quiero contarte cómo usar Azure Key Vault Provider for Secrets Store CSI Driver, que no solo te va a permitir que tus secretos estén más seguros sino que solo van a estar presentes cuando realmente se necesiten.
Crear un clúster de prueba
Para poder probar este escenario, lo primero que necesitas es un clúster. Si todavía no tienes uno puedes crearlo de la siguiente forma, añadiendo además el add-on para este driver, azure-keyvault-secrets-provider:
# Variables
RESOURCE_GROUP="aks-ak-csi-driver"
LOCATION="westeurope"
AKS_CLUSTER_NAME="aks-csi-driver"
# Create resource group
az group create -n $RESOURCE_GROUP -l $LOCATION
# Create AKS cluster with Azure Key Vault Provider for Secrets Store CSI Driver capability
az aks create \
-n $AKS_CLUSTER_NAME \
-g $RESOURCE_GROUP \
-l $LOCATION \
--node-vm-size Standard_B4ms \
--generate-ssh-keys \
--enable-addons azure-keyvault-secrets-provider \
--enable-managed-identity
# Get AKS credentials
az aks get-credentials -n $AKS_CLUSTER_NAME -g $RESOURCE_GROUP
Para comprobar que el driver se ha instalado de manera satisfactoria puedes hacerlo utilizando este comando:
# Verify the Azure Key Vault Provider for Secrets Store CSI Driver installation
kubectl get pods -n kube-system -l 'app in (secrets-store-csi-driver, secrets-store-provider-azure)'
Crear un Azure Key Vault para los secretos
El siguiente paso es crear un Azure Key Vault que es donde guardaremos y mantendremos nuestros secretos:
# Create an Azure Key Vault
KEY_VAULT_NAME="aks-kvault"
az keyvault create \
--name $KEY_VAULT_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION
Para que nuestro clúster pueda acceder a este recurso necesitamos darle permisos sobre el mismo. En este ejemplo he utilizado managed identity durante la creación del clúster, por lo que debo recuperar el client id de este de la siguiente forma:
# Provide an identity to access the Azure Key Vault
# Ger client id
CLIENT_ID=$(az aks show -n $AKS_CLUSTER_NAME -g $RESOURCE_GROUP --query "addonProfiles.azureKeyvaultSecretsProvider.identity.clientId" -o tsv)
Y asignarle el permiso get sobre los secretos de este vault:
# Set policy to access secrets in your key vault
az keyvault set-policy -n $KEY_VAULT_NAME \
--secret-permissions get \
--spn $CLIENT_ID
Ahora ya tenemos todo listo para juegar. Vamos a ver dos ejemplos: uno para recuperar secretos desde un punto montaje y otro desde una variable de entorno.
Pod que necesita los secretos montados en el sistema de ficheros
El primer escenario podría ser que el pod necesite recuperar el secreto de un punto de montaje. Para demostrarlo, necesitamos crear un secreto en Azure Key Vault con el que hacer la prueba:
# Store a plain text called mysecret in the Key Vault
az keyvault secret set \
--vault-name $KEY_VAULT_NAME \
-n mysecret \
--value HelloWorld
Después, definimos un nuevo recurso llamado SecretProviderClass:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: retrieve-secrets-using-managed-identity
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true" # Set to true for using managed identity
userAssignedIdentityID: <CLIENT_ID> # Set the clientID of the user-assigned managed identity to use
keyvaultName: aks-kvault # Set to the name of your key vault
objects: |
array:
- |
objectName: mysecret
objectType: secret # object types: secret, key, or cert
objectVersion: "" # [OPTIONAL] object versions, default to latest if empty
tenantId: <TENANT_ID> # The tenant ID of the key vault
Como ves, en este debes indicar el client id recuperado anteriormente de la identidad manejada, el nombre del Azure Key Vault (en mi caso aks-kvault), los objetos de este que queráis recuperar (en este caso mysecret), y el tenant id.
Una vez que tienes esto ya puedes generar un pod/despliegue que utilice el mismo de la siguiente manera:
kind: Pod
apiVersion: v1
metadata:
name: busybox-secrets-mounted
spec:
containers:
- name: busybox
image: k8s.gcr.io/e2e-test-images/busybox:1.29-1
command:
- "/bin/sleep"
- "10000"
volumeMounts:
- name: secrets-in-az-keyvault
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets-in-az-keyvault
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "retrieve-secrets-using-managed-identity"
Aplica estos dos manifiestos:
# Create a SecretProviderClass with the objects you want to retrieve
kubectl apply -f secret-provider-class.yaml
# Create a pod that uses the SecretProviderClass
kubectl apply -f pod-secret-as-mount.yaml
Para comprobar que el secreto ha sido montado correctamente en la ubicación indicada en el pod puedes hacerlo de la siguiente forma:
# Verify the secret is available
kubectl exec busybox-secrets-mounted -- ls /mnt/secrets-store/
# Print the content
kubectl exec busybox-secrets-mounted -- cat /mnt/secrets-store/mysecret
El resultado que obtendrías debería de ser el siguiente:
Pod que monta los secretos en variables de entorno
El otro escenario que te puedes encontrar es que el secreto se monte en variables de entorno dentro del pod. Para ello vamos a crear otro secreto en Azure Key Vault:
# Create another secret
az keyvault secret set \
--vault-name $KEY_VAULT_NAME \
-n username \
--value gisela
Para este caso, voy a crear un SecretProviderClass diferente (aunque podría estar todo en el mismo, pero para que quede claro qué es lo que necesitas):
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: demo-secret-as-variable
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true" # Set to true for using managed identity
userAssignedIdentityID: <CLIENT_ID> # Set the clientID of the user-assigned managed identity to use
keyvaultName: aks-kvault # Set to the name of your key vault
tenantId: <TENANT_ID> # The tenant ID of the key vault
objects: |
array:
- |
objectName: username
objectAlias: username
objectType: secret # object types: secret, key, or cert
objectVersion: "" # [OPTIONAL] object versions, default to latest if empty
secretObjects: # [OPTIONAL] SecretObjects defines the desired state of synced Kubernetes secret objects
- secretName: credential # name of the Kubernetes secret object
type: Opaque # type of Kubernetes secret object (for example, Opaque, kubernetes.io/tls)
data:
- key: user # data field to populate
objectName: username # this could be the object name or the object alias
En este caso, además de todo lo mostrado en el anterior, hay un apartado adicional llamado secretObjects que hará que se generen secretos en nuestro clúster. En este ejemplo se llamará credential y tendrá un solo valor con la key user y como valor el recuperado del objeto de Azure Key Vault con el nombre username.
Para utilizarlo en un pod/despliegue lo hacemos de la siguiente manera:
kind: Pod
apiVersion: v1
metadata:
name: busybox-secret-as-variable
spec:
containers:
- name: busybox
image: k8s.gcr.io/e2e-test-images/busybox:1.29-1
command:
- "/bin/sleep"
- "10000"
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: credential
key: user
volumeMounts:
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: demo-secret-as-variable
En este caso he creado una variable de entorno llamada SECRET_USERNAME que referencia al secreto llamado credential y a la key user. Por otro lado, utilizo el apartado volumeMounts para referenciar el último SecretProviderClass creado.
Si aplico la clase:
# Apply the secret class provider
kubectl apply -f secret-provider-class-env.yaml
y seguidamente compruebo los secretos, veré que el que necesitaré para el pod todavía no está:
# Check if the secret is available
kubectl get secret
Si ahora aplico el manifiesto del pod:
# Assign the secret to a variable
kubectl apply -f pod-secret-as-variable.yaml
Y compruebas de nuevo los secretos verás que el mismo ya aparece. Además puedes comprobar que devuelve el valor esperado, que en mi ejemplo es gisela, de esta forma:
# Check the content of the secret
kubectl get secret credential -o jsonpath="{.data.user}" | base64 --decode
También puedo recuperar la variable de entorno para comprobar que efectivamente el valor se ha montado en la misma correctamente:
# Check environment variable in the pod
kubectl exec busybox-secret-as-variable -- env | grep SECRET_USERNAME
El código de estos ejemplos los tienes en mi GitHub.
¡Saludos!