File di credenziali o socket di gestione di OpenVPN: quale scriptare
auth-user-pass copre il prompt dell'account; il socket di gestione copre tutti e quattro. Confronta l'esposizione, il costo di script e come ciascuno fallisce in un servizio.
Uno script che avvia OpenVPN e non si connette mai di solito non è un problema di rete: il client è fermo a un prompt senza un terminale su cui disegnarlo. OpenVPN ha esattamente due modi supportati per rispondere a quel prompt senza un umano, un file di credenziali dietro auth-user-pass e il socket di gestione, e non sono intercambiabili. Il file costa una direttiva e risponde a un prompt; il socket risponde a ogni prompt e consegna a qualunque cosa possa aprire una porta loopback un canale di controllo non autenticato. Quale appartenga al tuo deployment dipende da quale prompt sta davvero bloccando e da quale segreto puoi permetterti di lasciare a riposo.
Nomina il prompt prima di scegliere un meccanismo
Quattro prompt sembrano identici su un terminale. La coppia di account viene dalla configurazione PAM o auth-user-pass-verify del server e vi risponde auth-user-pass. La passphrase di una chiave privata appartiene al file della chiave e si legge tramite --askpass o si digita a mano. Un bundle PKCS#12 chiede una password propria. L'autenticazione proxy usa il file auth che --http-proxy prende, non le credenziali dell'account.
Quando OpenVPN gira come figlio di systemd, PHP o Node non c'è terminale, quindi ognuno di questi blocca in silenzio. Il processo resta lì, il registro si ferma dopo la riga del cifrario e l'unità alla fine va in timeout; quel silenzio è il motivo per cui le persone aggiungono un ciclo di retry o uno sleep invece di guardare quale prompt è in sospeso. Solo il socket di gestione può rispondere a tutti e quattro. Il file di credenziali risponde al primo e a nient'altro.
auth-user-pass: un file, un prompt
Il file contiene il nome utente sulla prima riga e la password sulla seconda, e nient'altro: niente virgolette, niente nomi di chiavi, niente commenti. Imposta il modo 600 e rendi proprietario l'account che esegue openvpn, che per un servizio è spesso un utente dedicato invece del tuo login. Un file di proprietà di root sotto /root è la differenza tra un comando che funziona nella tua shell e un'unità che fallisce all'avvio.
Due dettagli trasformano un file corretto in un prompt. Un editor che salva in CRLF lascia un ritorno carrello incollato alla password, così il server rifiuta una password che nel file sembra corretta; controlla con cat -A o od -c invece che a occhio. E un auth-user-pass senza argomento dentro il profilo significa «chiedi all'utente» per progetto, cosa che una seconda direttiva sulla riga di comando potrebbe non annullare, perché il profilo viene analizzato dove si trova.
L'ordine delle opzioni è la terza trappola, e dipende dalla versione. Alcune build di OpenVPN 2.4 ignorano --auth-user-pass quando compare prima di --config, quindi le stesse due opzioni nell'altro ordine si connettono. Metti la direttiva dentro il profilo, o dopo --config sulla riga di comando, e cerca nel profilo ogni occorrenza prima di cambiare altro.
auth-nocache appartiene a questo meccanismo ma non fornisce nulla. Limita quanto a lungo il client tiene la credenziale in memoria, il che aiuta dopo un crash e per niente quando il file manca. Una riconnessione dopo un timeout di sessione lato server rilegge il file, quindi una credenziale che funzionava all'avvio può comunque scadere a metà giornata.
Il socket di gestione: ogni prompt, più superficie
Avvia il client con un listener e dichiara esplicitamente che i prompt verranno riportati su di esso, usando le direttive documentate nel manuale di OpenVPN 2.6:
management 127.0.0.1 3030
management-query-passwords
Da bash, tieni la connessione su un descrittore di file libero e rispondi per nome del prompt invece che per posizione, perché una riconnessione può chiedere in un ordine diverso:
exec 3<>/dev/tcp/127.0.0.1/3030
while read -r -u 3 line; do
case "$line" in
*"Need 'Auth' username/password"*)
echo 'username "Auth" alice' >&3
echo 'password "Auth" secret' >&3 ;;
*"Need 'Private Key' password"*)
echo 'password "Private Key" secret' >&3 ;;
esac
done
Il confine è il trasporto. Il protocollo di gestione è testo in chiaro su TCP in loopback, quindi il listener non deve essere pubblicato su un indirizzo LAN o di tunnel per farci arrivare un controller remoto; un controller remoto significa un inoltro SSH, non un binding più ampio. Il demone della community accetta anche un solo client di gestione alla volta, quindi una GUI o un monitor non possono condividere il socket con il tuo script. La seconda connessione viene rifiutata, non messa in coda, e il guasto sembra uno script diventato sordo.
Quel socket è anche un canale di controllo. Senza una password di gestione, qualunque cosa possa connettersi a 127.0.0.1:3030 può leggere lo stato, uccidere il tunnel o rispondere da sola a un prompt. La password di gestione e il file di credenziali sono segreti della stessa classe, e entrambi meritano la stessa cura.
Quale si adatta al deployment
Scegli il file di credenziali quando l'unico prompt è la coppia di account, il client gira come servizio con un proprietario stabile e puoi tenere un file 600 fuori da backup che non controlli. Scegli il socket di gestione quando c'è di mezzo una passphrase di chiave o una password PKCS#12, quando il wrapper già supervisiona il processo, o quando devi osservare stato e prompt mentre esegui il debug di una riconnessione.
Due condizioni spostano la decisione. Se il wrapper è /bin/sh, un'unità systemd che chiama il binario direttamente, o un container minimo, la colla del socket solo per bash non è disponibile e il file è la risposta più economica. Se il segreto ruota spesso, nessuno dei due file è una buona casa: interroga un portachiavi da un piccolo wrapper e lascia che l'unità chiami il wrapper, il che tiene la rotazione in un punto solo e la password fuori dalla cronologia della shell.
La sostituzione di processo sta tra i due e non appartiene a nessuno. La riga singola qui sotto funziona perché bash la espande prima che openvpn parta, non perché il client capisca la sintassi:
openvpn --config client.ovpn --auth-user-pass <(printf "%s
%s
" "$USER" "$PASS")
Si rompe sotto /bin/sh, in un'unità che aggira la shell e in immagini senza bash o /dev/fd. Tienila per i test interattivi, mai come il contratto da cui dipende un servizio.
Proteggi il segreto, poi verifica dal registro
Entrambi i meccanismi lasciano un segreto leggibile dal processo OpenVPN. Non mettere mai la password sulla riga di comando o nel file dell'unità, dove ps e systemctl la mostrano a ogni utente locale; passa il percorso del file, non il valore che contiene. Dove i certificati possono sostituire la coppia di account, ciò elimina la categoria di prompt invece di nasconderla.
Un'esecuzione corretta mostra la sequenza di prompt completarsi e la sessione raggiungere «Initialization Sequence Completed». Se il client si ferma allo stesso prompt, la corrispondenza è fallita: stampa le righe esatte inviate dal socket, controlla le virgolette e il nome del prompt, e correggi il modello invece di aggiungere uno sleep. Un tunnel che riporta successo e non trasporta nulla è un problema di routing o di push, non di credenziali.
Poi prova la riconnessione, non solo il primo avvio. Interrompi la sessione lato server o attendi il suo timeout; il client richiede di nuovo, ed è lì che un file che sembrava corretto all'avvio mostra se è ancora leggibile dall'account che si riconnette.