Renforcer la sécurité des microservices avec l’authentification Cilium et SPIFFE

Le sujet de Cilium a déjà été abordé dans des articles précédents, mais de nombreuses facettes de ce CNI restent à explorer.
Dans cet article, nous proposons de découvrir la fonctionnalité qui permet l’authentification des applications entre elles.
Notre agenda sera le suivant :
- Comprendre la fonctionnalité d’authentification de Cilium
- Mettre en œuvre l’authentification pour des applications dans un cluster AKS
Commençons !
Pourquoi l’authentification est-elle nécessaire ?
La sécurité dans les microservices représente un enjeu majeur. Le nombre d’outils et de solutions disponibles à ce sujet, ainsi que l’engouement pour les certifications associées, en témoignent.
Dans un environnement Kubernetes, il est possible d’analyser le réseau et de mettre en place une approche Zero Trust, en autorisant uniquement les protocoles et ports nécessaires.
Pour ce faire, il est possible de s’appuyer sur des Network Policies. Par exemple, une Network Policy « Deny All Ingress » appliquée à un namespace :
La network policy ci-dessous autorise uniquement le trafic vers les pods avec le label app=userprofile, sur le port 8082, en provenance du namespace portant les labels tier=front et app=tripinsights.

Pourtant, cela peut s’avérer insuffisant, en plus d’être difficile à gérer, notamment en raison de l’absence d’un Control Plane central. C’est l’un des paradoxes de la sécurité distribuée.
À ce sujet, le concept de Service Mesh représente une initiative visant à instaurer un Control Plane central. De plus, un certain nombre de standards ont été définis pour renforcer la sécurité, notamment à travers des objets de contrôle du trafic.

Du point de vue de Cilium, l’examen se concentre sur ses fonctionnalités de Service Mesh, en particulier la sécurité basée sur l’identité.
Il est avantageux d’intégrer ce type de fonctionnalité au niveau de l’infrastructure, car l’inclusion de l’authentification entre les différentes couches d’une application, surtout dans une architecture de microservices, n’est pas toujours réalisable.
Regardons à présent l’authentification Cilium
Ce qui se cache sous la fonctionnalité d’authentification de Cilium
L’authentification est indissociable de l’identité.
Comme le suggère la spécification SMI mentionnée précédemment et détaillée dans la documentation de Cilium, la technologie sous-jacente à cette fonctionnalité est SPIFFE, acronyme de Secure Production Identity Framework for Everyone. Ce projet est Graduated project à la CNCF depuis août 2022, et ses spécifications sont disponibles sur un Github dédié.
Cet article présente un aperçu des standards SPIFFE, tel qu’indiqué dans le GitHub cité :
- L’ID SPIFFE, une chaîne structurée sous forme d’URI, servant de « nom » pour une entité.
- Le SVID (SPIFFE Verifiable Identity Document), un document portant l’ID SPIFFE, équivalent d’un passeport.
- Le Workload API, une méthode permettant aux workloads d’obtenir leurs SVIDs, souvent exposée localement via un Unix Domain socket.
Le trust domain, un namespace d’identité porté par une autorité disposant des clés de chiffrement, est également à mentionner. Ces clés sont utilisées pour crypter toutes les identités au sein du trust domain.
Pour plus de détails, les spécifications SPIFFE sont accessibles sur le GitHub dédié.
Une fois ces spécifications rapidement définies, il convient d’aborder leur mise en œuvre. À cet effet, Cilium repose sur un autre projet gradué de la CNCF, SPIRE, qui constitue une implémentation « Production ready » des spécifications SPIFFE.
Cilium met en place un serveur central SPIRE, agissant en tant que trust domain. Ce serveur fonctionne avec des agents SPIRE, un par nœud, qui obtiennent leur identité depuis le serveur SPIRE et valident ensuite les requêtes d’identité des workloads.
Pour comprendre le fonctionnement de SPIRE, il est nécessaire de définir certains concepts.
L’enregistrement du workload est l’action par laquelle SPIRE devient capable d’identifier le workload en question. Il permet au serveur SPIRE d’identifier le workload et de lui attribuer Id SPIFFE.
SPIRE effectue une attestation du workload en recueillant des attributs provenant du workload ainsi que du nœud sur lequel l’agent SPIRE est déployé. Cette attestation est réalisée par des éléments logiciels appelés attestators. Dans le cadre de Kubernetes, il existe un Kubernetes attestator et un node attestator de l’agent SPIRE. Sur Azure, le node attestator repose sur les identités managées associées aux Vms (machines virtuelles).
L’attestation de node suit les étapes suivantes :
- L’attestator de l’agent (ou de node) interroge la plateforme pour obtenir une preuve d’identité et transmet cette information à l’agent.
- L’agent transmet la preuve d’identité au serveur, qui à son tour la transmet à son node attestator.
- Le node attestator valide la preuve d’identité en appelant l’API de la plateforme, utilisant les informations obtenues à l’étape 2. Il crée également l’ID SPIFFE pour l’agent et le renvoie au serveur.
- Le serveur renvoie le SVID généré à l’agent du nœud.

De manière similaire, un workload qui requiert une identité suit les étapes suivantes :
- Appel de la Workload API exposée par l’agent pour demander un SVID.
- L’agent interroge le workload attestator, en lui fournissant les informations relatives au workload.
- Le workload attestator récupère des informations supplémentaires sur le workload en interrogeant des composants de la plateforme, tels que kubelet.
- L’attestator renvoie les informations récupérées à l’agent sous forme de selectors.
- L’agent détermine l’identité du workload en comparant les selectors obtenus aux enregistrements existants, puis renvoie le bon SVID au workload.

Davantage de détails peuvent être trouvés dans les spécifications de SPIFFE, mais cet article s’arrête ici. Passons à présent à l’implémentation de cette fonctionnalité sur un cluster AKS.
Préparation de l’environnement
Les tests seront réalisés sur un cluster AKS simple, situé dans son propre VNet et disposant d’un pool de nœuds unique. Le cluster est configuré en BYOCNI avec Cilium.
L’activation de l’authentification requiert certains paramètres spécifiques lors de l’installation du CNI. Voici la configuration du Helm Chart utilisée dans cet environnement de test.


Une fois l’installation terminée, la commande cilium status génère un output comme celui-ci :

Il est également possible de voir les objets Kubernetes relatifs à l’authentification :
Un déploiement pour le serveur SPIRE
Un daemonset pour les agents, sur chaque nœud

Avant de commencer nos tests, examinons le serveur SPIRE. La documentation de Cilium relative à l’authentification fournit quelques commandes.
Tout d’abord, vérifions l’état de santé du serveur.

Ensuite, examinons les agents et leur statut d’attestation. Dans notre cas, nous avons trois agents pour notre pool de nœuds, qui comprend trois nœuds.

En examinant les identités SPIFFE :

Passons maintenant aux tests d’authentification des workloads.
Utilisation de l’authentification mutuelle de Cilium
Pour simplifier les choses, nous allons créer quelques déploiements Nginx.
Une application cible :

Ainsi qu’une application cliente :

En examinant les endpoints Cilium, il est possible d’observer les associations d’identités :

Le champ SECURITY IDENTITY permet de vérifier l’identité SPIFFE sur le serveur SPIFFE. Le filtre jsonpath ‘{.items[0].status.identity.id}‘ sera utilisé pour extraire les informations et les stocker dans des variables.

Sur le pod du serveur SPIRE, il est possible de vérifier la correspondance des identités de chaque workload.

Et directement en tant qu’objets Cilium. Le résultat ci-dessous affiche les détails de l’identité de l’application client1.

La correspondance entre le champ « Name » de l’identité Cilium et le SPIFFE ID spiffe://spiffe.cilium/identity/76513, obtenu sur le serveur SPIRE, est notable.
Pour forcer l’authentification mutuelle, il est possible d’utiliser une Cilium Network Policy en spécifiant l’argument d’authentification.
Cela peut être fait de manière simple avec une Cilium Network Policy, en spécifiant l’argument d’authentification.

Écrivons 3 network policies :
Une règle « default deny » sur l’application demodeployment :

Une règle autorisant le trafic depuis client1 vers demodeployment :

Une règle autorisant le trafic depuis client2 vers demodeployment et nécessitant l’authentification :

En appliquant uniquement la première policy, l’application de démonstration n’est pas disponible.

En revanche, les pods de l’application client2 sont toujours accessibles.

Appliquons à présent les deux autres policies. Hubble nous permet d’observer le trafic provenant de client1 et client2, avec l’étape d’authentification dans le cas de client2 :

Hubble indique une étape d’authentification entre client2 et demodeployment. Une légère latence est également observée avant l’affichage de la page avec curl, caractéristique de l’étape d’authentification.
Nos tests sont terminés, passons à la conclusion.
Conclusion
Dans cet article, l’exploration des fonctionnalités de Cilium a permis de tester l’authentification mutuelle.Cette fonctionnalité implémente SPIFFE à l’aide d’un serveur SPIRE.Ensuite, il suffit d’utiliser une network policy en spécifiant que l’authentification doit être activée. Hubble permet de visualiser cette étape de manière simple. L’étape suivante consisterait à configurer plus précisément l’observabilité du serveur SPIRE. Il serait également envisageable de mettre en place des deny policies au niveau du cluster, et pourquoi pas un admission controller pour forcer l’authentification.