Tre host Linux, un progetto: un hub OpenVPN raggiungibile pubblicamente, un portatile consumer e una macchina di uscita dietro un normale router domestico che non inoltra nulla verso l'interno. L'uscita chiama l'hub da sola. Poi il traffico Internet del consumer entra nel tunnel sul portatile ed esce dall'uplink proprio dell'uscita. Tutto ciò che sorprende in questa disposizione deriva da un dettaglio: è stata l'uscita ad avviare la connessione, quindi il percorso verso l'interno esiste già e non serve alcun port forwarding per crearlo.

Quanto segue traccia il percorso del pacchetto e non l'ordine della configurazione, perché la configurazione ha senso solo quando si sa quale componente possiede quale decisione. Ogni livello possiede esattamente una decisione, e gli errori si concentrano dove due livelli vengono presi per uno.

Due sessioni di trasporto, un percorso interno

Ogni host mantiene la propria connessione esterna all'hub, e nessuna di queste connessioni sa dell'altra. Il traffico del consumer verso Internet è un flusso interno separato che viene trasportato dentro il tunnel del consumer e poi reimmesso dal tunnel dell'uscita. È questo flusso interno che l'hub deve instradare ed è quello che l'uscita deve inoltrare.

outer: consumer --TLS--> hub
outer: exit     --TLS--> hub
inner: consumer --tun--> hub --internal--> exit --tun--> uplink

Poiché entrambe le connessioni esterne partono dal lato client, il router dell'uscita non vede mai un SYN in ingresso e non ha mai bisogno di una regola NAT di destinazione. Deve solo mantenere vivo il trasporto stabilito, cosa che già fa per qualsiasi connessione di lunga durata. Nulla sulla Internet pubblica deve raggiungere prima l'uscita, per questo un indirizzo dinamico su quell'uplink non è un problema. Il router deve comunque consentire la sessione in uscita, ma è lo stesso permesso che un browser già usa.

Cosa installa davvero iroute dentro il server

L'hub è l'unico dispositivo che decide quale peer possiede quale destinazione, e prende quella decisione nella propria tabella di routing dello spazio utente, non in quella del kernel. Una voce client-config-dir per l'uscita chiamata exit-node può rivendicare lì il prefisso predefinito:

# /etc/openvpn/server/ccd/exit-node
iroute 0.0.0.0 0.0.0.0

Quella riga dice al processo server "i pacchetti interni con qualsiasi destinazione appartengono a questo client". Non aggiunge una rotta predefinita al sistema operativo dell'hub e non rende l'hub un gateway Internet. Riferimento di OpenVPN 2.6 tiene il routing interno specifico del client e il routing del kernel come meccanismi separati, e quella separazione è la ragione per cui il progetto funziona senza toccare la rotta predefinita dell'hub.

Il consumer ha bisogno dell'istruzione speculare, consegnata come push, perché il suo traffico entri nel tunnel invece di uscire dal gateway locale. Con la topologia a sottorete il pool è una rotta connessa sul dispositivo tun dell'uscita, quindi la direzione di ritorno non ha bisogno di alcun push aggiuntivo; ciò che l'uscita non deve ricevere è il reindirizzamento qui sotto, che rimanderebbe il suo traffico in uscita nel tunnel che sta cercando di usare. Invialo solo al consumer e leggi le rotte applicate sul client invece di fidarti che il file CCD sia stato scelto.

# /etc/openvpn/server/ccd/consumer-01
push "redirect-gateway def1"

È il kernel dell'uscita a fare l'inoltro

Dentro l'uscita, un pacchetto del consumer arriva sull'interfaccia tun indirizzato a un host Internet qualsiasi. Il kernel consulta la sua tabella di routing, trova l'uplink reale come migliore corrispondenza e poi applica due politiche indipendenti: se il pacchetto può essere inoltrato e se può uscire da quell'interfaccia con quell'indirizzo di origine. L'inoltro è un sysctl, ed è disattivato per impostazione predefinita su molte distribuzioni.

Quindi l'uscita ha bisogno dell'inoltro IPv4 attivo in modo persistente, di una regola che ammetta le sorgenti del tunnel del consumer verso l'uplink e del percorso di risposta di ritorno attraverso il tun. Attivare l'inoltro per l'intero host cambia anche il comportamento di tutto ciò che viene instradato attraverso di esso, per questo la regola dovrebbe nominare sorgenti e interfacce invece di accettare tutto.

È la traduzione della sorgente a comprare il percorso di ritorno

Un host Internet non ha alcuna rotta verso un indirizzo 10.90.0.x, quindi le risposte verrebbero scartate prima ancora di raggiungere l'uscita. Riscrivere la sorgente mentre il pacchetto lascia l'uplink risolve il problema, e il connection tracking annulla la riscrittura al ritorno.

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.90.0.0/24 -o enp1s0 -j MASQUERADE
ip -4 route get 10.90.0.9   # run on the exit: must point at the tun

La regola sopra è un punto di partenza per una sola interfaccia uplink, non una politica firewall. Restringi la sorgente alle assegnazioni che hai davvero autorizzato e tieni separata la politica di ingresso dell'uscita, così il mascheramento del traffico del tunnel non espone in silenzio servizi locali. Su un host con indirizzo uplink dinamico, il mascheramento ti risparmia anche di seguire l'indirizzo a mano.

Dove l'hub può imporre e dove non può

Con client-to-client e il tradizionale percorso dati nello spazio utente, l'hub consegna i pacchetti interni tra peer dentro il processo del server. Non attraversano mai la catena FORWARD del kernel dell'hub, quindi una regola del kernel lì non può limitare il transito tra peer, per quanto scritta con cura. L'applicazione deve vivere nella politica del server stesso: chi può connettersi, quale file CCD riceve e se la proprietà predefinita dell'uscita viene concessa.

Per questo un iroute predefinito è una decisione di fiducia ampia. L'uscita deve accettare legittimamente traffico di ritorno da indirizzi Internet arbitrari, e deve essere una macchina che controlli, aggiorni e monitori. Concedere la stessa proprietà a un peer a cui non affideresti un router è l'errore a cui questo progetto invita.

L'appartenenza è il vero controllo. Qualsiasi client che possa configurare le proprie rotte può provare a usare l'uscita, quindi limitare chi può connettersi, o a quali peer l'uscita servirà, fa parte del progetto e non è un ripensamento.

Fallire in sicurezza quando l'uscita sparisce

Metti l'uscita offline e la proprietà predefinita interna sparisce con la sua sessione. Quello che non deve succedere è che l'hub diventi silenziosamente l'uscita sostitutiva: nessuna rotta predefinita dal pool VPN all'uplink dell'hub, e nessuna regola di mascheramento ampia sull'hub che inoltrerebbe volentieri lo stesso traffico. Prova quel guasto di proposito, poi prova il ripristino con un flusso nuovo e non con una pagina già aperta.

# on the consumer, with the exit up and then down
ip -4 route get 1.1.1.1
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com

Se il traffico del consumer raggiunge ancora Internet dopo che l'uscita è sparita, qualcos'altro lo sta trasportando e il confine che volevi non esiste. Tratta DNS e IPv6 come decisioni separate: una rotta IPv4 più il mascheramento non dice nulla sul traffico AAAA, e un resolver spinto dall'hub risponderà da dove la query esce davvero.

Articoli correlati