Błąd client-connect w OpenVPN: czy sprzątanie przy disconnect jest gwarantowane?
Odrzucony klient nie cofa reguły firewalla ani zapisanego rekordu. Porównaj kontrakt wywołania zwrotnego z wycofaniem, które Twój hook musi wykonać sam.
Gdy skrypt client-connect kończy się kodem różnym od zera, OpenVPN odrzuca klienta, a administratorzy zwykle zakładają, że odpowiedni skrypt client-disconnect posprząta. Często w ogóle się nie uruchamia, bo przetwarzanie disconnect zależy od tego, co udało się ukończyć etapowi connect. Traktowanie odrzucenia i wycofania jako jednego mechanizmu sprawia, że nieudane logowanie zamienia się w wyciekłą regułę firewalla lub osierocony rekord. To dwa osobne obowiązki i tylko jeden z nich jest automatyczny. Przeczytaj własną ścieżkę wyjścia hooka, zanim obwinisz serwer o pozostawienie stanu.
Wywołanie z kodem różnym od zera odrzuca klienta; nie cofa Twojej pracy
Status wyjścia client-connect jest decyzją o dopuszczeniu. Zero oznacza dopuszczenie, cokolwiek innego oznacza rozbiórkę instancji, zanim zostanie w pełni ustanowiona. Nic w tym kontrakcie nie sprawdza, co skrypt już zrobił w świecie zewnętrznym. Jeśli skrypt dodał regułę iptables, zapisał wiersz lub zwiększył licznik, te zmiany przeżywają odrzucenie.
Ta sama asymetria dotyczy wtyczek, gdzie dla jednej instancji może działać kilka funkcji connect. Późniejsza awaria nie anuluje wstecznie wcześniejszego sukcesu, a wcześniejsza funkcja nie ma sposobu dowiedzieć się, że siostrzana funkcja zawiodła.
Odrzucenie jest automatyczne, sprzątanie nie
Podręcznik OpenVPN 2.6 wiąże przetwarzanie client-disconnect z powodzeniem wcześniejszego przetwarzania connect. Udokumentowany wyjątek obejmuje łańcuchy kaskadowe: jeśli jeden krok connect już zwrócił sukces, odpowiadające mu kroki disconnect nadal się wykonują przy rozbiórce instancji, nawet jeśli późniejszy krok connect zawiódł. Ten wyjątek jest wąski i zależy od liczby skonfigurowanych wywołań zwrotnych, dlatego testowanie jednego skryptu powłoki nic nie mówi o łańcuchu.
Zmapuj rzeczywistą konfigurację, zanim zaczniesz wokół niej projektować: który skrypt, która wtyczka i w jakiej kolejności się uruchamiają. Poniższa tabela wymienia, co zostawia po sobie nieukończona konfiguracja.
| 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 |
Każdy wiersz kończy się tak samo dla klienta i inaczej dla hosta. Klient nie może stwierdzić, który z nich nastąpił, więc hook musi wycofać to, co zrobił, przed zwróceniem statusu błędu.
Wyrównaj w skrypcie, który zawiódł
Zweryfikuj wszystko, co możesz, zanim dotkniesz współdzielonego stanu: pola certyfikatu, dostępność adresu, grupę, do której należy klient. Najtańsze odrzucenie to takie, które następuje przed jakimkolwiek przydziałem. Gdy konfiguracja obejmuje kilka operacji zewnętrznych, prowadź rejestr ukończonych i wycofuj je w odwrotnej kolejności przed zwróceniem statusu błędu.
Konkretny przypadek: skrypt tworzy wpis firewalla dla klienta, a potem nie udaje mu się zapisać wiersza stanu, który go opisuje. Zawodząca gałąź powinna usunąć właśnie utworzony wpis i dopiero wtedy zakończyć się kodem różnym od zera. Czekanie na zdarzenie disconnect, które może nigdy nie zostać zaplanowane, to sposób, w jaki reguła przeżywa sesję, dla której była przeznaczona.
Uczyń wyrównanie idempotentnym i przypisanym
Sprzątanie musi być bezpieczne przy dwukrotnym uruchomieniu i precyzyjne co do tego, co może usunąć. Późne zdarzenie z wcześniejszej próby nie powinno usuwać przydziału nowszej sesji tylko dlatego, że oba dzielą wspólną nazwę lub recyklingowany adres tunelu. Zapisuj konkretny uchwyt lub identyfikator operacji razem z zasobem i usuwaj po tym kluczu, a nie po nazwie. Transakcje bazy danych pomagają tylko przy własnych zmianach bazy; reguła firewalla poza tą granicą wymaga jawnego kroku wyrównania.
Załóż, że proces może umrzeć między zmianą a sprzątaniem, i dodaj uzgadniacz, który według harmonogramu porównuje stan zewnętrzny z listą dopuszczonych klientów.
Przetestuj trzy ścieżki, które naprawdę się różnią
Przećwicz trzy przypadki: odrzucenie, zanim cokolwiek zostanie przydzielone, odrzucenie po częściowym przydziale oraz normalną dopuszczoną sesję, która później się kończy. Te trzy dają różne wiersze dziennika i różne pozostałości. Sprawdzaj stan zewnętrzny po każdym z nich, nie tylko komunikat błędu klienta, ponieważ awaria klienta jest oczekiwanym wynikiem w dwóch z trzech.
Zapisuj etap hooka, status wyjścia i to, co przydzielono lub zwolniono, bez danych uwierzytelniających. Dziennik hooka, który nazywa utworzony zasób, jest tym, co czyni wyciekłą regułę znajdowalną pół roku później.