Le navigateur de l'hôte affiche l'adresse de sortie du VPN, le conteneur signale votre connexion domestique et quelqu'un appelle cela une fuite. Rien n'a fui. Un tunnel d'hôte est un ensemble d'interfaces, de routes et de règles qui appartiennent à l'espace de noms réseau de l'hôte, et un conteneur sur un pont est un autre espace de noms avec sa propre route par défaut. J'ai connu une pile Compose où le client VPN était connecté depuis des semaines et où pas un seul paquet de conteneur n'y était jamais entré.

Vérifiez l'espace de noms avant l'icône de la zone de notification

Les espaces de noms sont tout l'argument. Quand Docker démarre un conteneur sur un réseau pont, il crée un espace de noms réseau, y attache une extrémité d'une paire veth et donne à cet espace de noms une adresse et une route par défaut. L'interface du tunnel, quel que soit son nom, vit dans l'espace de noms de l'hôte. Le trafic à l'intérieur du conteneur ne peut pas la voir, et aucun réglage du client ne change cela de l'extérieur.

Deux mouvements pris en charge vous sortent de l'isolation. --network host place le conteneur dans l'espace de noms de l'hôte, si bien qu'il hérite des routes de l'hôte et donc du tunnel. --network container:vpn partage l'espace de noms d'un autre conteneur, et c'est ainsi que la plupart des conceptions sidecar font passer une charge de travail par un conteneur VPN. Les deux sont des choix explicites ; en général un seul correspond à ce que la personne qui déploie voulait dire.

Le mode hôte n'est pas une mise à niveau gratuite. Le conteneur cesse d'avoir sa propre adresse, la publication de ports cesse de s'appliquer, et un processus détenant NET_ADMIN à cet endroit peut réécrire le routage de l'hôte. L'accepter peut être la bonne décision. Supposer que c'est déjà actif, c'est ainsi que l'on passe une soirée à déboguer un tunnel qui n'a jamais été sur le chemin.

Suivez le paquet hors du pont

Un conteneur sur un pont envoie son paquet à sa passerelle par défaut, qui est l'interface du pont sur l'hôte. L'hôte transmet ensuite le paquet et, dans la configuration par défaut, traduit l'adresse source par la sienne avant que le paquet ne sorte. C'est cette étape de traduction qui défait silencieusement la politique.

Au moment où le paquet atteint le point de terminaison du tunnel, il porte l'adresse de l'hôte. Toute règle, route de politique ou décision de tunnel partagé fondée sur l'adresse d'origine du conteneur cesse de correspondre, et deux conteneurs censés sortir par des sorties différentes ne peuvent plus être distingués par la seule source. Si vous avez besoin d'un routage par conteneur, la décision doit intervenir avant la traduction ou dans un espace de noms que vous contrôlez.

Docker entretient lui-même ces règles, via le backend de pare-feu avec lequel le démon a été configuré. Les règles écrites à la main en dehors du point d'accroche pris en charge pour le trafic transmis, DOCKER-USER, peuvent être réordonnées ou supprimées lorsque le démon redémarre ou que l'hôte est mis à niveau. La documentation de Docker sur le filtrage de paquets montre l'ordre des chaînes ; le point pratique est que chaque règle a besoin d'un propriétaire et d'un chemin de rechargement que vous avez réellement testé.

Testez les deux systèmes séparément

Les preuves issues d'un espace de noms ne disent rien de l'autre. Une requête exécutée dans le conteneur VPN prouve que le tunnel fonctionne pour ce conteneur. Ce n'est pas une preuve concernant l'hôte, et la même commande exécutée depuis un conteneur --network host n'est pas une preuve concernant le pont.

# host namespace
ip route get 1.1.1.1
ip -brief addr show

# a fresh container on the same bridge network
docker run --rm --network appnet alpine sh -c 'ip route; cat /etc/resolv.conf; ip -6 route'

Répétez ensuite le test dans le vrai conteneur de l'application plutôt que dans une image similaire. L'application peut porter une route supplémentaire, une variable d'environnement de proxy ou un résolveur différent, et chacune de ces choses change la réponse. Vérifiez la résolution de noms et la seconde famille d'adresses à chaque fois : les conteneurs sur des ponts définis par l'utilisateur résolvent via le résolveur intégré de Docker à 127.0.0.11, et une route par défaut IPv6 sur le pont peut faire sortir du trafic pendant que vous êtes occupé à tester un tunnel uniquement IPv4.

Lisez le schéma avant de toucher aux identifiants

Quand l'hôte fonctionne et que le conteneur ne fonctionne pas, la cause est structurelle presque à chaque fois, et faire tourner les clés ou réinstaller le client fait perdre un après-midi. Triez d'abord le symptôme et le correctif se nomme généralement tout seul.

What you seeUsual causeFirst check
Host exits through the VPN, container shows the ISP addressThe container never entered the tunnelDefault route inside the container
Container works until the daemon restarts, then stopsRules outside the supported hook, or a changed backendDaemon's reported firewall backend and DOCKER-USER
Container loses internal hosts after the tunnel comes upVPN prefix overlaps the Docker bridge subnetip route on the host, then docker network inspect
Small replies arrive, large ones hangTunnel MTU or missing MSS clampingLink MTU on both ends of the tunnel
Container falls back to the ISP address when the tunnel dropsNothing enforces the kill switch for forwarded trafficStop the tunnel with a request in flight

La ligne du chevauchement mérite sa propre phrase. Un profil VPN qui pousse une route pour 172.17.0.0/16, ou pour un 10.0.0.0/8 qui contient par hasard le préfixe de votre pont, fait que le tunnel revendique le sous-réseau des conteneurs, et les réponses vers les adresses traduites partiront dans la mauvaise direction ou disparaîtront. Renuméroter le réseau Docker est généralement plus propre que d'accumuler les exceptions.

Rendez la couverture explicite, puis cassez-la exprès

Deux conceptions résistent à la revue. Partagez l'espace de noms du conteneur VPN et chaque charge couverte utilise un seul tunnel avec une seule frontière de redémarrage, ce qui est simple jusqu'à ce qu'un service ait besoin d'une autre sortie ou de son propre chemin DNS. Ou bien acheminez le réseau des conteneurs vers une passerelle VPN sur l'hôte: transmission activée, le préfixe du pont pointé vers le tunnel, le trafic de retour traité explicitement. Cela achète un contrôle par réseau et vous coûte un jeu de règles à maintenir au fil des mises à niveau du démon et du client.

Quoi qu'il en soit, le test qui compte est le cas de défaillance. Arrêtez le tunnel avec une requête de conteneur en vol et regardez où va le paquet. S'il retombe sur le chemin direct, la couverture n'a jamais été appliquée, car un kill switch de client régit le trafic du client lui-même et non les paquets transmis depuis un pont. Redémarrez ensuite le démon et l'hôte et confirmez le résultat une seconde fois ; c'est le moment où les règles écrites à la main disparaissent.

Avant de déclarer terminé, confirmez quatre choses : l'interface du tunnel se trouve dans le même espace de noms que le trafic, le test du conteneur a été exécuté dans le vrai conteneur, la résolution de noms et les deux familles d'adresses ont été vérifiées, et le test de défaillance du tunnel échoue toujours en mode fermé. Notez ensuite le backend de pare-feu, la version du client et le propriétaire de chaque règle. C'est cette trace qui empêche la mise à niveau suivante de laisser en place un mélange non documenté d'anciennes hypothèses.

Articles similaires