Wczoraj tunel działał, a dziś rano unit jest martwy, a w journalu jest Failed at step EXEC spawning /usr/local/bin/vpnclient. Poświadczenia, trasy i konto nigdy nie pojawiają się w tym zdaniu. systemd rozwiązał ExecStart do ścieżki, poprosił jądro o jej wykonanie, a jądro odmówiło, najczęściej dlatego, że pod tą ścieżką nic nie istnieje albo plik nie jest wykonywalny. Traktuj usterkę jako problem pliku i argv, dopóki analizowane polecenie nie dowiedzie czegoś przeciwnego.

Dlaczego EXEC zawodzi, zanim zacznie się uwierzytelnianie

systemd nie uruchamia ExecStart przez powłokę. Wykonuje fork, buduje wektor argumentów z linii i wywołuje execve na pierwszym elemencie. Jeśli to wywołanie zawiedzie, usługa nigdy nie dotrze do własnego kodu klienta VPN, więc edycja profilu ani sprawdzanie poświadczeń nie pomogą.

Kod stanu oddziela tę rodzinę od zwykłych awarii. Unit, który kończy się z status=203/EXEC, nigdy nie uruchomił procesu; unit, który startuje, a potem kończy się z status=1 mógł zawieść na uwierzytelnianiu, DNS lub routingu. Czytaj kod przed logiem klienta, bo w pierwszym przypadku log klienta będzie pusty.

Ścieżka w journalu to ta, której użył systemd, a niekoniecznie ta, którą edytowałeś. Pliki drop-in i wyjście generatora mogą nadpisać plik dostawcy, a nieaktualny daemon-reload zostawia starą linię w pamięci. Dlatego pierwsza diagnostyka to zapytać systemd, co uważa za polecenie, zamiast otwierać plik, który pamiętasz jako zmieniony.

Trzy miejsca, w których zapisana jest ścieżka pliku binarnego

Klient VPN zainstalowany z pakietu dystrybucji, skryptu dostawcy i tarballa wydania może trafić do trzech różnych prefiksów. Unit, reguła uprawnień i instalator zapisały po jednym z tych prefiksów, a usterka pojawia się, gdy dwa z trzech się nie zgadzają.

RecordCommon valueHow to read it
Unit or drop-in/usr/local/bin/vpnclientsystemctl cat vpnclient.service
Installed binary/usr/bin/vpnclientcommand -v vpnclient, then ls -l the result
Sudoers grant/opt/vpnclient/bin/vpnclientsudo grep -r vpnclient /etc/sudoers.d/

Konkretna niezgodność wygląda tak: skrypt dostawcy instaluje do /usr/local/bin, tarball rozpakowano pod /opt, a unit nadal wskazuje starą ścieżkę /usr/bin z poprzedniego pakietu. Istnieje tylko jeden z tych plików i rzadko jest to ten w ExecStart.

Zdecyduj, która ścieżka jest kanoniczna, zanim zmienisz którykolwiek plik. Jeśli plik binarny należy do pakietu dystrybucji, kanoniczna jest ścieżka pakietu i unit powinien ją śledzić. Jeśli należy do skryptu dostawcy, ta sama reguła dotyczy prefiksu dostawcy. Kopiowanie pliku binarnego między prefiksami tworzy dwie kopie, które rozjadą się przy następnej aktualizacji.

Gdy polecenie w cudzysłowie staje się nazwą pliku

Drugi tryb awarii ukrywa się za tą samą linią journalu. Ktoś wpisuje to do unitu i oczekuje, że klient połączy się podczas rozruchu:

ExecStart=/usr/bin/"vpnclient connect office"

systemd dzieli wartość na słowa, więc pierwsze słowo staje się /usr/bin/vpnclient connect office, a cudzysłowy są usuwane jako składnia grupowania. Potem jądro szuka pliku, którego nazwa zawiera spacje, a takiego nie ma. Błąd jest identyczny z brakiem pliku binarnego, choć plik jest zainstalowany.

Obsługiwana forma to prawdziwy plik wykonywalny, po którym następują osobne argumenty, na przykład ExecStart=/usr/bin/vpnclient connect office. Jeśli przepływ pracy naprawdę wymaga potoku lub łańcucha, umieść go w skrypcie i wywołaj skrypt, bo ExecStart nie ma &&, nie ma potoków i nie ma rozwinięcia glob.

Napraw unit, nie system plików

Kuszącą naprawą jest dowiązanie symboliczne z oczekiwanej ścieżki do prawdziwego pliku binarnego. Działa, dopóki pakiet się nie zaktualizuje i dowiązanie nie wskaże usuniętego pliku, albo dopóki etykieta SELinux nie przestanie pasować do celu. Lepiej edytuj unit przez drop-in, aby plik dostawcy pozostał nietknięty, a aktualizacje pozostały przewidywalne.

Reguła uprawnień musi odpowiadać tej samej ścieżce i tym samym podpoleceniom. Wpis w sudoers dla /usr/local/bin/vpnclient nie upoważnia /usr/bin/vpnclient, a wpis zezwalający na dowolny argument pozwala każdemu lokalnemu procesowi przekonfigurować lub zatrzymać tunel. Przyznaj dwa czasowniki, których potrzebuje unit, i trzymaj demontaż poza nim, jeśli zatrzymanie usługi już zamyka interfejs.

Tu porównanie się opłaca: unit, baza pakietów i sudoers powinny wskazywać jedną ścieżkę. Gdy tak jest, błąd EXEC znika bez restartu i bez kopii pliku binarnego w trzecim miejscu.

Udowodnij analizowane polecenie przed restartem

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-pager

systemctl show to decydujące wyjście, bo wypisuje argv, które systemd naprawdę przeanalizował, łącznie z każdym drop-inem. systemd-analyze verify wychwytuje brakującą lub niewykonywalną ścieżkę przed restartem, a polecenie status po restarcie mówi, czy proces teraz istnieje.

Jeśli unit startuje i potem zawodzi, opuściłeś rodzinę EXEC i wszedłeś w obsługę błędów samego klienta. Szukaj w journalu komunikatu klienta, a nie kolejnej awarii execve. Trzymaj diff unitu i diff sudoers razem w zapisie zmian, aby następna aktualizacja nie przywróciła po cichu rozdzielonej ścieżki.

Powiązane artykuły