K8S_secrets_keyvault_kubernetes

Intégration d’Azure Keyvault avec Kubernetes

Introduction

Un des enjeux critique dans l’automatisation des chaînes de déploiement consiste notamment à pouvoir transmettre des « Secrets » de manière fiable et sécurisée au travers des différentes étapes du processus. Cela ne doit ni provoquer de rupture dans le caractère automatique du déploiement, ni risquer d’en compromettre la sécurité. Au contraire la démarche présentée ici vise tout particulièrement à fluidifier votre processus de déploiement et d’éviter l’échange de secrets critiques par des moyens susceptibles d’en compromettre la sécurité.

Ici par « Secret », nous faisons référence à une clé, un certificat, un mot de passe ou une chaîne de connexion qui revêt un caractère évidemment sensible, et qui doit pouvoir être utilisé par un ou plusieurs de nos containers. Prenons l’exemple d’un pipeline qui exécute du code Terraform pour provisionner une base de données, un bus de données et un compte de stockage, dans le cadre du déploiement d’une nouvelle instance de notre application. Il nous faut récupérer les chaînes de connexion produites à cette occasion pour permettre à nos micro-services dans Kubernetes de s’y connecter.

De plus, ces secrets doivent pouvoir être conservés de manière fiable et durable car ils seront nécessaires tout au long du cycle de vie de l’application.

Le principe présenté dans cet article consiste donc à faire le lien entre le coffre Keyvault dans Azure, la création de secrets stockés dans ce coffre, et le fait de permettre à Kubernetes d’y accéder et de les exposer aux micro-services concernés de la manière la plus naturelle.

Kubernetes fourni en standard un mécanisme natif de gestion des secrets et insiste sur la nécessité de différencier la gestion des secrets, par rapport à d’autres éléments de configuration qui ne comportent pas d’information sensible. Ainsi, Kubernetes différencie l’usage des « Secrets » de celui des « ConfigMaps ».

Il est cependant possible de recourir à des mécanismes externes pour la gestion des secrets de manière intégrée à Kubernetes.

Dans cet article nous allons nous intéresser donc plus particulièrement à l’intégration d’Azure Keyvault avec un cluser Kubernetes sur Azure (AKS) afin de lui déléguer la gestion de nos secrets.

Intégration d’Azure Keyvault dans Kubernetes

L’intégration d’Azure Keyvault repose sur l’utilisation de services complémentaires à intégrer dans Kubernetes. Il s’agit d’AAD pod identity, et du SecretStore CSI Driver. Le premier vise à attribuer une identité managée aux POD de notre Cluster Kubernetes pour leur permettre d’accéder au Service Keyvault selon des privilèges préalablement établis. Le deuxième vise à permettre à Kubernetes d’utiliser les secrets stockés dans Keyvault selon des règles prédéfinies pour nos déploiements.

Pour les étapes d’installation et de configuration initiale je vous renvoie directement à cet excellent tutoriel de Microsoft : Configurer et exécuter le fournisseur Azure Key Vault pour le pilote CSI du magasin des secrets sur Kubernetes

Je vais d’ailleurs m’appuyer sur l’exemple présenté dans ce tutoriel pour la suite de cet article et ainsi apporter quelques compléments.

Quelques précautions préalables :

  • Utilisez un nom en minuscules pour votre coffre Azure Keyvault compatible avec les exigences de Kubernetes, ainsi que pour le nom de l’identité managée utilisée par AAD Pod Identity.
  • Créez votre identité managée dans le même groupe de ressources que votre Cluster AKS

Créer des secrets dans Keyvault avec Terraform

Dans votre code terraform, prévoyez de provisionner votre coffre Azure Keyvault, de créer vos secrets et de définir les droits d’accès dédiés à l’identité managée comme indiqué dans le tutoriel mentionné précédemment.

Le provider azurerm pour Terraform permet notamment de créer les secrets dans Azure Keyvault.


resource "azurerm_key_vault_secret" "example" {
  name         = "secret2"
  value        = "test2"
  key_vault_id = azurerm_key_vault.example.id
}

Utiliser les secrets Keyvault sous la forme de fichiers dans un point de montage

Le Secret Store CSI Driver permet par défaut d’utiliser les secrets Keyvault sous la forme de fichiers dans un point de montage. Les secrets à exploiter doivent être déclarés dans le tableau d’objets comme présenté ci-dessous dans le bloc « objects ». Chacun des secrets apparaîtra sous la forme d’un fichier dont le nom repose sur l’attribut « objectName » ou « objectAlias ».

apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
kind: SecretProviderClass
metadata:
  name: azure-kv-infra-xxxx
spec:
  provider: azure
  parameters:
    usePodIdentity: "true"               # [OPTIONAL] if not provided, will default to "false". Set to "true" if using pod identities.
    useVMManagedIdentity: "false"        # [OPTIONAL] if not provided, will default to "false". Set to "true" if using managed identities.
    userAssignedIdentityID: ""            
    keyvaultName: "kv-infra-xxxx"        # [REQUIRED] the name of the key vault 
    cloudName: ""                        # [OPTIONAL] if not provided, Azure environment will default to AzurePublicCloud
    objects:  |
      array:
        - |
          objectName: secret1         # [REQUIRED] object name
                                      #     az keyvault secret list --vault-name "contosoKeyVault5"
                                      #     the above command will display a list of secret names from your key vault
          objectType: secret          # [REQUIRED] object types: secret, key, or cert
          objectVersion: ""           # [OPTIONAL] object versions, default to latest if empty
        - |
          objectName: secret2
          objectType: secret
          objectVersion: ""
        - |
          objectName: secret3
          objectType: secret
          objectVersion: ""
    tenantId: "XXXXXXXXXXXXXXXXXXXXXXXXXX"                                  # [REQUIRED] the tenant ID of the key vault

Le déploiement à partir du fichier qui suit réalise un montage à partir du volume créé selon le fichier qui précède.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-secrets-store-inline
  labels:
    aadpodidbinding: azure-pod-identity-binding-selector # The selector defined in AzureIdentityBinding in the previous step
spec:
  containers:
    - name: nginx
      image: nginx
      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: azure-kv-infra-xxxx

Les secrets apparaissent sous la forme de fichiers dans le point de montage défini dans le paramètre « mountPath »

sgautier@sgautier-laptop:~$ kubectl exec nginx-secrets-store-inline -- ls /mnt/secrets-store
secret1
secret2
secret3

Nous pouvons observer la valeur des secrets à partir de la commande qui suit :

sgautier@sgautier-laptop:~$ kubectl exec nginx-secrets-store-inline -- cat /mnt/secrets-store/secret2
test2

Utiliser les secrets Keyvault sous la forme de variables d’environnement

Il est enfin possible d’utiliser les secrets de notre Keyvault sous la forme de variables d’environnement pour nos Pods. Pour cela nous disposons d’un mécanisme conçu pour synchroniser les secrets montés au travers du Secret Store CSI Driver dans un « Secret » Kubernetes.

Cette solution peut s’avérer utile notamment pour éviter d’induire des modifications dans le code ou la configuration de votre application pour accéder à ces secrets.

Dans le fichier YAML qui suit, le bloc « secretObjects » établit les règles de synchronisation dans un « Secret » Kubernetes à partir des secrets Keyvault déclarés dans le bloc « objects ».

Il ne doit pas y avoir de « Secret » dans Kubernetes portant déjà le même nom que celui déclaré dans « secretName ». Ce « Secret » sera créé dynamiquement par Kubernetes.

apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
kind: SecretProviderClass
metadata:
  name: azure-kv-infra-xxxx
spec:
  provider: azure
  secretObjects:                                # [OPTIONAL] SecretObject defines the desired state of synced K8s secret objects
    - secretName: mysecrets                     # name of the Kubernetes Secret object
      type: Opaque                              # type of the Kubernetes Secret object e.g. Opaque, kubernetes.io/tls
      data:
        - key: SQLSERVER_CONNECTIONSTRINGS_DEFAULTCONNECTION        # data field to populate
          objectName: secret1                                       # name of the mounted content to sync.
        - key: AZURE_SERVICE_BUS_CS                                 # data field to populate
          objectName: secret2                                       # name of the mounted content to sync.
        - key: AZURE_BLOB_STORAGE_CS                                # data field to populate
          objectName: secret3
  parameters:
    usePodIdentity: "true"
    useVMManagedIdentity: "false"
    userAssignedIdentityID: ""
    keyvaultName: "kv-infra-xxxx"                                          
                                                                            
    cloudName: ""                                                           
    objects:  |
      array:
        - |
          objectName: secret1                                               
                                                                            
          objectType: secret                                                
          objectVersion: ""                                                 
        - |
          objectName: secret2
          objectType: secret
          objectVersion: ""
        - |
          objectName: secret3
          objectType: secret
          objectVersion: ""
    tenantId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

Déploiement d’un Pod Nginx qui utilise les variables d’environnement

L’exemple qui suit présente le déploiement d’un Pod Nginx qui utilise les variables d’environnement. La synchronisation des secrets sous la forme de variables d’environnement n’intervient qu’après le démarrage du Pod et le montage des secrets sous la forme de fichiers. C’est pourquoi seule l’utilisation des 2 mécanismes combinés permet d’obtenir les secrets sous la forme de variables d’environnement.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-secrets-store-inline
  labels:
    aadpodidbinding: azure-pod-identity-binding-selector # The selector defined in AzureIdentityBinding in the previous step
spec:
  containers:
    - name: nginx
      image: nginx
      volumeMounts:
        - name: secrets-store-inline
          mountPath: "/mnt/secrets-store"
          readOnly: true
      env:
        - name: SQLSERVER_CONNECTIONSTRINGS_DEFAULTCONNECTION
          valueFrom:
            secretKeyRef:
              name: mysecrets
              key: SQLSERVER_CONNECTIONSTRINGS_DEFAULTCONNECTION
        - name: AZURE_SERVICE_BUS_CS
          valueFrom:
            secretKeyRef:
              name: mysecrets
              key: AZURE_SERVICE_BUS_CS
        - name: AZURE_BLOB_STORAGE_CS
          valueFrom:
            secretKeyRef:
              name: mysecrets
              key: AZURE_BLOB_STORAGE_CS
  volumes:
    - name: secrets-store-inline
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: azure-kv-infra-xxxx

Vérification des variables d’environnement

Attention : Les variables ne seront synchronisées qu’au déploiement du pod et après montage des secrets par le principe du volume.

sgautier@sgautier-laptop:~$ kubectl exec nginx-secrets-store-inline -- printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=nginx-secrets-store-inline
SQLSERVER_CONNECTIONSTRINGS_DEFAULTCONNECTION=test1
AZURE_SERVICE_BUS_CS=test2
AZURE_BLOB_STORAGE_CS=test3
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.0.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
NGINX_VERSION=1.19.8
NJS_VERSION=0.5.2
PKG_RELEASE=1~buster
HOME=/root

Conclusion

Vous disposez maintenant d’un exemple concret et complet d’intégration d’Azure Keyvault avec Kubernetes pour diffuser vers vos applications des secrets de manière fiable et sécurisée. Pour aller plus loin sur la gestion de Clés, de Certificats selon le même principe, ou de la rotation des secrets, je vous encourage à consulter la documentation de AZURE KEY VAULT PROVIDER FOR SECRETS STORE CSI DRIVER . Enfin Digitneos se propose de vous accompagner dans les phases de conception, de déploiement, de migration et de sécurisation Kubernetes chez les principaux opérateurs de Cloud public. Digitneos accompagne notamment des éditeurs de logiciel dans leur transformation vers le Cloud et la containérisation de leurs applications.

Laisser un commentaire