Blog

Quels clients ont réellement besoin de leur propre VM ? Un plan d'isolation Docker par niveaux

Une VM pour chaque client, c'est excessif. Voici comment décider du niveau d'isolation nécessaire pour chaque locataire—et automatiser la décision.

Résumé

Les agences paniquent souvent lorsqu'un client demande à quel point ses données sont réellement isolées des autres locataires. Les namespaces et cgroups de Docker offrent une isolation réelle, mais ils ne constituent pas une barrière matérielle. Plutôt que de faire tourner chaque client sur une VM—ou pire, de traiter tous les clients de la même manière—créez un petit ensemble de niveaux d'isolation et faites correspondre chaque client à l'un d'eux en fonction de la sensibilité des données, de la confiance et de la conformité. Un conteneur verrouillé (non-root, capacités supprimées, seccomp, racine en lecture seule) couvre la plupart des sites ; les charges de travail réglementées ou hostiles nécessitent une VM ou un hybride conteneur-dans-VM. Cet article propose un processus de décision réutilisable, un tableau comparatif et un regard honnête sur les cas où une isolation supplémentaire est excessive.

En êtes-vous au point d'un appel commercial où le nouveau client dit « nous sommes dans la santé, montrez-nous que nos données sont isolées de vos autres clients » et vous préféreriez parler de n'importe quoi d'autre ?

C'est le problème des agences : non pas un déploiement parfait, mais le même déploiement fiable répété auprès d'une douzaine de clients avec des budgets, des profils de risque et des exigences de conformité différents. Voici la version honnête. L'isolation Docker est réelle, mais elle est spécifique. Les namespaces donnent à chaque conteneur sa propre vue des processus, du réseau et du système de fichiers ; les cgroups plafonnent le CPU, la mémoire et les E/S disque afin que les locataires ne puissent pas s'affamer mutuellement. Ce que cela ne vous apporte pas, c'est une barrière matérielle entre le conteneur et le noyau hôte. Si un attaquant s'échappe du conteneur, il se retrouve à l'intérieur du seul noyau que vous possédez. Le reste de cet article transforme ce fait inconfortable en une décision réutilisable : classer chaque client par sensibilité des données et confiance, appliquer un profil de durcissement de base, et ne recourir à une VM que lorsque le coût d'une violation est supérieur au coût de la VM.

Attendez, les conteneurs ne sont-ils pas déjà isolés ?

Docker repose sur les namespaces et cgroups de Linux, et ces mots font un vrai travail. Les namespaces séparent les identifiants de processus, les piles réseau, les points de montage et les utilisateurs, de sorte qu'un processus dans un conteneur ne peut pas voir la table des processus d'un autre. Les cgroups fixent des limites : donnez à un conteneur 0,5 CPU, 512 Mo de mémoire et un poids d'E/S bloc fixe, et c'est exactement ce qu'il obtient. Une boucle incontrôlée dans un locataire est limitée au lieu de faire tomber le voisin. Si vous n'avez pas configuré de limites, vous avez sauté la chose la plus basique à laquelle servent les cgroups.

Prenons une simple application PHP dans le conteneur A. Elle voit son propre système de fichiers, sa propre interface réseau, son propre PID 1. Le conteneur B a la même chose, mais une vue différente. C'est ça, les namespaces. Maintenant, éloignez-vous et omettez la limite de mémoire : le conteneur A peut remplir la RAM de l'hôte et faire ramper le conteneur B. C'est pour empêcher cela que les cgroups existent. Mais deux conteneurs peuvent être isolés l'un de l'autre par les namespaces et partager tout de même le noyau hôte, ce qui est le sujet de chaque histoire d'évasion de conteneur. Une exploitation qui atteint le noyau peut potentiellement atteindre tous les locataires de cet hôte.

« Docker est isolé » est une phrase à moitié vraie. La version précise est : « Docker isole avec des namespaces et des cgroups, et une vulnérabilité du noyau est le rayon d'explosion. » Avant de faire confiance à un locataire pour exécuter du code non fiable, méditez une minute sur cette pensée. La réponse n'est pas « ne jamais utiliser de conteneurs »—c'est la panique facile. La réponse est un système de niveaux.

Alors pourquoi certains clients ont-ils besoin de plus que des namespaces ?

La réponse honnête est que l'isolation n'est pas un interrupteur, c'est un spectre. À une extrémité, vous avez un conteneur entièrement partagé où tout le monde est effectivement dans une seule application. À l'autre extrémité, vous avez une VM distincte par locataire avec son propre noyau. La plupart du travail en agence se situe dans le milieu inconfortable, et ce milieu n'est pas un choix binaire entre « Docker suffit » et « faites tourner une VM pour tout le monde ».

Ce qui pousse un client vers la droite, ce n'est pas sa taille. Ce sont quatre questions :

  • Stockent-ils des données réglementées ? Dossiers de santé, détails de carte de paiement, tout ce qu'un régulateur qualifierait de sensible.
  • Une violation sur leur locataire a-t-elle un chemin réaliste vers un autre locataire ? S'ils peuvent exécuter du code arbitraire, oui.
  • Faites-vous confiance au code et aux personnes qui le déploient ? Un client qui engage le freelance le moins cher n'est pas au même niveau de confiance qu'un client dont vous connaissez l'équipe de développement.
  • Leur contrat mentionne-t-il « dédié », « isolé » ou « privé » ? Si c'est le cas, vous avez déjà promis un niveau ; la seule tâche maintenant est de choisir le bon.

Si vous ne pouvez pas encore répondre à ces questions, placez le client dans un niveau de base et notez les hypothèses. Ce n'est pas un audit de sécurité ; c'est un contrôle de cohérence que vous répétez à chaque intégration.

Comment décider pour chaque client sans lancer un audit de sécurité à chaque fois ?

Créez un petit tableau et tenez-vous-y. Vous n'avez pas besoin d'une matrice à quarante cellules. Quatre niveaux couvriront presque tous les clients qu'une agence rencontre.

Position du clientCe qui les sépare réellementUtiliser quand
Niveau 1 : Application/conteneur partagéLogique applicative uniquementUtilitaires internes, données à faible risque, projets où tout le monde est explicitement dans un seul système de connexion
Niveau 2 : Même hôte, conteneurs séparésNamespaces et cgroupsLa plupart des sites marketing, formulaires de contact, pas de données sensibles
Niveau 3 : Conteneur verrouilléNiveau 2 + non-root, capacités supprimées, seccomp, racine en lecture seule, segmentation réseauE-commerce, PII (données personnelles), code personnalisé auquel vous ne faites pas entièrement confiance
Niveau 4 : VM par locataireHyperviseur et noyau séparéSanté, finance, paperasse de conformité, code non fiable, voisins bruyants

Voici comment cela se passe en pratique. Un client boulanger avec un formulaire de contact et un lien Instagram va au Niveau 2 : un conteneur sur un hôte partagé, réseau Docker par défaut, limites de ressources, travail terminé. Une boutique en ligne qui stocke les noms, adresses et redirections de paiement des clients va au Niveau 3 : même hôte partagé, mais le conteneur tourne en tant qu'utilisateur non-root, n'a pas de capacités noyau supplémentaires, utilise un profil seccomp et n'expose que le port 443. Un portail médical qui stocke des informations de santé protégées va au Niveau 4 : une VM par locataire, car le coût d'une violation n'est pas « on va nettoyer » mais « nous ne pouvons pas montrer au client que nous l'avons pris au sérieux ».

Tout l'artifice est que vous ne repensez pas l'architecture pour chaque client. Vous choisissez une ligne dans un tableau que vous avez déjà approuvé. C'est ainsi qu'une agence de cinq personnes peut gérer cent sites sans cent obsessions de sécurité distinctes. Cela signifie aussi que le prochain client n'obtient pas une réponse qui dépend du membre de l'équipe qui a répondu au téléphone. Pour le débat architectural plus profond derrière ces choix, ce guide sur la conception de niveaux d'isolation multi-locataires couvre les compromis plus en détail.

À quoi ressemble réellement un conteneur verrouillé ?

Arrêtons de dire « verrouillé » et devenons concrets. Voici ce que signifie le Niveau 3 pour un client WordPress ou PHP typique.

Premièrement, changez l'utilisateur. La plupart des images officielles tournent encore en root par défaut ; dans votre Dockerfile, créez un utilisateur non-root et exécutez l'application avec cet utilisateur. Cela supprime immédiatement le moyen le plus courant par lequel un compromis de conteneur devient un compromis de l'hôte. Deuxièmement, supprimez les capacités dont vous n'avez pas besoin. Exécutez avec --cap-drop ALL et rajoutez-en une seule, généralement NET_BIND_SERVICE pour que l'application puisse écouter sur le port 80. Rien que cela est un changement plus important que ce que la plupart des gens attendent. Troisièmement, rendez le système de fichiers racine en lecture seule avec --read-only, et montez les répertoires inscriptibles (téléversements, répertoire de données de la base) comme volumes ou tmpfs. Quatrièmement, appliquez un profil seccomp et, si votre hôte le prend en charge, AppArmor ou SELinux. Enfin, placez le conteneur sur un réseau Docker dédié et n'exposez que les ports qui doivent réellement être accessibles.

Prenons l'exemple de WordPress. L'image de base tourne probablement en root, donc vous ajoutez une étape useradd et une directive USER. Vous exécutez le conteneur avec une limite de mémoire et une limite de CPU, afin qu'un pic de trafic de plugin ne nuise pas au voisin. Vous montez /var/www/html/wp-content/uploads comme volume inscriptible. Vous définissez --read-only. Vous l'attachez à un réseau qui n'a aucun drapeau --privileged à proximité. Le résultat est un conteneur qui était « un site WordPress » et qui est maintenant « un site WordPress qui se trouve être plus verrouillé que la plupart des serveurs privés virtuels ».

Si le fait de tout faire à la main semble fragile, il existe une voie moyenne plus facile : l'isolation renforcée des conteneurs de Docker, qui utilise l'isolation des namespaces utilisateur et un runtime de conteneur sécurisé. C'est un raccourci légitime, mais ce n'est pas un laissez-passer pour sauter les étapes non-root ou la suppression des capacités. Le locataire a toujours besoin d'une image sensée. La différence est que la surface d'attaque côté noyau devient plus petite sans que vous deveniez un expert seccomp du jour au lendemain. Si vous voulez la séquence exacte pour un seul locataire, le guide de durcissement d'isolation étape par étape transforme cette section en commandes copier-coller.

Quand est-ce que j'arrête d'empiler et je leur donne simplement une VM ?

Voici la partie provocatrice : plus d'isolation n'est pas automatiquement mieux. Les VM offrent une isolation au niveau matériel, un noyau séparé et une surface d'attaque beaucoup plus petite si le noyau invité tombe. C'est exactement ce que les clients de la santé et de la finance attendent lorsqu'ils disent « nous voulons être isolés ». Mais chaque VM ajoute des coûts de correctifs, de sauvegarde et de calcul, et multiplie le travail de maintenance d'une flotte. Si vous mettez chaque client sur une VM parce qu'un client vous a dit un jour que Docker lui faisait peur, vous avez acheté du théâtre de sécurité avec de l'argent réel.

Une VM est la bonne réponse lorsque le risque par locataire est plus élevé que le coût opérationnel d'une VM par locataire. Cela signifie des données réglementées, des exigences de conformité écrites, du code tiers non fiable, ou un client qui a besoin de faire disparaître un voisin bruyant. C'est aussi la bonne réponse lorsque le contrat du client promet littéralement un environnement dédié, car « conteneur » n'est pas ce qu'il imagine en signant « dédié ».

Mais une VM n'excuse pas un conteneur bâclé. Un piège courant consiste à placer le client dans une VM puis à sauter le durcissement parce que « la VM le protège ». La VM protège l'hôte du locataire, pas le locataire de sa propre mauvaise image. Vous voulez toujours du non-root, des capacités supprimées et du seccomp à l'intérieur de cette VM. L'approche hybride—des conteneurs dans une VM—est souvent le point idéal : la VM fournit la frontière pour les conversations de conformité, et le conteneur vous donne le flux de déploiement que vous connaissez déjà. Il existe une version plus longue de ce débat dans Chaque client devrait-il avoir sa propre VM ?, mais la réponse courte est que la VM est pour le contrat, pas pour la peur.

Comment rendre cela réutilisable pour chaque client ?

Vous le rendez réutilisable en faisant du système de niveaux un modèle, pas un souvenir. Gardez un répertoire de fichiers Compose, un par niveau : tier2-baseline, tier3-locked, tier4-vm-hybrid. Quand un nouveau client se présente, copiez le modèle, changez les variables d'environnement, et vous connaissez déjà la forme de l'isolation avant d'avoir écrit une ligne de nouvelle infrastructure.

Ensuite, écrivez la décision. Pas un rapport de sécurité de 400 pages, mais un court paragraphe dans le dépôt du client : quelles données il stocke, à quel niveau il se trouve, pourquoi, et ce qui le ferait passer à un niveau supérieur. Ce paragraphe vaut plus qu'une centaine de règles de pare-feu, car c'est la chose que vous pouvez montrer au prochain auditeur ou au prochain client inquiet. Cela vous évite aussi d'avoir à vous souvenir pourquoi la boulangerie a obtenu le Niveau 2 et la boutique en ligne le Niveau 3 après que l'appel commercial initial s'est estompé.

Automatisez les vérifications ennuyeuses. Demandez à votre CI d'analyser chaque image client et d'échouer une construction si elle tourne en root, si elle a toutes les capacités, ou si elle essaie de publier un port autre que ceux autorisés par le niveau. Rien de tout cela n'est exotique ; il s'agit simplement de s'assurer que le modèle n'est pas accidentellement cassé par un développeur bien intentionné. Si vous construisez de toute façon le flux d'hébergement environnant, l'article sur les stratégies d'hébergement Docker prêtes pour la production couvre la partie qui vient après la définition des conteneurs.

Rien de tout cela n'est glamour. Aucun article de blog ne rendra « l'isolation des locataires » aussi excitant qu'un diagramme d'architecture greenfield. Mais c'est la différence entre une agence qui répond « à quel point sommes-nous isolés ? » avec un « totalement » en croisant les doigts, et une qui peut montrer un niveau, une configuration et une raison. Les conteneurs ne sont pas un mur magique. Les VM ne sont pas une balle magique. Un système de niveaux n'est qu'une décision que vous écrivez et réutilisez—et pour une agence, la réutilisabilité est tout le jeu.

Sources (5)