Il traffico dei container Docker aggira la VPN dell'host: come verificarlo
Una VPN dell'host copre solo lo spazio dei nomi dell'host. Usate questi controlli per scoprire se il traffico dei container sul bridge entra nel tunnel o esce direttamente.
Il browser sull'host mostra l'indirizzo di uscita della VPN, il container riporta la vostra connessione di casa e qualcuno lo chiama leak. Non è trapelato nulla. Un tunnel dell'host è un insieme di interfacce, rotte e regole che appartengono allo spazio dei nomi di rete dell'host, e un container su un bridge è un altro spazio dei nomi con la propria rotta predefinita. Sono stato su uno stack Compose dove il client VPN era connesso da settimane e nemmeno un pacchetto di container vi era mai entrato.
Controllate lo spazio dei nomi prima dell'icona nella barra
Gli spazi dei nomi sono tutta l'argomentazione. Quando Docker avvia un container su una rete bridge, crea uno spazio dei nomi di rete, vi collega un'estremità di una coppia veth e dà a quello spazio dei nomi un indirizzo e una rotta predefinita. L'interfaccia del tunnel, comunque si chiami, vive nello spazio dei nomi dell'host. Il traffico dentro il container non può vederla, e nessuna impostazione del client cambia questo dall'esterno.
Due mosse supportate vi tolgono dall'isolamento. --network host mette il container dentro lo spazio dei nomi dell'host, così eredita le rotte dell'host e quindi il tunnel. --network container:vpn condivide lo spazio dei nomi di un altro container, ed è così che la maggior parte dei progetti sidecar manda un carico di lavoro attraverso un container VPN. Entrambe sono scelte esplicite; di solito solo una delle due è ciò che intendeva chi effettua il deployment.
La modalità host non è un aggiornamento gratuito. Il container smette di avere un proprio indirizzo, la pubblicazione delle porte smette di valere, e un processo che detiene NET_ADMIN lì può riscrivere il routing dell'host. Accettarlo può essere la decisione giusta. Dare per scontato che sia già attivo è il modo in cui le persone passano una serata a fare debug di un tunnel che non è mai stato nel percorso.
Seguite il pacchetto fuori dal bridge
Un container sul bridge invia il suo pacchetto al gateway predefinito, che è l'interfaccia del bridge sull'host. L'host inoltra poi il pacchetto e, nella configurazione predefinita, traduce l'indirizzo di origine con il proprio prima che il pacchetto esca. È quel passaggio di traduzione che sconfigge silenziosamente la policy.
Quando il pacchetto raggiunge l'endpoint del tunnel porta l'indirizzo dell'host. Qualsiasi regola, rotta di policy o decisione di tunnel diviso basata sull'indirizzo originale del container smette di corrispondere, e due container destinati a uscire da uscite diverse non si possono distinguere solo dall'origine. Se vi serve un routing per container, la decisione deve avvenire prima della traduzione o dentro uno spazio dei nomi che controllate.
Docker mantiene da sé queste regole, attraverso qualunque backend del firewall con cui è stato configurato il demone. Le regole scritte a mano fuori dall'hook supportato per il traffico inoltrato, DOCKER-USER, possono essere riordinate o eliminate quando il demone si riavvia o l'host viene aggiornato. La documentazione di Docker sul filtraggio dei pacchetti mostra l'ordine delle catene; il punto pratico è che ogni regola ha bisogno di un proprietario e di un percorso di ricaricamento che abbiate davvero provato.
Provate i due sistemi separatamente
Le prove di uno spazio dei nomi non dicono nulla sull'altro. Una richiesta eseguita dentro il container VPN dimostra che il tunnel funziona per quel container. Non è una prova sull'host, e lo stesso comando eseguito da un container --network host non è una prova sul bridge.
# 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'
Poi ripetete il test dentro il container reale dell'applicazione invece che in un'immagine simile. L'app può portare una rotta in più, una variabile d'ambiente per il proxy o un resolver diverso, e ciascuna di queste cose cambia la risposta. Controllate la risoluzione dei nomi e la seconda famiglia di indirizzi ogni volta: i container su bridge definiti dall'utente risolvono tramite il resolver integrato di Docker a 127.0.0.11, e una rotta predefinita IPv6 sul bridge può portare traffico fuori mentre siete occupati a testare un tunnel solo IPv4.
Leggete lo schema prima di toccare le credenziali
Quando l'host funziona e il container no, la causa è quasi sempre strutturale, e ruotare le chiavi o reinstallare il client fa perdere un pomeriggio. Ordinate prima il sintomo e la correzione di solito si nomina da sola.
| What you see | Usual cause | First check |
|---|---|---|
| Host exits through the VPN, container shows the ISP address | The container never entered the tunnel | Default route inside the container |
| Container works until the daemon restarts, then stops | Rules outside the supported hook, or a changed backend | Daemon's reported firewall backend and DOCKER-USER |
| Container loses internal hosts after the tunnel comes up | VPN prefix overlaps the Docker bridge subnet | ip route on the host, then docker network inspect |
| Small replies arrive, large ones hang | Tunnel MTU or missing MSS clamping | Link MTU on both ends of the tunnel |
| Container falls back to the ISP address when the tunnel drops | Nothing enforces the kill switch for forwarded traffic | Stop the tunnel with a request in flight |
La riga della sovrapposizione merita una frase propria. Un profilo VPN che spinge una rotta per 172.17.0.0/16, o per una 10.0.0.0/8 che contiene per caso il prefisso del vostro bridge, fa sì che il tunnel rivendichi la sottorete dei container, e le risposte verso gli indirizzi tradotti andranno nella direzione sbagliata o svaniranno. Rinumerare la rete Docker è di solito più pulito che accumulare eccezioni.
Rendete esplicita la copertura, poi rompetela di proposito
Due progetti reggono la revisione. Condividete lo spazio dei nomi del container VPN e ogni carico coperto usa un solo tunnel con un solo confine di riavvio, il che è semplice finché un servizio non ha bisogno di un'uscita diversa o del proprio percorso DNS. Oppure instradate la rete dei container verso un gateway VPN sull'host: inoltro abilitato, il prefisso del bridge puntato al tunnel, il traffico di ritorno gestito esplicitamente. Questo compra il controllo per rete e vi costa un insieme di regole da mantenere attraverso gli aggiornamenti del demone e del client.
In ogni caso, il test che conta è il caso di guasto. Fermate il tunnel con una richiesta di container in volo e guardate dove va il pacchetto. Se ricade sul percorso diretto, la copertura non è mai stata applicata, perché un kill switch del client governa il traffico del client stesso e non i pacchetti inoltrati da un bridge. Riavviate poi il demone e l'host e confermate il risultato una seconda volta; è il momento in cui le regole scritte a mano scompaiono.
Prima di dichiararlo finito, confermate quattro cose: l'interfaccia del tunnel è nello stesso spazio dei nomi del traffico, il test del container è stato eseguito dentro il container reale, la risoluzione dei nomi e entrambe le famiglie di indirizzi sono state controllate, e il test di guasto del tunnel fallisce ancora in modo chiuso. Poi annotate il backend del firewall, la versione del client e il proprietario di ogni regola. È quella registrazione che impedisce al prossimo aggiornamento di lasciare in piedi un misto non documentato di vecchie ipotesi.