Skrypt, który uruchamia OpenVPN i nigdy się nie łączy, zwykle nie jest problemem sieci: klient siedzi przy monicie bez terminala, na którym mógłby go narysować. OpenVPN ma dokładnie dwa obsługiwane sposoby odpowiedzi na ten monit bez człowieka, plik poświadczeń za auth-user-pass i gniazdo zarządzania, i nie są wymienne. Plik kosztuje jedną dyrektywę i odpowiada na jeden monit; gniazdo odpowiada na każdy monit i daje wszystkiemu, co potrafi otworzyć port loopback, nieuwierzytelniony kanał sterowania. Które z nich pasuje do twojego wdrożenia, zależy od tego, który monit naprawdę blokuje, oraz od tego, który sekret możesz sobie pozwolić zostawić w spokoju.

Nazwij monit, zanim wybierzesz mechanizm

Cztery monity wyglądają na terminalu identycznie. Para konta pochodzi z ustawienia PAM lub auth-user-pass-verify serwera i odpowiada na nią auth-user-pass. Fraza hasła klucza prywatnego należy do pliku klucza i jest czytana przez --askpass albo wpisywana ręcznie. Pakiet PKCS#12 prosi o własne hasło. Uwierzytelnianie proxy używa pliku auth, który przyjmuje --http-proxy, a nie danych konta.

Gdy OpenVPN działa jako dziecko systemd, PHP lub Node, nie ma terminala, więc każdy z nich blokuje po cichu. Proces siedzi, log kończy się po wierszu szyfru, a jednostka w końcu przekracza limit czasu; ta cisza jest powodem, dla którego ludzie dodają pętlę ponowień albo sleep, zamiast sprawdzić, który monit czeka. Tylko gniazdo zarządzania potrafi odpowiedzieć na wszystkie cztery. Plik poświadczeń odpowiada na pierwszy i na nic więcej.

auth-user-pass: jeden plik, jeden monit

Plik zawiera nazwę użytkownika w pierwszym wierszu i hasło w drugim, i nic więcej: bez cudzysłowów, bez nazw kluczy, bez komentarza. Ustaw tryb 600 i uczyń właścicielem konto, które uruchamia openvpn, co w usłudze jest często osobnym użytkownikiem, a nie twoim loginem. Plik należący do roota pod /root to różnica między poleceniem, które działa w twojej powłoce, a jednostką, która zawodzi przy starcie.

Dwa szczegóły zamieniają poprawny plik w monit. Edytor zapisujący CRLF zostawia przyklejony do hasła znak powrotu karetki, więc serwer odrzuca hasło, które w pliku wygląda poprawnie; sprawdzaj przez cat -A lub od -c, a nie okiem. A auth-user-pass bez argumentu w profilu oznacza z założenia «zapytaj użytkownika», czego druga dyrektywa w wierszu poleceń może nie nadpisać, bo profil jest parsowany tam, gdzie leży.

Kolejność opcji to trzecia pułapka i zależy od wersji. Niektóre kompilacje OpenVPN 2.4 ignorują --auth-user-pass, gdy występuje przed --config, więc te same dwie opcje w innej kolejności się łączą. Umieść dyrektywę w profilu albo po --config w wierszu poleceń i przed zmianą czegokolwiek przeszukaj profil pod kątem każdego wystąpienia.

auth-nocache należy do tego mechanizmu, ale nic nie dostarcza. Ogranicza, jak długo klient trzyma poświadczenia w pamięci, co pomaga po awarii i wcale nie pomaga, gdy pliku brakuje. Ponowne połączenie po przekroczeniu limitu sesji po stronie serwera czyta plik od nowa, więc poświadczenia, które działały przy starcie, mogą mimo to wygasnąć w środku dnia.

Gniazdo zarządzania: każdy monit, więcej powierzchni

Uruchom klienta z nasłuchiwaczem i wyraźnie zadeklaruj, że monity będą raportowane na nim, używając dyrektyw opisanych w podręczniku OpenVPN 2.6:

management 127.0.0.1 3030
management-query-passwords

W bashu trzymaj połączenie na wolnym deskryptorze pliku i odpowiadaj po nazwie monitu, a nie po pozycji, bo ponowne połączenie może pytać w innej kolejności:

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

Granicą jest transport. Protokół zarządzania to czysty tekst przez TCP na loopbacku, więc nasłuchiwacza nie wolno publikować na adresie LAN lub tunelu, aby dotarł do niego zdalny kontroler; zdalny kontroler oznacza przekierowanie SSH, a nie szersze powiązanie. Demon społeczności przyjmuje też tylko jednego klienta zarządzania naraz, więc GUI albo monitor nie mogą dzielić gniazda z twoim skryptem. Drugie połączenie jest odrzucane, a nie kolejkowane, i awaria wygląda jak skrypt, który ogłuchł.

To gniazdo jest też kanałem sterowania. Bez hasła zarządzania wszystko, co potrafi połączyć się z 127.0.0.1:3030, może czytać stan, zabić tunel albo samo odpowiedzieć na monit. Hasło zarządzania i plik poświadczeń to sekrety tej samej klasy i oba zasługują na tę samą troskę.

Które pasuje do wdrożenia

Wybierz plik poświadczeń, gdy jedynym monitem jest para konta, klient działa jako usługa ze stabilnym właścicielem, a plik 600 możesz trzymać poza kopiami zapasowymi, których nie kontrolujesz. Wybierz gniazdo zarządzania, gdy w grze jest fraza hasła klucza lub hasło PKCS#12, gdy opakowanie już nadzoruje proces albo gdy musisz obserwować stan i monity podczas debugowania ponownego połączenia.

Dwa warunki przesuwają decyzję. Jeśli opakowaniem jest /bin/sh, jednostka systemd wywołująca binarkę bezpośrednio albo minimalny kontener, klej gniazda tylko dla basha nie jest dostępny i plik jest tańszą odpowiedzią. Jeśli sekret często się rotuje, żaden plik nie jest dla niego dobrym domem: zapytaj o pęk kluczy z małego opakowania i pozwól jednostce wywołać opakowanie, co trzyma rotację w jednym miejscu, a hasło poza historią powłoki.

Podstawienie procesu stoi między oboma i nie należy do żadnego. Jednowierszowiec poniżej działa, bo bash go rozwija, zanim openvpn wystartuje, a nie dlatego, że klient rozumie składnię:

openvpn --config client.ovpn --auth-user-pass <(printf "%s
%s
" "$USER" "$PASS")

Psuje się pod /bin/sh, w jednostce omijającej powłokę i w obrazach bez basha lub /dev/fd. Zatrzymaj go do testów interaktywnych, nigdy jako kontrakt, od którego zależy usługa.

Chroń sekret, a potem zweryfikuj w logu

Oba mechanizmy zostawiają sekret czytelny dla procesu OpenVPN. Nigdy nie umieszczaj hasła w wierszu poleceń ani w pliku jednostki, gdzie ps i systemctl pokazują je każdemu lokalnemu użytkownikowi; podaj ścieżkę do pliku, a nie wartość w nim. Tam, gdzie certyfikaty mogą zastąpić parę konta, usuwa to kategorię monitu zamiast ją ukrywać.

Poprawne uruchomienie pokazuje ukończenie sekwencji monitów i sesję dochodzącą do «Initialization Sequence Completed». Jeśli klient zatrzymuje się na tym samym monicie, dopasowanie zawiodło: wydrukuj dokładne wiersze wysłane przez gniazdo, sprawdź cudzysłowy i nazwę monitu, i napraw wzorzec zamiast dodawać sleep. Tunel, który raportuje sukces i nic nie przenosi, to problem routingu lub push, nie poświadczeń.

Potem przetestuj ponowne połączenie, nie tylko pierwszy start. Zerwij sesję po stronie serwera albo odczekaj jej limit czasu; klient pyta ponownie i właśnie tam plik, który przy starcie wyglądał poprawnie, pokazuje, czy jest nadal czytelny dla konta, które łączy się ponownie.

Powiązane artykuły