Il servizio VPN fallisce a EXEC: confronta unit, binario e sudoers
Un errore allo step EXEC significa che systemd non è riuscito ad avviare il binario. Confronta il percorso dell’unit, il percorso installato e l’autorizzazione sudoers prima di modificare qualsiasi cosa.
Ieri il tunnel funzionava, e stamattina l’unit è morto con Failed at step EXEC spawning /usr/local/bin/vpnclient nel journal. Credenziali, route e account non entrano mai in quella frase. systemd ha risolto ExecStart in un percorso, ha chiesto al kernel di eseguirlo, e il kernel ha rifiutato, il più delle volte perché a quel percorso non esiste nulla o perché il file non è eseguibile. Tratta il guasto come un problema di file e argv finché il comando analizzato non dimostra il contrario.
Perché EXEC fallisce prima che inizi l’autenticazione
systemd non esegue ExecStart tramite una shell. Fa un fork, costruisce un vettore di argomenti dalla riga e chiama execve sul primo elemento. Se quella chiamata fallisce, il servizio non raggiunge mai il codice proprio del client VPN, quindi nessuna modifica del profilo né verifica delle credenziali può aiutare.
Il codice di stato separa questa famiglia dai crash ordinari. Un’unit che esce con status=203/EXEC non ha mai avviato un processo; un’unit che parte e poi esce con status=1 può aver fallito l’autenticazione, il DNS o l’instradamento. Leggi il codice prima del log del client, perché nel primo caso il log del client sarà vuoto.
Il percorso nel journal è quello usato da systemd, non necessariamente quello che hai modificato. I file drop-in e l’output del generatore possono sostituire il file del fornitore, e un daemon-reload obsoleto lascia la riga vecchia in memoria. Per questo la prima diagnostica è chiedere a systemd cosa ritiene sia il comando, invece di aprire il file che ricordi di aver cambiato.
Tre punti in cui è scritto il percorso del binario
Un client VPN installato da un pacchetto di distribuzione, da uno script del fornitore e da un tarball di rilascio può finire in tre prefissi diversi. L’unit, la regola di privilegio e l’installer hanno registrato ciascuno uno di quei prefissi, e il guasto compare quando due dei tre non concordano.
| Record | Common value | How to read it |
|---|---|---|
| Unit or drop-in | /usr/local/bin/vpnclient | systemctl cat vpnclient.service |
| Installed binary | /usr/bin/vpnclient | command -v vpnclient, then ls -l the result |
| Sudoers grant | /opt/vpnclient/bin/vpnclient | sudo grep -r vpnclient /etc/sudoers.d/ |
Una discrepanza concreta appare così: lo script del fornitore installa in /usr/local/bin, il tarball è stato estratto sotto /opt, e l’unit nomina ancora il vecchio percorso /usr/bin di un pacchetto precedente. Solo uno di quei file esiste, e raramente è quello presente in ExecStart.
Decidi quale percorso è canonico prima di modificare uno dei due file. Se il pacchetto di distribuzione possiede il binario, il percorso del pacchetto è canonico e l’unit deve seguirlo. Se lo possiede lo script del fornitore, la stessa regola vale per il prefisso del fornitore. Copiare un binario tra prefissi crea due copie che divergono al prossimo aggiornamento.
Quando un comando tra virgolette diventa un nome di file
Un secondo modo di guasto si nasconde dietro la stessa riga del journal. Qualcuno scrive questo nell’unit e si aspetta che il client si connetta durante l’avvio:
ExecStart=/usr/bin/"vpnclient connect office"systemd divide il valore in parole, quindi la prima parola diventa /usr/bin/vpnclient connect office, con le virgolette rimosse come sintassi di raggruppamento. Poi il kernel cerca un file il cui nome contenga spazi, e non esiste. L’errore è identico a quello di un binario mancante, anche se il binario è installato.
La forma supportata è un vero eseguibile seguito da argomenti separati, per esempio ExecStart=/usr/bin/vpnclient connect office. Se il flusso di lavoro richiede davvero una pipe o una catena, mettila in uno script e chiama lo script, perché ExecStart non ha &&, non ha pipe e non ha espansione glob.
Ripara l’unit, non il filesystem
La riparazione tentante è un collegamento simbolico dal percorso atteso al binario reale. Funziona finché il pacchetto non viene aggiornato e il collegamento punta a un file rimosso, oppure finché un’etichetta SELinux non corrisponde più alla destinazione. Preferisci modificare l’unit tramite un drop-in, così il file del fornitore resta intatto e gli aggiornamenti restano prevedibili.
La regola di privilegio deve corrispondere allo stesso percorso e agli stessi sottocomandi. Una voce sudoers per /usr/local/bin/vpnclient non autorizza /usr/bin/vpnclient, e una voce che consente qualsiasi argomento lascia che qualsiasi processo locale riconfiguri o fermi il tunnel. Concedi i due verbi che servono all’unit e tieni lo smontaggio fuori da essa se fermare il servizio chiude già l’interfaccia.
È qui che il confronto ripaga: l’unit, il database dei pacchetti e sudoers dovrebbero tutti indicare un solo percorso. Quando lo fanno, l’errore EXEC sparisce senza riavvio e senza una copia del binario in un terzo posto.
Dimostra il comando analizzato prima del riavvio
sudo systemctl daemon-reload
sudo systemctl cat vpnclient.service
systemctl show -p ExecStart vpnclient.service
systemd-analyze verify /etc/systemd/system/vpnclient.service
sudo systemctl restart vpnclient.service
systemctl status vpnclient.service --no-pagersystemctl show è l’output decisivo perché stampa l’argv che systemd ha realmente analizzato, incluso qualsiasi drop-in. systemd-analyze verify intercetta un percorso mancante o non eseguibile prima che riavvii, e il comando status dopo il riavvio dice se il processo ora esiste.
Se l’unit parte e poi fallisce, hai lasciato la famiglia EXEC e sei entrato nella gestione degli errori del client stesso. Cerca nel journal il messaggio del client, non un altro errore di execve. Conserva il diff dell’unit e il diff di sudoers insieme nel registro delle modifiche, così il prossimo aggiornamento non reintroduce silenziosamente il percorso diviso.