Sécuriser Kubernetes et ses conteneurs : la checklist de durcissement terrain
La plupart des compromissions Kubernetes n'ont rien d'un zero day. Ce sont des réglages par défaut que personne n'a modifiés. Voici la checklist de durcissement que nous déroulons réellement sur les clusters de nos clients.
Par Innovation T Team
La plupart des compromissions Kubernetes ne commencent pas par un zero day. Elles commencent par un conteneur qui tourne en root, un service account doté de cluster-admin, ou un dashboard exposé sur internet en 2023 et oublié depuis. Le durcissement n'est pas un produit que l'on achète. C'est un ensemble de réglages par défaut que l'on refuse d'accepter, et voici la checklist que nous déroulons chaque fois que nous reprenons un cluster.
Le modèle de menace en un paragraphe
Un attaquant qui prend pied dans un conteneur cherche trois choses : élever ses privilèges à l'intérieur du conteneur, s'échapper vers le nœud, ou se déplacer latéralement à travers le réseau du cluster et l'API Kubernetes. Chaque contrôle ci-dessous existe pour casser l'un de ces trois chemins. Si vous ne savez pas dire quel chemin un contrôle bloque, vous n'en avez probablement pas encore besoin. Ce cadrage garde le travail de durcissement honnête et évite aux équipes de se noyer dans des lignes de benchmark CIS qui ne changent rien aux résultats réels.
Commencez par l'image, pas par le cluster
Les vulnérabilités les moins chères à corriger sont celles que vous ne livrez jamais. L'hygiène des images est l'endroit où le durcissement rapporte en premier.
Réduisez la surface d'attaque
Une image node:20 par défaut embarque des centaines de paquets système, un shell, un gestionnaire de paquets, et le plus souvent des centaines de CVE connues le jour du scan. Chaque binaire présent dans l'image est un outil offert à l'attaquant après compromission. Vos options, par ordre d'effort croissant :
- Les variantes allégées (
-slim,-alpine) : des gains rapides, mais la libc musl d'Alpine casse parfois les dépendances natives et le comportement DNS de façon subtile. Testez avant de vous engager. - Distroless (les images
gcr.io/distrolessde Google) : pas de shell, pas de gestionnaire de paquets. Le débogage passe par les conteneurs éphémères (kubectl debug), un changement de méthode de travail que votre équipe doit répéter avant un incident, pas pendant. - Les images Chainguard ou basées sur Wolfi : reconstruites quotidiennement, avec un nombre de CVE connues proche de zéro et des SBOM inclus. La contrepartie : une dépendance au rythme de build d'un éditeur et, pour certaines images, un coût de licence.
Build multi-stage, exécution non root
La chaîne de compilation n'a jamais sa place dans l'image d'exécution. Compilateurs, npm, git et curl sont exactement ce dont un reverse shell a besoin.
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/api
FROM gcr.io/distroless/static:nonroot
COPY --from=build /app /app
USER 65532:65532
ENTRYPOINT ["/app"]
Cette image finale contient un binaire, aucun shell, un utilisateur non root. L'attaquant qui exploite le processus applicatif atterrit dans une pièce vide, sans le moindre outil.
Scanner, signer, épingler
Un scan sans politique associée, c'est du théâtre. Câblez-le dans le pipeline avec un vrai point de contrôle :
- Trivy ou Grype en CI, avec échec du build sur les CVE critiques et hautes corrigeables. Autorisez des exceptions documentées et limitées dans le temps, jamais des ignores silencieux.
- Cosign pour signer les images au build, avec une politique d'admission côté cluster qui rejette les images non signées. Cela ferme la faille du « quelqu'un a poussé une image depuis son portable ».
- L'épinglage par digest des images de base (
FROM node@sha256:...) : les builds deviennent reproductibles et un tag empoisonné ne peut plus se glisser dans la chaîne. - La génération de SBOM avec Syft, stockée comme artefact. Au prochain événement façon Log4j, vous répondez à la question « sommes-nous exposés » en minutes, pas en jours. C'est aussi exactement le type de preuve de maîtrise que vos clients grands comptes et les exigences européennes type NIS2 vous demanderont de produire.
C'est un problème de pipeline autant qu'un problème de sécurité. Nous avons détaillé le versant CI dans notre guide pour construire un pipeline DevSecOps.
Verrouillez la spécification du pod
Les valeurs par défaut de Kubernetes sont optimisées pour « ça tourne », pas pour « c'est sûr ». Le securityContext du pod est l'endroit où l'on corrige cela, pour un coût quasi nul à l'exécution.
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
capabilities:
drop: ["ALL"]
Ce que chaque ligne vous rapporte concrètement :
runAsNonRootavec un UID fixe : les techniques d'évasion de conteneur qui reposent sur le root à l'intérieur du conteneur cessent pour l'essentiel de fonctionner.allowPrivilegeEscalation: false: empêche les binaires setuid de s'élever, ce qui élimine toute une classe d'escalades de privilèges locales.readOnlyRootFilesystem: true: un malware ne peut plus écrire sa charge utile sur le disque. Montez unemptyDirsur/tmppour les applications qui ont besoin d'un espace de travail.seccompProfile: RuntimeDefault: filtre une soixantaine des appels système les plus exotiques. Presque toutes les évasions de conteneur au niveau noyau de ces dernières années nécessitaient un syscall que ce profil bloque.drop: ["ALL"]sur les capabilities : ne réintroduisez que ce que vous pouvez justifier, et méfiez-vous de tout ce qui réclameNET_ADMINouSYS_ADMIN. Un pod avecSYS_ADMINouprivileged: trueest, en pratique, root sur le nœud.
Faites appliquer tout cela avec la Pod Security Admission au niveau du namespace. Étiquetez chaque namespace applicatif avec pod-security.kubernetes.io/enforce: restricted et traitez les exceptions comme des tickets, avec un responsable et une date d'expiration. D'expérience, la migration fait remonter une poignée de charges legacy qui ont réellement besoin de privilèges (agents CNI, collecteurs de logs, pilotes de stockage). Isolez-les dans des namespaces dédiés plutôt que d'assouplir la politique partout.
Contenez le rayon d'impact
Partez du principe qu'un pod finira par être compromis. La vraie question est ce que l'attaquant peut atteindre ensuite.
RBAC : moindre privilège, sans négociation
Le constat le plus fréquent dans nos revues de clusters : des service accounts avec des permissions que personne ne sait expliquer. Les règles sont simples et ennuyeuses :
- Ne liez jamais
cluster-adminà une charge de travail. Jamais. Y compris pour les déployeurs CI. - Un service account par charge de travail, limité à son namespace, avec les verbes qu'il utilise réellement.
- Positionnez
automountServiceAccountToken: falsepar défaut. La plupart des pods applicatifs n'appellent jamais l'API Kubernetes, et pourtant chacun d'eux embarque un identifiant API valide, monté à un chemin bien connu. Ce jeton est la première chose qu'un attaquant lit. - Auditez périodiquement avec
kubectl auth can-i --list --as=system:serviceaccount:ns:nameou un outillage commerbac-tool. Un wildcard dans les verbes ou les ressources est une constatation d'audit, pas une commodité.
Network policies : tout refuser par défaut, puis autoriser
Par défaut, chaque pod peut parler à tous les autres pods, dans tous les namespaces. C'est ce réseau à plat qui transforme un frontend compromis en fuite de base de données, avec la notification à la CNIL sous 72 heures que le RGPD impose lorsque des données personnelles sont touchées. Démarrez chaque namespace avec un refus par défaut :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
Puis autorisez des flux précis : frontend vers API sur le port 8080, API vers Postgres sur le 5432, tout le monde vers le DNS. La politique d'egress compte autant que l'ingress, parce que c'est elle qui transforme « un attaquant dans un pod » en « un attaquant dans un pod qui ne peut joindre ni son serveur de commande et contrôle ni le point de métadonnées du cloud ». Bloquer l'accès des pods à 169.254.169.254 sauf besoin avéré a déjà stoppé de vrais scénarios de vol d'identifiants cloud. C'est le zero trust appliqué au niveau du pod, et le raisonnement rejoint ce que nous exposions dans l'architecture zero trust expliquée.
La contrepartie : les network policies sont impitoyables à déboguer quand un déploiement casse parce que quelqu'un a oublié la règle d'egress DNS. Livrez une bibliothèque de politiques testées avec vos gabarits de plateforme, plutôt que de demander à chaque équipe d'écrire les siennes.
Secrets : cessez de faire comme si base64 était du chiffrement
Les Secrets Kubernetes sont encodés en base64, pas chiffrés, et quiconque a un accès en lecture aux secrets d'un namespace détient le texte en clair. Autant dire que l'article 32 du RGPD, qui exige des mesures techniques appropriées, n'est pas franchement satisfait. Le minimum vital :
- Activez le chiffrement au repos d'etcd avec un fournisseur KMS (sur cluster managé : vérifiez-le, ne le supposez pas).
- Préférez les volumes de secrets montés aux variables d'environnement. Les variables d'environnement fuient dans les crash dumps, dans
kubectl describeet dans les processus enfants. - Pour tout ce qui est sérieux, tirez les secrets d'un gestionnaire externe (Vault, AWS Secrets Manager, GCP Secret Manager) via External Secrets Operator ou un driver CSI, afin que la rotation n'oblige pas à tout redéployer.
- Verrouillez le RBAC sur la ressource Secret elle-même. Un
get secretsà l'échelle du cluster, c'est un admin de domaine déguisé.
Durcissez le plan de contrôle et les nœuds
Sur un Kubernetes managé (EKS, GKE, AKS, ou les offres européennes comme OVHcloud et Scaleway), le fournisseur possède etcd et les binaires de l'API server, mais la configuration qui compte reste chez vous :
- Point d'accès API privé, ou a minima des listes d'autorisation CIDR strictes. Un API server exposé sur internet est scanné dans les minutes qui suivent sa création.
- Journalisation d'audit activée et exportée vers un stockage interrogeable. Sans logs d'audit, l'après-incident consiste à reconstruire les actions de l'attaquant de mémoire, ce qui rend impossible une notification RGPD sérieuse.
- OS des nœuds : utilisez des images minimales optimisées conteneurs (Bottlerocket, COS, Flatcar), maintenez les pools de nœuds sur un cycle de mise à jour automatisé, et n'autorisez jamais le SSH vers les nœuds comme pratique courante.
- Fraîcheur des versions : une version mineure de Kubernetes sort du support en un an environ. Un cluster en retard de plus de deux mineures accumule des CVE non corrigées et une falaise de mise à niveau vertigineuse. Mettez à jour peu, mais souvent.
Détectez à l'exécution, parce que la prévention finit par céder
Tout ce qui précède réduit la probabilité. La détection à l'exécution gère le résiduel. Des outils comme Falco ou Tetragon observent les appels système via eBPF et signalent des comportements qui ne devraient jamais se produire dans une charge durcie : un shell qui apparaît dans un conteneur distroless, une connexion sortante inattendue, un processus qui lit le jeton du service account, une écriture dans /etc/passwd.
La qualité du signal est ici exceptionnellement bonne, précisément parce que le durcissement a supprimé le bruit. Si votre image n'a pas de shell, « exécution d'un shell » n'est pas un score d'anomalie. C'est un incident. Raccordez ces alertes à un circuit de réponse avec des responsables et des runbooks. Si ce muscle vous manque encore, commencez par notre playbook de réponse à incident.
Les échecs que nous voyons en boucle
- Le durcissement appliqué aux nouveaux services pendant que le namespace legacy garde
privileged: true« provisoirement », depuis deux ans. - Un scanner en CI avec le seuil d'échec désactivé, qui génère des rapports que personne ne lit.
- Des network policies déployées sans règle d'egress pour le DNS, qui cassent la production, puis sont entièrement retirées au lieu d'être corrigées.
- Des politiques d'admission en mode audit pour l'éternité, parce que personne n'a planifié la bascule en mode blocage.
- Des secrets « migrés vers Vault » mais les anciens Secrets Kubernetes jamais supprimés, toujours lisibles, toujours valides.
Le schéma derrière ces cinq cas : un durcissement traité comme un projet avec une date de fin, au lieu d'un ensemble de réglages par défaut imposés. Les moteurs de politiques (Kyverno, ou la ValidatingAdmissionPolicy native avec CEL) existent pour faire du chemin sécurisé le seul chemin possible.
La checklist terrain
Déroulez-la de haut en bas. Chaque point appelle un oui ou un non, et « en grande partie » compte comme un non.
- Toutes les images d'exécution sont minimales (distroless, Chainguard ou slim) et construites en multi-stage.
- La CI fait échouer les builds sur les CVE critiques et hautes corrigeables, avec pour seules exceptions des dérogations limitées dans le temps.
- Les images sont signées avec Cosign et le cluster rejette les images non signées.
- Les images de base sont épinglées par digest et un SBOM est généré à chaque build.
- Chaque charge de travail tourne non root avec
allowPrivilegeEscalation: false, les capabilities supprimées, le seccompRuntimeDefaultet un système de fichiers racine en lecture seule. - La Pod Security Admission impose
restrictedsur tous les namespaces applicatifs. - Aucun service account de charge de travail ne détient cluster-admin, et le montage automatique du jeton est désactivé par défaut.
- Chaque namespace a des network policies en refus par défaut, en ingress comme en egress, y compris le blocage du point de métadonnées.
- Le chiffrement au repos d'etcd s'appuie sur un KMS, et les secrets vivent dans un gestionnaire externe avec rotation.
- L'API server est privé ou restreint par CIDR, et les logs d'audit sont exportés vers un stockage interrogeable.
- Les nœuds tournent sur un OS optimisé conteneurs avec correctifs automatisés, et le cluster est à moins de deux versions mineures de la version courante.
- Une détection à l'exécution (Falco ou Tetragon) est déployée, avec des alertes routées vers un processus de réponse qui a un propriétaire.
- Des politiques d'admission imposent tout ce qui précède, pour qu'aucune dérive ne puisse s'installer en silence.
Douze ou plus : vous êtes devant l'immense majorité des clusters que nous évaluons. Moins de huit : traitez ce chantier comme la priorité du trimestre, parce que les attaquants automatisent la découverte de précisément ces lacunes.
Comment Innovation T peut vous aider
Innovation T conçoit, construit et durcit des plateformes conteneurisées pour des entreprises en Europe, dans le Golfe et en Afrique du Nord. Nous menons des évaluations de sécurité de cluster sur exactement cette checklist, puis nous faisons le travail ingrat : migrer les charges vers la pod security restricted, écrire la bibliothèque de politiques, câbler la signature d'images dans la CI, et laisser à votre équipe des réglages par défaut imposés plutôt qu'un rapport PDF.
Si votre cluster a grandi plus vite que ses garde-fous, découvrez nos services cloud et sécurité ou parlez à nos ingénieurs. Nous vous dirons honnêtement lesquels de ces treize points comptent le plus pour votre environnement, et lesquels peuvent attendre.
Prêt à construire avec Innovation T ?
Qu'il s'agisse de sécurité, de croissance ou d'ingénierie, notre équipe peut vous aider à livrer dans les meilleures conditions.