Se rendre au contenu

« Le front n'est jamais autorisé à aller sur le back »

Deux ingénieurs, le même exercice, la même inversion. Ils savaient énoncer la règle de cloisonnement ; ils l'ont écrite à l'envers. Et un cloisonnement inversé ne produit aucun symptôme.
18 septembre 2026 par
« Le front n'est jamais autorisé à aller sur le back »
Frazer Sado

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.

Ce qui a été écritreseau_frontreseau_backapiwebdble front atteint la base directementCe que l’exercice demandaitreseau_frontreseau_backwebapidbla base n’est joignable que par l’API
À gauche, ce que les deux rendus contenaient : le conteneur web partage le réseau de la base. À droite, la seule configuration qui tient : l’API est le seul point de passage.

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 conteneurs sur le même réseaureseau_backapidbdb:5432aucun port publié n’est nécessaireLa même base, publiée avec -p 5432:5432interface réseau de l’hôtedbInternet5432le port de la base est ouvert sur l’hôte
Publier un port ne sert jamais à faire communiquer deux conteneurs entre eux — seulement à ouvrir le service sur l’hôte, et donc au-delà.

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.

Trois commandes qui prouvent le cloisonnement$ docker network inspect reseau_back --format '{{range .Containers}}{{.Name}} {{end}}'api dbla liste doit contenir l’API et la base, jamais le web$ docker compose exec web sh -c 'nc -z -w2 db 5432; echo $?'1un échec ici est le résultat attendu : le front n’atteint pas la base$ docker compose exec api sh -c 'nc -z -w2 db 5432; echo $?'0l’API, elle, doit passer — sinon le cloisonnement est trop serré
Un cloisonnement juste se démontre dans les deux sens : le front doit échouer, l’API doit passer.

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.

« J'ai commenté les lignes et ça remarche »
Deux ingénieurs, le même symptôme, la même correction. Elle marche, et elle rouvre l'injection de scripts. Anatomie du raccourci qui ressemble à une réparation.