Quoi de mieux que finir l’été avec un test de kube sur mon nouveau setup basé sur XCP-ng, oui j’ai jeté VMware (mais je l’aime toujours, c’est toxique) car ça devenait nimp avec Broadcom pour les homelabers.
Maintenant, attaquons le sujet qui nous intéresse, l’un des points faibles de Kubernetes pour le multi tenancy est le cloisonnement système. À partir de la 1.33, les user namespaces sont activées par défaut mais les versions antérieures ne bénéficient pas de cette fonctionnalité et expose donc les pods mal configurés à des vulnérabilités type breakout.
Néanmoins, les user namespaces sont une fonctionnalité du noyau donc le cloisonnement repose exclusivement sur la sécurité du noyau. Sur cette année 2026, nous assistons à une myriade de vulnérabilités noyau exploitables facilement et permettant le breakout de container (copy fail, etc).
Est-ce qu’il est souhaitable que le cloisonnement des pods dépende uniquement des mécanismes offerts par le noyau linux ? Pour répondre à cette question, nous allons investiguer kata-containers et le CoCo (article qui arrive prochainemment dans la série) pour identifier les améliorations qu’offrent ces modèles ainsi que leurs limites.
C’est quoi kata-containers ?
Ce sont des conteneurs qui font du karaté, reproduire les katas toussa toussa, la blague est nul, oui. Pour faire simple, Kata Containers est un projet open source qui fournit un environnment d’exécution basé sur des machines virtuelles légères qu’on appellera micro vm.
Une micro vm est un pod composé d’une machine virtuelle qui exécute elle même un ou plusieurs conteneurs. Dans la micro vm, il existe un composant très important qui se nomme le kata-agent, les conteneurs sont les processus fils du kata-agent car c’est lui qui est chargé de leur création comme le ferait runc.
Quelle est la différence avec un pod classique ?
Pour rappel un pod est un groupe d’un ou plusieurs conteneurs partageant les mêmes namespaces sauf le PID namespace (pour faire simple mais il y en a quelques autres). La conteneurisation n’est pas de la virtualisation et repose exclusivement sur les mécanismes offerts par le noyau (cgroups, namespaces, etc) tandis qu’une micro vm dépend vraiment d’un VMM. Chaque objet Pod dispose de propre noyau et ne partage plus les ressources systèmes de l’hôte.
Architecture
Kata fonctionne sous le kubelet et containerd en s’appuyant sur un shim. Le kubelet communique avec containerd qui lui même appelle containerd-shim-kata-v2. Lors de la création d’un pod basé sur un objet RuntimeClass fourni par Kata, QEMU ajoute un périphérique vhost-vsock-pci (https://wiki.qemu.org/Features/VirtioVsock).
Ce périphérique fourni un socket basé sur VSOCK permettant d’établir un canal de communication bidirectionnelle entre la VM et l’hyperviseur. Ce socket est indépendant de la configuration réseau de la machine virtuelle.
Le shim utilise alors ce canal de communication pour communiquer avec le kata-agent qui s’exécute dans la VM. Le shim fournit la configuration et les requêtes nécessaires au kata-agent pour la gestion des conteneurs en utilisant le protocole gRPC.

Mais comment ça se déploie une micro VM ?
Si une machine virtuelle est utilisée au sein d’un hyperviseur, il faut activer le nested virt et vérifier ensuite les extensions sur x86_64 et si le module KVM est bien chargé.
root@coco1:~# grep -E -o '(vmx|svm)' /proc/cpuinfo | sort -uvmxroot@coco1:~# ls /dev/kvm/dev/kvm
Pour utiliser kata, il est nécessaire de configurer des container runtimes compatibles et containerd-shim-kata-v2 est idéal pour ça car c’est le runtime utilisé pour exécuter des pods avec QEMU.
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu]runtime_type = "io.containerd.kata-qemu.v2"runtime_path = "/opt/kata/bin/containerd-shim-kata-v2"privileged_without_host_devices = truepod_annotations = ["io.katacontainers.*"]
Il faut aussi que K8S sache quel container runtime doit être sélectionné pour exécuter les pods et on utilise l’objet RuntimeClass pour ça :
kind: RuntimeClassapiVersion: node.k8s.io/v1metadata: name: kata-qemuhandler: kata-qemuoverhead: podFixed: memory: "160Mi" cpu: "250m"scheduling: nodeSelector: katacontainers.io/kata-runtime: "true"
Une micro VM dans Kubernetes est en réalité un Pod avec une clef runtimeClassName. Il est donc possible de choisir un container runtime qui communique avec un VMM, au hasard QEMU et ne pas appeler runc dans ce cas.
kind: Podmetadata: name: ubuntu-kataspec: runtimeClassName: kata-qemu containers: - name: ubuntu image: ubuntu
Une micro VM est représentée en tant que Pod au sein de l’API kube et il est possible
de manipuler cet objet avec le binaire kubectl comme n’importe quel autre pod.
osadmin@master-test:~$ kubectl get podsNAME READY STATUS RESTARTS AGEubuntu-kata 1/1 Running 0 16mosadmin@master-test:~$ kubectl edit pod ubuntu-kata
Sur mon noeud de type worker, je retrouve bien un processus QEMU qui correspond à une machine virtuelle et à l’objet pod ubuntu-kata que je liste depuis le kube-apiserver.

Ça sert à quoi ?
- J’ai besoin d’une configuration système privilégiée pour exécuter mon application au sein de
mon cluster et pour ne pas casser ma politique de sécurité, j’exécute mon application dans
une micro VM pour avoir un niveau de cloisonnement plus robuste en cas de
compromission de celle-ci.
- L’application est compromise sur mon noeud mais “de manière sécurisée” car l’attaquant est dans une VM donc tout va bien.
D’un point de vue sécurité c’est top car on peut isoler les workloads qui ne sont pas de confiance mais on va se poser la question suivante;
What could go wrong ?

Dans un cluster Kubernetes, l’interface réseau (veth pair) d’un pod a accès au réseau overlay qui est à plat car il n’existe pas de netpol par défaut. Aussi, Kata utilise virtiofs pour partager les ressources liées au plan de contrôle au niveau du filesystem de la machine virtuelle notamment les certificats, les secrets et les serviceaccounts.
Donc, les administrateurs pourraient vite oublier qu’une micro VM est un pod et qu’un pod
dispose d’un ServiceAccount par défaut.
Les problèmes de sécurité relatifs au control plane sont aussi présents dans une micro vm.

Provenant de la documentation :
virtio-fs is supported in Cloud Hypervisor and QEMU. virtio-fs‘s interaction with the host filesystem is done through a vhost-user daemon, virtiofsd. The virtio-fs client, running in the guest, will generate requests to access files. virtiofsd will receive requests, open the file, and request the VMM to mmap it into the guest. When DAX is utilized, the guest will access the host’s page cache, avoiding the need for copy and duplication. DAX is still an experimental feature, and is not enabled by default.
On va donc emprunter le chemin d’attaque classique, si des long live tokens sont utilisés (que les tokens des SA ne sont pas directement stockés dans des projects volumes mais bien au sein d’ETCD dans les secrets). Avec mon outil acsconsole que j’avais également utilisé lors de ma présentation d’OSAKA à la conf UYBHYS24, je vais vous montrer qu’il est possible de compromettre un cluster depuis une micro vm. Cet outil automatise l’exploitation des chemins d’attaques dans kube.
Avant toute chose, il faut redéployer le pod en y ajoutant un ServiceAccount (SA) :
kind: Podmetadata: name: ubuntu-kataspec: serviceAccountName: thor runtimeClassName: kata-qemu containers: - name: ubuntu image: ubuntu
Récupération des tokens
Sur la sortie, on constate très rapidement que j’ai le privilège de lire et créer des secrets avec mon SA (_LIST_SECRETS_CREATE). L’outil va dumper les secrets de chaque namespace et les mettre dans /tmp/namespaces/{$namespace}.

Ensuite je récupère la totalité des JWT (JSON Web Tokens)

Réutilisation du jeton
Les jetons sont liés à des SA qui ont des privilèges beaucoup plus élevés que juste lire les secrets, par exemple capacité à créer des pods. Cette commande aura donc pour conséquence de créer des pods privilégiés sur le control plane avec la racine de l’hôte monté en tant que volume dans le pod. (les pods privilégiés peuvent casser le cloisonnement offert par les user namespaces)

Création des pods privilégiés sur le control plane

Récupération de la session TCP (reverse shell) et compromission du control plane
Le mount /acs permet d’accéder au filesystem du control plane et d’y effectuer des actions en tant que root.

Le cluster a été totalement compromis depuis une micro vm dont le SA a trop de privilèges.
Les limites de la micro VM
Il est inutile de se focaliser sur les vulnérabilités système car ça nécessite de réaliser un escape
de VM soit en exploitant une CVE non patché ou une zero day. Un attaquant se retrouve dans la situation nominale du plan de contrôle exposé aux applications métiers pouvant effectuer des requêtes sur l’API K8S.
Néanmoins, la création de micro VM introduit la notion “nested” quand l’environnement n’est pas
bare metal, le nested est connu pour augmenter la surface d’attaque de l’hyperviseur car plus de
complexité à gérer susceptible de causer des vulnérabilités.
Une micro VM protège contre :
- Les escapes triviales vers le noeud
- Les attaques systèmes sur les composants kube
Une micro VM ne protège pas contre :
- Les attaques du control plane
- Le vol du token des SA après un escape de pods
Comment je sécurise mon cluster alors ?

Pour construire une architecture multi tenant l’idéal serait de bloquer toute remontée direct vers le control plane et d’utiliser les micro vm pour les workloads qui ne sont pas de confiance (ayant besoin de privilèges systèmes élevés), un workload utilisant CAP_SYS_ADMIN par exemple. Je suis assez fan de rupture protocolaire mais il serait très complexe d’en faire ici mais par contre créer une API qui n’autorisera que des actions prédéfinies sur le control plane serait vraiment intéressant.
Le « unstrusted workload » ne pourrait pas effectuer des requêtes directement sur le kube-apiserver mais uniquement vers une API qui ferait les requêtes à sa place. Car, celui-ci n’a tout simplement pas de ServiceAccount monté dans son filesystem et qu’il est restreint par une netpol d’un point de vue réseau. Le ServiceAccount pourrait avoir un ClusterRole type cluster-admin suite à une mauvaise conf mais le workload n’en serait tenu qu’aux actions proposées par l’API pour son cas d’usage. Bien sûr, l’API doit être durcie et auditée.
Conclusion
Abusez de la micro VM dans votre infrastructure kube si ce n’est pas encore le cas mais ayez toujours à l’esprit que Kata à lui seul ne permet pas de faire du multi tenancy mais peut fortement y contribuer.
Par ailleurs, la plupart des infras sont basées sur de la virtualisation donc pour utiliser Kata, il faut aussi activer le nested virt qui est connue pour amener de la complexité au sein des hyperviseurs.
Dans mon cas, ça a fonctionné mais ce n’est clairement pas une feature recommandée en prod pour le moment : https://docs.xcp-ng.org/compute/#nested-virtualization

Laisser un commentaire