Skip to main content

Command Palette

Search for a command to run...

Introduction à la sécurité d’un cluster Kubernetes (2/2)

Updated
21 min read
Introduction à la sécurité d’un cluster Kubernetes (2/2)

Dans le premier article, nous avons posé les bases : l’architecture matérielle d’un cluster Kubernetes (nœuds et pods) et la couche logique au travers des principaux objets du cluster. L’accent a été mis sur ceux qui comptent côté sécurité, notamment les objets qui accordent des droits et des identités au sein du cluster. Cette introduction a servi à cadrer les concepts essentiels.

Dans cette seconde partie, nous allons approfondir le cloisonnement dans un cluster Kubernetes, à la fois réseau et logique. Nous verrons comment un cluster est exposé, les grands types de réseaux impliqués et les mécanismes de séparation possibles côté réseau, puis nous aborderons le cloisonnement logique au moyen des namespaces.

Types de réseaux

Quand on découvre Kubernetes, la partie réseau fait souvent un peu peur : on entend parler de CNI, de Services, d’Ingress, de NodePort, de LoadBalancer, etc.

En réalité, le réseau Kubernetes repose sur quelques concepts simples qui se combinent entre eux. L’objectif de cette section est ainsi de donner une vision simple pour comprendre “qui parle à qui” dans un cluster Kubernetes.

Au sein d’un cluster, il existe 3 principaux réseaux :

  • Réseau des nodes : correspond au réseau “classique” de l’infrastructure (machines physiques, VM, nœuds cloud). C’est grâce à lui que les nœuds du cluster peuvent parler entre eux et avec Internet.

  • Réseau des pods : est le réseau interne au cluster dans lequel chaque pod reçoit sa propre adresse IP. Tous les pods peuvent communiquer directement entre eux, comme s’ils étaient sur un même grand réseau local. Ce réseau, géré par le plugin CNI présent sur chaque nœud, permet à des pods d’un nœud de communiquer avec des pods d’un autre nœud comme s’ils étaient tous sur un réseau plat.

  • Réseau des services (réseau virtuel) : lui, est particulier. Les adresses IP de Service (ClusterIP) ne sont pas associées à une carte réseau physique, elles servent simplement de point d’entrée stable pour accéder à un ensemble de pods. Quand un pod en appelle un autre via l’IP d’un service, le trafic est automatiquement redirigé vers l’une des instances réelles (les pods) situées sur le réseau des pods.

L’ensemble de ces réseaux travaille ensemble pour permettre aux applications de communiquer, aussi bien à l’intérieur du cluster qu’avec l’extérieur.

Réseau des nodes

Dans l’article précédent, nous avons parlé des nodes : ces machines (physiques ou virtuelles) qui composent le cluster et sur lesquelles tournent les pods. Chaque node étant une machine, il est forcément connecté à un réseau d’infrastructure : LAN d’entreprise, VPC cloud, réseau du datacenter, etc.

Ce réseau sous-jacent, c’est ce qu’on appelle ici le réseau des nodes. Kubernetes ne le crée pas : il l’utilise tel quel.

Ce réseau des nodes sert notamment à :

  • permettre aux nodes de communiquer entre eux (échanges internes au cluster)

  • transporter le trafic entre les nodes et le plan de contrôle (communication des kubelets avec l’API du cluster)

  • offrir une sortie vers l’extérieur pour le cluster (accès Internet, API externes, systèmes d’information existants)

  • servir de support à l’exposition de certains Services vers l’extérieur, par exemple les Services de type NodePort ou LoadBalancer, que l’on détaillera dans la suite de l’article.

En résumé, les nodes dont on a déjà parlé disposent de leur propre réseau d’infrastructure. Ce réseau des nodes constitue le socle sur lequel Kubernetes vient ensuite poser le réseau des pods et le réseau (virtuel) des services.

Réseau des pods

Une fois posé le réseau des nodes, Kubernetes ajoute une deuxième couche : le réseau des pods.
C’est le réseau interne du cluster, là où vivent réellement les applications.

Chaque pod reçoit sa propre adresse IP, issue d’un espace d’adressage dédié au cluster. L’un des principes de base du modèle réseau Kubernetes, c’est que tous les pods doivent pouvoir communiquer directement entre eux, sans NAT, qu’ils soient sur le même node ou sur des nodes différents. Vu depuis un pod, l’ensemble du cluster ressemble donc à un grand réseau plat (👀).

Mais comment ça marche concrètement ?

Concrètement, les pods ne touchent pas directement au matériel. Chaque pod dispose d'une interface réseau virtuelle avec sa propre IP, qui agit comme un tunnel vers la carte réseau physique du node. Cette mécanique est ainsi mise en place par le plugin CNI (Calico, Cilium, Flannel, etc.) installé sur chaque node.

Que le destinataire soit sur la même machine ou à l'autre bout du cluster, le trafic est automatiquement encapsulé et redirigé par le plugin CNI pour circuler de façon transparente sur le réseau des nodes.


Plugin Container Network Interface (CNI)

Le CNI attribue une IP à chaque pod, crée les interfaces virtuelles, configure les bridges, pose les routes (et éventuellement les tunnels) pour que le trafic pod↔pod fonctionne et donne l’illusion, vue depuis les pods, d’un grand réseau plat à l’échelle du cluster.

Il en existe plusieurs implémentations (Calico, Cilium, Flannel, Weave Net, etc.), chacune avec ses choix techniques, ses capacités de sécurité et ses intégrations préférées selon les distributions Kubernetes (k3s, EKS, GKE, clusters on-premise, …). Voici un exemple de plusieurs CNI :

CNI Support des NetworkPolicies Kubernetes (cloisonnement réseau) Chiffrement du trafic pod↔pod (overlay / entre nœuds) Où on le rencontre souvent / intégrations typiques
Calico Oui (support complet, avec extensions propres Calico pour des policies plus fines) Possible Clusters on-prem, distributions Kubernetes « DIY » ou managées qui laissent le choix du CNI, kOps, certaines plateformes managées orientées sécurité.
Cilium Oui Oui (chiffrement possible du trafic entre nodes/pods) Clusters orientés sécurité/observabilité avancée, environnements bare metal ou cloud.
Flannel Non (ou très limité : souvent combiné avec un autre composant) Non k3s (par défaut dans beaucoup de configurations), petits clusters de test ou environnements simples où l’on ne cherche pas de micro-segmentation.
Weave Net Oui (support des RepliNetworkPolicies de base) Oui (overlay chiffrable en option) Clusters auto-hébergés, labs, petites plateformes voulant un overlay simple avec option de chiffrement.
AWS VPC CNI (EKS) Partiel / indirect : on s’appuie surtout sur les mécanismes AWS (Security Groups, routage VPC). Des solutions comme Calico/Cilium peuvent être ajoutées pour des NetworkPolicies complètes Pas d’overlay chiffré spécifique : on s’appuie sur le chiffrement du VPC, du trafic applicatif (TLS), ou sur des solutions complémentaires Par défaut sur EKS.

Ce tableau est volontairement simplifié et centré sur la sécurité. Dans la pratique, le choix d’un CNI dépend aussi des performances, des fonctionnalités avancées, du support par la distribution Kubernetes utilisée et de la culture de l’équipe en charge de l’implémentation.

Réseau des services

Après le réseau des nodes (infrastructure) et le réseau des pods (là où vivent les applications), Kubernetes ajoute une troisième couche : le réseau des services. Celui-ci est entièrement virtuel.

Ce réseau vise à résoudre un problème simple : les pods vont et viennent, changent de node, se recréent… mais on veut une adresse stable pour accéder à une application. Et, très souvent, cette application n’est pas portée par un seul pod mais par plusieurs pods (replicas) d’un même composant. Passer par un Service permet alors deux choses essentielles :

  • disposer d’un point d’entrée unique pour l’application,

  • laisser le cluster choisir automatiquement vers quel pod envoyer la requête, parmi tous ceux qui proposent le même service.

C’est précisément ces besoins que vient résoudre le réseau des services. Techniquement, cela est rendu possible par kube-proxy, qui tourne sur chaque node.

Ainsi, pour faire le lien avec le précédent article, quand on déploie une application avec un “Deployment”, on obtient un ensemble de pods identiques (réplicas). Ces pods ont chacun leur propre IP sur le réseau des pods, et ces IP peuvent changer au fil des redéploiements.

Pour donner un point d’entrée stable vers ces pods, on crée en général un objet Kubernetes « Service » associé. Cet objet :

  • sélectionne les pods concernés

  • reçoit une IP virtuelle de service (ClusterIP), tirée d’un espace d’adressage réservé au réseau des services

  • expose un ou plusieurs ports sur cette IP.

Les autres pods (ou un Ingress, ou un client interne) n’ont alors plus besoin de connaître les IP individuelles des pods : ils utilisent l’IP (ou le nom DNS) du Service, et le cluster se charge de rediriger le trafic vers un des pods disponibles. Au sein du cluster, c’est le service « CoreDNS » qui est en charge de cette résolution (<service>.<namespace>.svc.cluster.local).


Exemple :

Imaginons une application web simple :

  • un objet Kubernetes « Deployment » se nommant « frontend » qui lance 3 pods d’un site web

  • un objet Kubernetes “Service” frontend associé.

Sans Service, on aurait par exemple :

  • Pod A : 10.244.1.5

  • Pod B : 10.244.2.7

  • Pod C : 10.244.3.9

Chaque pod possède sa propre adresse IP sur le réseau des pods, et ces IP peuvent changer à chaque redéploiement ou recréation de pod. Il serait très compliqué de suivre en permanence cette liste d’IP et de faire soi-même la répartition du trafic.

Avec un Service, le fonctionnement change :

  • Kubernetes crée un objet Kubernetes “Service” nommé frontend

  • l’objet reçoit une ClusterIP (nous y reviendrons plus tard dans l’article), par exemple 10.96.5.12

  • il est configuré pour pointer vers tous les pods correspondant au site web

À partir de là :

  • les autres pods du cluster peuvent joindre l’application via le nom DNS du Service, par exemple http://frontend ou http://frontend.default.svc

  • de façon plus bas niveau, la même application est accessible via 10.96.5.12:80 (ClusterIP et port du Service)

Ainsi, kube-proxy se charge de distribuer le trafic reçu sur cette ClusterIP vers l’un des pods frontend disponibles (A, B ou C), de manière transparente.

TL;DR - En résumé

Le réseau Kubernetes repose sur trois couches qui se complètent : le réseau des nodes, le réseau des pods et le réseau des services. Le réseau des nodes, c’est simplement le réseau d’infrastructure existant (LAN, VPC, datacenter) sur lequel Kubernetes vient se poser. Par-dessus, le réseau des pods fournit à chaque pod sa propre adresse IP et, grâce au plugin CNI, fait en sorte que tous les pods puissent communiquer entre eux comme s’ils étaient sur un grand réseau plat, même lorsqu’ils sont sur des machines différentes.

Enfin, le réseau des services est une couche entièrement virtuelle qui apporte des points d’entrée stables vers les applications. Plutôt que d’adresser directement des pods dont les IP changent, on s’appuie sur des objets Kubernetes “Service” qui reçoivent une IP virtuelle (ClusterIP) et un nom DNS. Les autres composants du cluster parlent au Service, et kube-proxy se charge de rediriger de façon transparente le trafic vers l’un des pods disponibles, ce qui permet à la fois la stabilité (une même adresse pour l’application) et la répartition de charge entre plusieurs réplicas.

Exposition réseau

Disposer d’un réseau interne avec des IP stables, c’est une chose. Mais une application qui ne communique qu’avec elle-même n'est pas très utile. Le vrai défi est là : les adresses des services sont privées et invisibles depuis l'extérieur.

Pour qu'un utilisateur puisse accéder à un service au sein d’un cluster Kubernetes, il faut donc créer un "pont" entre le monde extérieur (Internet ou le réseau de votre entreprise) et ce réseau interne. Kubernetes propose plusieurs solutions pour ouvrir ces portes, selon le degré d'exposition souhaité.

Exposition interne

ClusterIP

Le type ClusterIP est le mode d’exposition le plus basique et le plus courant. Le Service reçoit une adresse IP virtuelle (ClusterIP) dans le réseau des services (comme présenté précédemment), mais cette IP n’est accessible que depuis l’intérieur du cluster : pods, nœuds, éventuellement Ingress ou autres composants internes.

Ce type de Service est typiquement utilisé pour relier des briques applicatives entre elles : un frontend qui parle à un backend, un backend qui parle à une base de données, un job qui appelle une API interne, etc. Du point de vue des pods, on se contente d’appeler le nom DNS du Service (par exemple http://backend), et kube-proxy se charge d’acheminer le trafic vers l’un des pods qui implémente ce service.

Flux entrant

Une fois que l’on dispose du réseau des services (avec ses IP virtuelles stables qui pointent vers des pods), se pose une question très concrète : comment ces services sont-ils accessibles ?
Kubernetes propose plusieurs manières d’exposer une application vers l’extérieur :

On peut les voir comme différents niveaux d’ouverture :

  • Headless : résolution directe vers les pods ;

  • NodePort : exposition via les nodes ;

  • LoadBalancer : exposition via un load balancer externe ;

  • Ingress / Gateway API : exposition HTTP(S) plus avancée.

Headless

Dans certaines architectures (bases de données distribuées, systèmes stateful, brokers, etc.), on veut non pas cacher les pods derrière une IP virtuelle, mais au contraire les voir individuellement.

C’est là qu’intervient le Service Headless. Dans ce mode, Kubernetes ne crée pas de ClusterIP. À la place, l’entrée DNS du Service renvoie directement les IP des pods qui correspondent à ce service (les endpoints). Les clients peuvent ainsi établir une connexion directe vers un pod particulier, ou parcourir la liste des pods pour appliquer leur propre logique de répartition ou de découverte (par exemple, découvrir les nœuds d’un cluster de base de données).

NodePort

Dès que l’on souhaite qu’un service soit accessible depuis l’extérieur du cluster, le réseau des nodes entre en jeu. Le type NodePort consiste à ouvrir un port spécifique sur chaque nœud du cluster (et donc des machines), dans une plage réservée (par défaut entre 30000 et 32767).

Client externe → IP d’un node + port NodePort → Service → Pods

Le Service reste le même à l’intérieur du cluster, mais il est désormais atteignable en utilisant l’adresse IP d’un node et ce port NodePort. C’est une solution simple, qui ne nécessite pas de composant externe supplémentaire, mais qui repose sur le fait de connaître les IP des nodes.

LoadBalancer

Dans la plupart des environnements cloud, on ne veut pas gérer à la main la liste des IP des nodes.
Le type LoadBalancer vient alors s’appuyer sur NodePort, mais en demandant à la plateforme d’infrastructure de créer un load balancer externe (par exemple un ELB/NLB sur AWS, un Load Balancer GCP/Azure, etc.). Le flux devient ainsi

Client Internet → Load Balancer externe → NodePort sur les nodes → Service → Pods

Pour le client, il n’y a qu’une seule IP (ou un nom DNS) à contacter : celle du load balancer.
Pour Kubernetes, il s’agit toujours de router le trafic vers le Service, puis vers les pods. Le réseau des nodes sert d’interface entre le load balancer externe et le cluster.

Ingress / Gateway API

Enfin, pour les applications web, on ajoute souvent une couche supplémentaire : Ingress ou la Gateway API. Plutôt que d’exposer directement un Service sur une IP et un port, on définit des règles de routage HTTP(S) : nom de domaine, chemins, TLS, routage vers différents backends, etc.

Dans ce modèle, un contrôleur Ingress ou Gateway tourne dans le cluster, souvent exposé lui-même via un Service de type LoadBalancer ou NodePort. Le flux ressemble alors à :

Client Internet → Load Balancer / IP publique → Ingress / Gateway → Service → Pods

Exemple :

Pour une application web, on veut souvent exposer un nom comme “https://www.nijiapp.fr” plutôt qu’une IP et un port. Alors, dans dans le cluster, on décrit une règle du type “pour l’hote https://www.nijiapp.fr, envoie le trafic vers le Service frontend”.

L’Ingress Controller lit cette règle et sait que tout ce qui arrive pour “https://www.nijiapp.fr” doit être routé vers le Service correspondant. De l’autre côté, dans le DNS public, on fait simplement pointer “www.nijiapp.fr” vers l’IP exposée par ce contrôleur (souvent un Service de type LoadBalancer).

Flux sortant (egress)

Les pods ne restent pas isolés : ils ont souvent besoin de sortir du cluster, par exemple pour interroger une API externe ou récupérer une image sur un registre Docker.

Par défaut, Kubernetes est très libre : n’importe quel pod peut initier une connexion vers l’extérieur. Le seul bémol est la visibilité. Une fois que le flux sort, il est "anonymisé" par le node : le monde extérieur voit l'adresse du cluster, mais il est impossible de savoir quel pod précis est en train de parler. C'est une porte ouverte par défaut qu'il faut parfois apprendre à encadrer.

Aspect sécurité

Toutes ces mécaniques d’exposition ne sont pas neutres du point de vue sécurité : elles définissent par où un attaquant peut espérer atteindre des services.

Dès qu’un Service sort du simple ClusterIP interne, il crée une surface attaquable supplémentaire. Un Service de type NodePort, par exemple, implique qu’un port est ouvert sur chaque nœud dans la plage 30000–32767. Si les adresses IP des nœuds sont accessibles (depuis Internet ou depuis un réseau interne moins maîtrisé), il devient possible de scanner ces ports et d’énumérer les services exposés de cette manière, sans forcément passer par un load balancer “officiel”. De la même façon, un LoadBalancer mal filtré ou un Ingress qui accepte trop de hosts ou de chemins facilite l’énumération des endpoints applicatifs depuis l’extérieur.

Côté sortie, le fait que tout pod puisse parler librement vers l’extérieur par défaut permet, en cas de compromission d’un conteneur, de contacter une infrastructure de commande et contrôle, de scanner des ressources internes accessibles via le réseau du cluster, ou de cibler des services cloud sensibles (comme l’IMDS).

Comprendre les chemins d’entrée et de sortie n’est donc pas seulement utile pour faire fonctionner l’application : c’est aussi la base pour raisonner en termes d’exposition, d’énumération possible et de mouvements latéraux potentiels.

Cloisonnement

Le chapitre précédent décrit comment le réseau Kubernetes permet aux applications de communiquer, et comment les services peuvent être exposés ou sortir vers l’extérieur. Pris isolément, ce modèle est très fonctionnel… mais il est aussi très ouvert.

La question suivante devient donc : comment découper et limiter cet espace, de manière à éviter qu’une application, un environnement ou un composant compromis puissent trop facilement en atteindre d’autres. Kubernetes propose plusieurs leviers pour cela. Certains relèvent du cloisonnement logique (organisation des ressources, séparation des environnements, contrôle des droits), d’autres du cloisonnement réseau au sens strict (quels flux sont possibles entre quels pods).

Cloisonnement logique - Namespace

Un namespace est une “boîte logique” dans laquelle on range des objets Kubernetes : pods, services, configmaps, secrets, roles, etc.

Ce cloisonnement est avant tout organisationnel et logique :

  • il permet de séparer des environnements (par exemple dev, preprod, prod) ;

  • il permet de séparer des applications ou des équipes (par exemple paiement, marketing, data) ;

  • il sert de base à d’autres mécanismes comme RBAC (droits par namespace) ou les quotas (limites de ressources par namespace).

⚠️ Attention : Un namespace ne fournit pas, en soi, une isolation réseau. Par défaut, un pod dans un namespace peut tout à fait parler à un pod dans un autre namespace. Rien, au niveau réseau, n’empêche un flux d’un namespace vers un autre. Pour obtenir un vrai cloisonnement de trafic entre namespaces, il faut ajouter une couche de politiques réseau (NetworkPolicies) et s’assurer que le CNI les applique correctement.

On peut voir les namespaces comme un cadre logique : ils organisent les objets, structurent qui a le droit de faire quoi, et servent souvent de frontière “administrative”.

Dans le cadre d’un audit ou d’un pentest Kubernetes, il est fréquent d’obtenir l’accès à un compte n’ayant des privilèges que sur un namespace donné. Le enjeu pour l’auditeur devient alors de voir s’il est possible de sortir de ce périmètre logique et d’influencer, directement ou indirectement, des objets situés dans d’autres namespaces auxquels ce compte n’aurait pas dû avoir accès au départ.

Cloisonnement réseau

Jusqu’ici, le réseau Kubernetes a été présenté comme un grand espace où les pods peuvent tous se parler. C’est pratique pour démarrer, mais dès qu’on parle sécurité, cette caractéristique devient un problème : par défaut, le cluster est très ouvert. Un pod peut contacter n’importe quel autre pod, dans n’importe quel namespace (notion que , et sortir vers l’extérieur tant que le réseau sous-jacent le permet. Kubernetes ne met pas en place de cloisonnement réseau automatique entre applications. C’est pour pallier à ce problème qu’existent les objets Kubernetes “NetworkPolicies**”**, qui permettent de décrire explicitement quels flux sont autorisés, et donc de refermer progressivement ce grand espace ouvert.

Network Policies

Une NetworkPolicy est un objet Kubernetes qui décrit, pour un ensemble de pods donné, quel trafic réseau est autorisé à entrer (ingress) et/ou à sortir (egress). On peut la voir comme un pare-feu “logique”. Concrètement, une NetworkPolicy ne s’applique pas à tout le cluster, mais à un groupe de pods ciblés.

Un point souvent mal compris est que les NetworkPolicies fonctionnent de manière restrictive. Tant qu’aucune NetworkPolicy ne cible un pod, ce pod reste dans le modèle ouvert : il peut parler à tout le monde. En revanche, dès qu’au moins une NetworkPolicy s’applique à lui, tout ce qui n’est pas explicitement autorisé par au moins une règle est, en pratique, bloqué (à condition que le CNI supporte les NetworkPolicies, ce qui n’est pas le cas de tous comme on l’a vu plus haut).

Exemple d’utilisation :

  • autoriser un frontend à parler à un backend mais pas directement à la base de données

  • empêcher un namespace “dev” de joindre un namespace “prod”

  • limiter la sortie vers Internet à quelques services externes bien identifiés.

Vu sous l’angle d’un auditeur ou d’un attaquant, la présence ou l’absence de NetworkPolicies fait une énorme différence : sans elles, un pod compromis peut très facilement explorer le cluster et tenter de joindre n’importe quel autre service. Avec des NetworkPolicies, ses mouvements latéraux sont beaucoup plus contraints et dépendent des flux explicitement prévus par les règles.

Exemple analogique simple

On peut voir un cluster Kubernetes comme un immeuble de bureaux :

  • Les namespaces, ce sont les départements (Marketing, Finance, IT).

  • Les objets Kubernetes (Pods, Services, Secrets, ConfigMaps, etc.) sont les dossiers, applis et coffres de chaque département.

  • Les ServiceAccounts / droits RBAC, ce sont les badges qui permettent d’agir sur ces dossiers et coffres.

  • Le réseau, ce sont les couloirs qui relient toutes les pièces.

  • Les NetworkPolicies, ce sont les portes et tourniquets dans ces couloirs.

Si les couloirs sont totalement ouverts (pas de NetworkPolicies), n’importe qui peut sortir de son bureau, se balader dans l’immeuble et entrer dans d’autres pièces. On n’ouvre pas directement tous les coffres d’un autre département, mais on peut atteindre des postes, des applis internes ou des armoires où traînent des badges. Une fois un badge trouvé, ce n’est plus le réseau qui limite, mais le cloisonnement logique (namespaces + RBAC) : ce badge peut alors permettre d’agir sur les objets d’un autre département.

Conclusion

Dans cet article, le cluster Kubernetes a été présenté sous l’angle du réseau et du cloisonnement : réseaux des nœuds, des pods et des services, chemins d’entrée et de sortie, exposition des applications, namespaces, NetworkPolicies et leurs impacts en matière d’attaque et de défense. L’objectif était de construire un modèle mental simple pour comprendre “qui peut parler à qui”, par où une application peut être atteinte, et comment un manque de cloisonnement logique ou réseau peut ouvrir la porte à des mouvements latéraux.

Dans la suite de cette série, le point de vue sera plus frontalement orienté pentest. À partir de ces bases, il sera alors possible de dérouler une véritable roadmap de reconnaissance et d’attaque sur un cluster Kubernetes : cartographier les surfaces d’exposition, identifier les chemins réseau intéressants, abuser des configurations faibles, chercher des escalades de privilèges entre namespaces et, plus globalement, comprendre comment une compromission locale peut se transformer en prise de contrôle beaucoup plus large du cluster.