Errore di client-connect in OpenVPN: la pulizia al disconnect è garantita?
Un client rifiutato non annulla una regola del firewall né un record scritto. Confrontate il contratto del callback con il rollback che il vostro hook deve eseguire da solo.
Quando uno script client-connect esce con un codice diverso da zero, OpenVPN rifiuta il client, e gli operatori di solito presumono che lo script client-disconnect corrispondente sistemi tutto. Spesso non viene mai eseguito, perché l’elaborazione del disconnect dipende da ciò che la fase connect è riuscita a completare. Trattare rifiuto e rollback come un unico meccanismo è ciò che trasforma un accesso fallito in una regola del firewall trapelata o in un record orfano. Sono due obblighi separati, e solo uno dei due è automatico. Leggete il percorso di uscita proprio dell’hook prima di dare la colpa al server per lo stato lasciato indietro.
Un’uscita diversa da zero rifiuta il client; non annulla il vostro lavoro
Lo stato di uscita di client-connect è una decisione di ammissione. Zero significa ammettere, qualsiasi altra cosa significa smantellare l’istanza prima che sia pienamente stabilita. Nulla in quel contratto ispeziona ciò che lo script ha già fatto al mondo esterno. Se lo script ha aggiunto una regola iptables, scritto una riga o incrementato un contatore, quelle modifiche sopravvivono al rifiuto.
La stessa asimmetria vale per i plugin, dove più funzioni connect possono essere eseguite per una singola istanza. Un errore successivo non annulla retroattivamente un successo precedente, e la funzione precedente non ha modo di sapere che una funzione sorella è fallita.
Il rifiuto è automatico, la pulizia no
Il manuale di OpenVPN 2.6 lega l’elaborazione di client-disconnect al successo dell’elaborazione connect precedente. Un’eccezione documentata copre le catene a cascata: se un passo connect ha già restituito successo, i passi disconnect corrispondenti vengono comunque eseguiti quando l’istanza viene smantellata, anche se un passo connect successivo è fallito. Quell’eccezione è stretta e dipende da quanti callback sono configurati, per questo testare un solo script shell non vi dice nulla su una catena.
Mappate la configurazione reale prima di progettare attorno a essa: quale script, quale plugin e in quale ordine vengono eseguiti. La tabella seguente elenca ciò che lascia dietro di sé un’installazione a metà.
| What the hook did before failing | What the client sees | What stays behind |
|---|---|---|
| Created a firewall entry | A rejection, with no tunnel | A rule matching an address nobody owns |
| Wrote a state row | A rejection, with no tunnel | A record of a session that never started |
| Allocated a tunnel address | A rejection, with no tunnel | An address marked busy until something releases it |
Ogni riga finisce allo stesso modo per il client e in modo diverso per l’host. Il client non può sapere quale si è verificata, quindi l’hook deve disfare ciò che ha fatto prima di restituire lo stato di errore.
Compensate dentro lo script che è fallito
Validate tutto il possibile prima di toccare lo stato condiviso: campi del certificato, disponibilità dell’indirizzo, gruppo a cui appartiene il client. Il rifiuto meno costoso è quello che avviene prima di qualsiasi allocazione. Una volta che l’installazione copre più operazioni esterne, tenete un registro di quali sono state completate e disfatele in ordine inverso prima di restituire lo stato di errore.
Un caso concreto: lo script crea una voce del firewall per client, poi non riesce a scrivere la riga di stato che la descrive. Il ramo che fallisce dovrebbe rimuovere la voce appena creata e poi uscire con un codice diverso da zero. Aspettare un evento disconnect che forse non verrà mai pianificato è il modo in cui una regola sopravvive alla sessione per cui era pensata.
Rendete la compensazione idempotente e attribuita
La pulizia deve essere sicura da eseguire due volte e precisa su ciò che può eliminare. Un evento tardivo di un tentativo precedente non dovrebbe rimuovere l’allocazione di una sessione più recente solo perché entrambe condividono un nome comune o un indirizzo di tunnel riciclato. Registrate l’handle o l’id di operazione specifico insieme alla risorsa, ed eliminate per quella chiave invece che per nome. Le transazioni del database aiutano solo con le modifiche proprie del database; una regola del firewall fuori da quel confine richiede un passo di compensazione esplicito.
Presupponete che il processo possa morire tra la modifica e la pulizia, e aggiungete un riconciliatore che confronta lo stato esterno con l’elenco dei client ammessi secondo un calendario.
Testate i tre percorsi che differiscono davvero
Esercitate tre casi: un rifiuto prima che sia allocato qualcosa, uno dopo un’allocazione parziale e una sessione ammessa normale che poi termina. Quei tre producono righe di log diverse e residui diversi. Verificate lo stato esterno dopo ciascuno, non solo il messaggio di errore del client, perché il fallimento del client è il risultato atteso in due dei tre.
Registrate la fase dell’hook, lo stato di uscita e ciò che è stato allocato o rilasciato, senza le credenziali. Un log dell’hook che nomina la risorsa creata è ciò che rende una regola trapelata rintracciabile sei mesi dopo.