Perché un container Docker non raggiunge gli host dietro la VPN
I container in bridge hanno il proprio spazio dei nomi, quindi il tunnel dell’host è invisibile per loro. Confrontate la rete dell’host, un dispositivo tun e macvlan prima di modificare il DNS.
Un container che non raggiunge nulla dietro la VPN aziendale vive di solito in uno spazio dei nomi di rete diverso da quello del tunnel. Le rotte, i server DNS e i contrassegni del firewall del client dell’host appartengono al suo stack; un container in rete bridge ha il proprio. Questo unico fatto spiega la maggior parte delle segnalazioni che arrivano a un team di piattaforma, ed è anche il motivo per cui «la VPN è rotta» e «Docker aggira la VPN» possono essere entrambe sbagliate nello stesso momento. Due configurazioni molto diverse si nascondono dietro un solo sintomo, quindi identificate quale avete prima di cambiare un’impostazione.
Scoprite a quale spazio dei nomi appartiene il tunnel
Su Docker Desktop per macOS o Windows i container vengono eseguiti dentro una macchina virtuale Linux. Un client VPN installato sul desktop crea un tunnel nel sistema operativo del desktop, e la macchina virtuale non lo vede mai. Nessuna configurazione del container lo risolve, perché l’interfaccia di cui il container avrebbe bisogno non esiste affatto nel suo spazio dei nomi di rete.
Su un host Linux il tunnel vive nello spazio dei nomi dell’host, e i container in bridge di solito lo raggiungono tramite la tabella di routing dell’host con NAT di origine applicato in uscita. Quando quel percorso fallisce, la causa è di solito la politica di inoltro, il filtraggio del percorso inverso o una sottorete sovrapposta più che una rotta mancante. Confermate in quale dei due mondi vi trovate prima di leggere oltre:
docker info --format '{{.OperatingSystem}}'
docker run --rm --net=host alpine ip route | head -5
docker run --rm alpine ip route | head -5
La differenza tra queste due tabelle di routing è tutto il problema su un solo schermo. Se la vista dell’host contiene l’interfaccia del tunnel e quella del bridge no, state guardando l’isolamento degli spazi dei nomi, e tutto il resto è una scelta su come attraversarlo.
La rete dell’host è un test, non un progetto
Eseguire un container con la rete dell’host, che Docker documenta come condivisione dello spazio dei nomi di rete dell’host, lo fa ereditare direttamente l’interfaccia del tunnel e le sue rotte. Se la stessa richiesta poi riesce, la teoria delle rotte è confermata e il guasto è l’isolamento degli spazi dei nomi, non le credenziali, il DNS o una regola di accesso.
docker run --rm --net=host curl -sS -o /dev/null -w '%{http_code}\n' https://api.internal/health
Tenetelo come diagnostica. La rete dell’host rinuncia alla pubblicazione delle porte e alla maggior parte dell’isolamento che volevate da un container, e su Docker Desktop «host» significa la macchina virtuale, non il desktop, quindi un successo o un fallimento lì può ingannarvi se dimenticate quale host stavate indirizzando.
Un dispositivo tun e i privilegi che comporta
Un client VPN dentro il container ha bisogno di un dispositivo di tunnel e del permesso di configurarlo. La scorciatoia comune è --privileged, che concede ogni dispositivo e allenta la politica di sicurezza su tutto il container. È molto più di quanto richieda questo lavoro, ed è difficile giustificarlo in una revisione.
sudo modprobe tun
docker run --rm -it --device /dev/net/tun --cap-add NET_ADMIN myvpn-client
La forma più stretta mappa il singolo nodo del dispositivo e aggiunge solo la capacità di amministrazione della rete. Caricate prima il modulo sull’host perché il nodo esista. Se il container si avvia e il client segnala che non riesce ad aprire il dispositivo di tunnel, controllate la mappatura prima di sospettare del profilo, perché quell’errore di solito è letterale.
Questo progetto significa anche che il client viene eseguito per container. È la risposta giusta quando ogni carico di lavoro ha bisogno del proprio tunnel, e quella sbagliata quando volevate solo che il tunnel dell’host fosse visibile, cosa che il team di piattaforma probabilmente dava per scontata.
Il NAT del bridge cambia la sorgente che vede la VPN
Con il bridge predefinito, il traffico in uscita del container viene tradotto nell’indirizzo dell’host. Dietro una VPN il lato aziendale vede quindi l’indirizzo del tunnel dell’host e non quello del container, le regole di accesso per sorgente si comportano come se la richiesta l’avesse fatta l’host, e due container diventano indistinguibili per il server VPN.
macvlan o ipvlan assegna invece al container un indirizzo LAN, il che aiuta la scoperta sulla LAN e fa sembrare il container un pari sul segmento. Aggira anche del tutto il tunnel dell’host, quindi un container così non raggiungerà i prefissi riservati alla VPN a meno che la VPN non sia raggiungibile anche da quella LAN.
Entrambe le forme sono legittime; rispondono a requisiti diversi. Se il requisito è «questo container deve raggiungere un servizio riservato alla VPN», scegliete tra instradare attraverso l’host e terminare il tunnel dentro il container. Scrivete questa scelta prima della distribuzione, non dopo la prima richiesta fallita. Qualunque cosa scegliate, registrate l’indirizzo sorgente che vedrà l’estremità remota, perché è quello che mostreranno le sue regole di accesso e i suoi log. Un progetto che non sa dichiarare quell’indirizzo non è finito.
Il DNS segue il demone, non il tunnel
Un container riceve la sua configurazione del resolver all’avvio, copiata dal demone o dall’immagine. Gli indirizzi che la VPN invia dopo la connessione arrivano troppo tardi per quella copia, e vengono inviati al resolver dell’host e non nel container.
Impostate il resolver esplicitamente per il servizio, oppure puntatelo a un resolver interno raggiungibile tramite il tunnel. Un container che risolve un nome privato in un indirizzo pubblico di solito usa un resolver pubblico, non soffre di una rotta rotta, e la correzione appartiene al file compose o alla configurazione del demone.
Lo stesso ragionamento vale per la questione dell’indirizzo di uscita. Se ogni container deve apparire da un indirizzo VPN diverso, un solo tunnel dell’host non può fornirlo perché il NAT li riduce a un’unica sorgente. Le forme praticabili sono un client nello spazio dei nomi di ogni container o un sidecar che possiede il tunnel e condivide il suo stack, ed entrambe cambiano il modo in cui l’applicazione viene indirizzata.
Lista di controllo prima di chiudere il ticket: identificate lo spazio dei nomi che possiede il tunnel, dimostrate il percorso con la rete dell’host, mappate solo il dispositivo tun necessario, ricordate l’indirizzo sorgente dopo il NAT, impostate il resolver esplicitamente e trattate gli indirizzi di uscita per container come una decisione architetturale e non come un flag.