Hook client-connect di OpenVPN: dove finisce davvero il tuo indirizzo
Uno script client-connect non può restituire un indirizzo stampandolo. Scopri come il file temporaneo dell'hook, lo stato di uscita e la topologia decidono l'IP del client.
Restituire un indirizzo da uno script client-connect sembra dover impostare l'IP del client. Non lo fa: l'unico canale dell'hook verso il server è un file temporaneo il cui percorso arriva come ultimo argomento dello script, e l'indirizzo va scritto lì nella sintassi di ifconfig-push. Uno script che stampa l'indirizzo o esce con zero senza scrivere nulla produce un client connesso con un indirizzo dal pool, e il log del server sembra perfettamente sano.
La confusione è ragionevole, perché diversi altri hook rispondono davvero tramite codici di uscita o variabili d'ambiente. Questo ha il proprio contratto, e il contratto inizia con un nome di file.
L'hook scrive un file, non un valore di ritorno
La dichiarazione sono due righe: script-security 2 per consentire la chiamata, poi client-connect /etc/openvpn/server/assign-address.sh. Quando un client si autentica, il server esegue quello script con il percorso del file generato aggiunto come ultimo argomento, che è $1 solo se la dichiarazione non passa argomenti aggiuntivi propri. Tutto ciò che viene applicato a quel client finisce in quel file come direttive ordinarie, una per riga, esattamente come apparirebbero in una configurazione. Un wrapper che aggiunge i propri parametri sposta la posizione, e il sintomo classico è uno script che scrive in un percorso che assomiglia a --verbose.
Lo stato di uscita è l'altra metà del contratto. Zero significa accetta e leggi il file; qualsiasi altra cosa rifiuta la connessione, ed è così che un'identità non mappata viene negata invece di ricadere silenziosamente nel pool. Scrivi la direttiva completa prima di restituire successo, così un file scritto a metà non diventi mai la configurazione di un client.
Due direttive meritano attenzione perché interagiscono con tutto il resto. ifconfig-push fissa l'indirizzo, mentre una route inviata dice al client cosa è raggiungibile attraverso il tunnel; restituisci la prima e dimentica la seconda e ottieni un client con un indirizzo e nessun posto dove andare. L'ordine nel file non conta, la completezza sì. Un hook che invia una rotta per un prefisso che nessun peer può raggiungere è lo stesso errore visto dall'altro capo.
Gli input arrivano tramite variabili d'ambiente: common_name, trusted_ip, ifconfig_pool_remote_ip e il resto. Trattali tutti come stringhe dall'esterno del server, mai come frammenti di shell.
La mappatura degli indirizzi è una politica, non aritmetica
Una scorciatoia tentante è analizzare il nome del certificato e usarne le cifre come ultimo ottetto. Non è un allocatore, e fallisce in modi ordinari: due nomi con lo stesso suffisso collidono, un nome che termina in 300 non è un indirizzo, e un certificato rinominato da un amministratore sposta silenziosamente un dispositivo. Convalida l'appartenenza alla sottorete, l'intervallo di host utilizzabile, gli indirizzi riservati e l'unicità a ogni chiamata, e tieni la mappatura in una tabella che una persona possa leggere.
Chi controlla il nome controlla l'indirizzo, quindi decidi cosa rappresenta realmente common_name in questa distribuzione. Con username-as-common-name attivo, l'identità è il login e non il soggetto del certificato. Non passare mai quel valore alla valutazione della shell, e non lasciare mai che il client influenzi un percorso di file.
Un allocatore basato su un registro deve sopravvivere alla concorrenza. Riserva l'indirizzo in modo atomico, gestisci una scrittura fallita senza ammettere un client a metà configurazione, e contrassegna ogni allocazione con la connessione che la possiede, così una disconnessione tardiva di una sessione più vecchia non possa liberare un indirizzo che una più nuova già detiene. Più istanze del server che condividono un pool rendono tale requisito più stringente, non opzionale.
La topologia decide cosa significa ifconfig-push
Con topology subnet su 10.77.0.0/24, ifconfig-push 10.77.0.20 255.255.255.0 è un indirizzo più la sua netmask. Sotto topology net30 il secondo argomento non è affatto una netmask ma l'indirizzo del peer, quindi copiare la forma di sottorete su un server net30 ti dà un client che si associa e non può far passare traffico. Cambia la topologia deliberatamente, mai per far sembrare coerente un esempio. Un server che già esegue net30 ha client attivi sul vecchio indirizzamento, quindi il passaggio è una migrazione con finestra di manutenzione, non una modifica di una riga.
Gli indirizzi statici devono stare fuori dal pool dinamico e fuori da ogni altra assegnazione statica, comprese quelle distribuite dai normali file client-config-dir. Se due meccanismi possono produrre lo stesso indirizzo per client diversi, uno dei due alla fine lo farà.
Prova quattro casi prima di fidarti
Esegui gli stessi controlli su un'identità mappata, una non mappata, una riconnessione e un guasto del registro simulato. openvpn --config client.ovpn --verb 4 mostra l'indirizzo assegnato nella risposta push, e sul server grep -A2 PUSH_REPLY /var/log/openvpn/openvpn.log conferma cosa è stato realmente inviato. Per l'identità non mappata il risultato atteso è una connessione rifiutata, non un indirizzo dal pool.
Poi ispeziona la directory in cui il demone scrive quei file temporanei. Lo script viene eseguito nel contesto di servizio ridotto, quindi un percorso scrivibile da root ma non dal demone trasforma ogni connessione in un rifiuto con un errore di permessi sepolto diverse righe più in basso nel log.
Assegnare non è autorizzare: un indirizzo fissato non dà comunque accesso a ogni destinazione dietro il server. Tieni la politica di routing e firewall nel proprio livello, e verificala rispetto all'indirizzo che hai appena fissato.