OpenVPN agisce su esattamente un output di uno script di accesso: lo stato di uscita. Tutto il resto che lo script fa è decorazione. Un hook può raggiungere il verificatore giusto, analizzare la risposta e stampare una riga amichevole restituendo però uno stato che significa l’opposto di ciò che intendeva il suo autore. Leggi prima il contratto e scrivi l’adattatore attorno a esso, e tutta quella classe di bug sparisce.

Il contratto è piccolo: identifica l’account inviato, poni al verificatore approvato la domanda prevista e accetta solo l’esito positivo documentato di quel verificatore per quella richiesta. Tutto il resto è impianto.

I tre stati che l’hook può restituire

Per auth-user-pass-verify, il manuale di OpenVPN 2.6 definisce il codice di uscita 0 come accettazione, 1 come rifiuto e 2 come autenticazione differita. Sono le uniche tre risposte che il server comprende, e ciascuna ha una conseguenza diversa per la connessione. Uno script che esce con lo stato del suo ultimo comando ha di fatto lasciato che una chiamata di log o un passo di pulizia voti. Scrivi lo stato esplicitamente in ogni ramo, compresi i rami che non hanno mai raggiunto il verificatore.

Il successo HTTP e il successo dell’autenticazione sono livelli separati, e confonderli è il modo più comune in cui questo hook sbaglia. Una risposta 202 significa che la richiesta è stata accettata per l’elaborazione, non che una password sia stata verificata. Definisci lo stato atteso, la forma della risposta e il verdetto finale dal contratto API che ti è stato dato, e non inventare un nome di campo perché sembra plausibile.

Se la risposta arriva davvero più tardi, quello è il percorso differito e va costruito come tale. Un tentativo differito che non viene mai completato trattiene risorse finché il timeout non scade.

Leggi il file delle credenziali esattamente com’è scritto

Con il metodo documentato via file, il server scrive nome utente e password come prime due righe di un file temporaneo, passa quel percorso allo script e rimuove il file quando lo script ritorna. Leggi quelle due righe come dati: non fare mai source del file, non valutarlo mai e non incollare mai il suo contenuto in una riga di comando dove la tabella dei processi possa vederlo. Conserva la password byte per byte, perché normalizzarla come normalizzeresti un nome utente cambia silenziosamente la credenziale. La directory usata per quel file temporaneo è una decisione di distribuzione, quindi verifica che esista e che non sia leggibile da tutti sull’host.

Tieni la credenziale del verificatore separata da quella dell’utente: appartiene all’integrazione del gateway, vive nel vault dei segreti della piattaforma e necessita solo del permesso di verificare, quindi non è prova che una determinata persona si sia autenticata. Non lasciare nemmeno che l’hook diventi un secondo verificatore di password. Reimplementare a mano un confronto di hash non diventa sicuro ottimizzando il ciclo, e un verificatore mantenuto comprende già lo schema salato e costoso dell’archivio degli account.

Decidi cosa significa una risposta non classificabile

Timeout, errori di trasporto, risposte malformate e un backend non disponibile condividono una proprietà: nessuno di essi è una verifica positiva. L’hook dovrebbe fallire in chiuso per ciascuno di essi e registrare un motivo su cui un operatore possa agire. Quel motivo appartiene a un log protetto, non a un messaggio restituito alla persona che tenta di connettersi.

Sii altrettanto attento con i percorsi che non raggiungono mai il verificatore. Un valore di configurazione mancante, un segreto illeggibile o una dipendenza non installata dovrebbero produrre un rifiuto con un marcatore operativo distinto invece di un’accettazione accidentale. Prova quei rami rimuovendo la dipendenza in laboratorio invece di ragionarci sopra. Dove un fallimento della registrazione può cambiare lo stato di uscita, la registrazione è diventata parte della decisione e va resa innocua.

Anche la configurazione del server conta: gli script definiti dall’utente sono rispettati solo quando la configurazione in esecuzione li consente al livello di sicurezza richiesto, e quell’impostazione è una scelta deliberata, non un valore predefinito da alzare.

Verifica il verdetto, non la riga stampata

Costruisci un test che ispezioni direttamente lo stato di uscita. Scrivi un file di credenziali di esempio, esegui l’hook su di esso con un account notoriamente buono e uno notoriamente cattivo, e leggi lo stato riportato dalla shell invece del testo stampato dallo script. Quell’unico controllo intercetta la maggior parte dei bug di contratto prima che un client si connetta.

Poi fai passare gli stessi casi attraverso un client reale contro un listener fuori produzione e conferma ciò che il server ha registrato: accettato, rifiutato o differito. Copri un account disabilitato, una password errata, una risposta malformata del verificatore, un timeout e un verificatore irraggiungibile. Se un caso finisce in uno stato che non sai nominare, l’adattatore non è finito.

Tieni la corrispondenza tra esito del verificatore e stato di uscita accanto allo script nel repository, così il prossimo ingegnere può leggere il contratto senza fare reverse engineering del codice.

Articoli correlati