L'exercice tenait en une phrase : trois services, deux réseaux, et une base de données que seule l'API doit pouvoir joindre. Deux ingénieurs l'ont traité chacun de leur côté. Les deux ont su énoncer l'objectif avec les mots justes. Les deux ont écrit exactement l'inverse.
L'exercice, et le piège assumé
Une application classique en trois couches : un front servi par un serveur web, une API métier, une base de données. Deux réseaux déclarés dans le fichier de composition. La consigne, répétée à l'oral : la base ne doit être accessible que par l'API.
Les deux rendus étaient différents sur beaucoup de points, et identiques sur celui-là : le conteneur web était rattaché aux deux réseaux. Quand je leur ai demandé de reformuler l'objectif, ils l'ont formulé correctement — « c'était pour que la base ne soit accessible que par l'API ». La phrase était juste. La configuration disait le contraire.
Le piège était volontaire. Il est là parce que c'est l'erreur que je vois le plus souvent passer en revue d'architecture, et parce qu'elle ne se voit pas : c'est l'API qui doit appartenir aux deux réseaux, pas le front. Le front n'est jamais autorisé à aller sur le back.
Pourquoi l'inversion est grave
Le front est la seule pièce exposée à Internet. C'est celle qui reçoit des entrées non maîtrisées, celle qui embarque le plus de dépendances tierces, celle qui se fait compromettre en premier. Tout le raisonnement de cloisonnement part de là : on suppose qu'elle tombera.
Rattacher ce conteneur au réseau de la base ne rend pas l'architecture « un peu moins étanche ». Elle supprime la raison d'être de l'API. Authentification, validation des entrées, limitation de débit, journalisation des accès : tout ce que l'API impose devient facultatif, puisqu'on peut désormais l'éviter. Le point de contrôle est toujours là, mais il a cessé d'être un passage obligé.
Et voici ce qui rend l'erreur durable : un cloisonnement inversé ne produit aucun symptôme. Les deux réseaux sont bien déclarés. Le schéma d'architecture est juste. L'application démarre, répond, passe les tests fonctionnels. Aucun journal ne signale quoi que ce soit. Il n'y a rien à remarquer — jusqu'au jour où quelqu'un remarque.
Savoir énoncer n'est pas savoir écrire
C'est le vrai sujet de cette séance, et il dépasse largement Docker. Réciter « moindre privilège » ne coûte rien. Traduire ce principe en une déclaration de réseaux coûte une décision par service — et cette décision ne devient visible que si on se force à la poser.
La méthode qui évite l'inversion consiste à ne pas commencer par les réseaux. On commence par la liste des flux autorisés, et elle est courte : le front appelle l'API, l'API interroge la base. C'est tout. Les réseaux se déduisent ensuite mécaniquement : un réseau est un ensemble de conteneurs qui doivent se parler, et un conteneur n'y entre que s'il a un flux à y faire passer.
Le contrôle qui attrape l'erreur tient alors en une question, posée service par service, et qui exige une réponse nommant un flux : qu'est-ce que ce conteneur a à dire aux autres membres de ce réseau ? Pour le conteneur web sur le réseau de la base, il n'existe aucune réponse. C'est le signe qu'il n'a rien à y faire.
L'autre moitié du malentendu : publier un port
La même séance a fait remonter une confusion qui va avec, et qu'on retrouve partout : l'idée que deux conteneurs auraient besoin d'un port publié pour communiquer. C'est faux, et c'est une fausse croyance coûteuse.
Sur un réseau utilisateur, le moteur fournit sa propre résolution de noms : un conteneur joint un autre par son nom de service, sur n'importe quel port, sans que rien ne soit publié. Publier un port ne sert qu'à une seule chose — rendre le conteneur joignable depuis l'hôte, et donc depuis tout ce qui peut joindre l'hôte.
D'où la conséquence, qui est la véritable faille de la série : publier le port de la base de données. Si l'hôte est sur Internet, quelqu'un passe par l'adresse de la machine et tombe directement sur la base. Sans cette publication, il n'avait aucun chemin. On a ouvert une porte pour un confort de développement, et on l'a laissée ouverte en production.
Deux détails qui servent vraiment : on peut publier en précisant l'adresse
d'écoute — 127.0.0.1:5432:5432 n'expose le port qu'à la machine elle-même, ce qui
suffit pour un accès local avec un client graphique. Et les deux erreurs de cet article se
cumulent : un front rattaché au réseau de la base, plus un port de base publié, et le
cloisonnement n'existe plus que sur le schéma.
Comment on le vérifie, au lieu de l'espérer
Un cloisonnement ne se relit pas, il se teste. Relire son propre fichier de composition est précisément l'exercice qui a échoué deux fois : les deux auteurs le trouvaient conforme. La seule preuve est une tentative de connexion, et elle a une particularité qui déroute au début — ici, le test réussi est celui qui échoue.
Si la deuxième commande répond autre chose qu'un échec, le front atteint la base et le cloisonnement est décoratif. Si la troisième échoue, on a cloisonné trop serré et l'application tombera à la première requête. Les deux résultats comptent : un cloisonnement juste se démontre dans les deux sens.
Détail pratique : une image minimale ne contient pas d'outil réseau, et ce n'est pas une raison d'en installer un dans une image de production. On attache alors un conteneur jetable au réseau à tester, on fait la tentative depuis lui, et on le supprime.
La question qui sépare
Personne, en entretien comme en revue d'architecture, ne demande si vous connaissez la syntaxe des réseaux. On vous demande où vous avez placé chaque service, et pourquoi. La différence se joue sur une question, à poser pour chaque conteneur et chaque réseau auquel il appartient :
Quel flux justifie cette adhérence ?
S'il existe une réponse qui nomme un appel réel, le rattachement est légitime. S'il n'y en a pas, le conteneur est là par commodité, et la commodité vient de supprimer un contrôle que personne ne verra manquer. C'est la même mécanique que tous les raccourcis qui « marchent » : le symptôme disparaît, la raison d'être du garde-fou reste entière, et plus personne ne la regarde.
Dernière chose, valable au-delà de cet exercice : un cloisonnement qu'on ne sait pas tester est un cloisonnement qu'on n'a pas.