Jak klient OpenVPN za NAT staje się wyjściem do internetu
Wyjście samo łączy się na zewnątrz, więc jego router nie potrzebuje żadnego portu przychodzącego. Prześledź, co trasuje koncentrator, co przekazuje wyjście i skąd pochodzi droga powrotna.
Trzy hosty Linux, jeden projekt: publicznie dostępny koncentrator OpenVPN, laptop użytkownika i maszyna wyjściowa za zwykłym routerem domowym, który niczego nie przekazuje do środka. Wyjście samo łączy się z koncentratorem. Potem ruch internetowy użytkownika wchodzi do tunelu na laptopie i wychodzi własnym łączem wyjścia. Wszystko zaskakujące w tym układzie wynika z jednego szczegółu: to wyjście zainicjowało połączenie, więc droga do środka już istnieje i nie trzeba przekierowania portu, by ją utworzyć.
Poniżej śledzimy drogę pakietu, a nie kolejność konfiguracji, bo konfiguracja ma sens dopiero wtedy, gdy wiadomo, który komponent podejmuje którą decyzję. Każda warstwa podejmuje dokładnie jedną decyzję, a błędy gromadzą się tam, gdzie dwie warstwy uznaje się za jedną.
Dwie sesje transportowe, jedna droga wewnętrzna
Każdy host utrzymuje własne zewnętrzne połączenie z koncentratorem i żadne z nich nie wie o drugim. Ruch użytkownika do internetu to osobny wewnętrzny strumień, który przypadkiem jest przenoszony wewnątrz tunelu użytkownika, a potem ponownie emitowany z tunelu wyjścia. To ten strumień koncentrator musi trasować, a wyjście przekazywać.
outer: consumer --TLS--> hub
outer: exit --TLS--> hub
inner: consumer --tun--> hub --internal--> exit --tun--> uplink
Ponieważ oba połączenia zewnętrzne wychodzą od strony klienta, router wyjścia nigdy nie widzi przychodzącego SYN i nigdy nie potrzebuje reguły NAT docelowego. Musi tylko utrzymać ustanowiony transport przy życiu, co już robi dla każdego długotrwałego połączenia. Nic w publicznym internecie nie musi najpierw dotrzeć do wyjścia, dlatego dynamiczny adres na tym łączu nie jest problemem. Router nadal musi zezwalać na sesję wychodzącą, ale to ta sama zgoda, której używa przeglądarka.
Co iroute faktycznie instaluje w serwerze
Koncentrator jest jedynym urządzeniem, które decyduje, który peer posiada który cel, i robi to we własnej tabeli tras przestrzeni użytkownika, a nie w tabeli jądra. Wpis client-config-dir dla wyjścia o nazwie exit-node może tam zażądać domyślnego prefiksu:
# /etc/openvpn/server/ccd/exit-node
iroute 0.0.0.0 0.0.0.0
Ta linia mówi procesowi serwera "wewnętrzne pakiety z dowolnym celem należą do tego klienta". Nie dodaje domyślnej trasy do systemu operacyjnego koncentratora i nie czyni koncentratora bramą internetową. Dokumentacja OpenVPN 2.6 trzyma trasowanie wewnętrzne właściwe klientowi i trasowanie jądra jako osobne mechanizmy, i właśnie ten rozdział sprawia, że projekt działa bez dotykania domyślnej trasy koncentratora.
Użytkownik potrzebuje instrukcji lustrzanej, dostarczonej jako push, aby jego własny ruch wszedł do tunelu, zamiast wychodzić przez lokalną bramę. Przy topologii podsieci pula jest trasą połączoną na urządzeniu tun wyjścia, więc kierunek powrotny nie wymaga dodatkowego push; czego wyjście nie może otrzymać, to przekierowanie poniżej, które odesłałoby jego własny ruch łącza z powrotem do tunelu, którego próbuje użyć. Wysyłaj je tylko do użytkownika i czytaj zastosowane trasy na kliencie, zamiast ufać, że wybrano plik CCD.
# /etc/openvpn/server/ccd/consumer-01
push "redirect-gateway def1"
Przekazywanie wykonuje jądro wyjścia
Wewnątrz wyjścia pakiet od użytkownika dociera na interfejs tun, adresowany do jakiegoś hosta internetowego. Jądro sprawdza swoją tabelę tras, znajduje rzeczywiste łącze jako najlepsze dopasowanie i stosuje dwie niezależne polityki: czy ten pakiet można w ogóle przekazać i czy może opuścić ten interfejs z tym adresem źródłowym. Przekazywanie to sysctl, domyślnie wyłączony w wielu dystrybucjach.
Wyjście potrzebuje więc trwale włączonego przekazywania IPv4, reguły wpuszczającej źródła tunelu użytkownika w stronę łącza oraz drogi odpowiedzi z powrotem przez tun. Włączenie przekazywania dla całego hosta zmienia też zachowanie wszystkiego innego, co jest przez niego trasowane, dlatego reguła powinna nazywać źródła i interfejsy, zamiast akceptować wszystko.
Drogę powrotną kupuje translacja źródła
Host internetowy nie ma trasy do adresu 10.90.0.x, więc odpowiedzi zostałyby odrzucone, zanim dotarłyby do wyjścia. Przepisanie źródła, gdy pakiet opuszcza łącze, rozwiązuje to, a śledzenie połączeń odwraca przepisanie w drodze powrotnej.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.90.0.0/24 -o enp1s0 -j MASQUERADE
ip -4 route get 10.90.0.9 # run on the exit: must point at the tun
Powyższa reguła to punkt wyjścia dla jednego interfejsu łącza, nie polityka zapory. Zawęź źródło do przypisań, które naprawdę autoryzowałeś, i trzymaj własną politykę wejścia wyjścia osobno, aby maskowanie ruchu tunelu nie ujawniało po cichu usług lokalnych. Na hoście z dynamicznym adresem łącza maskowanie oszczędza też ręcznego śledzenia adresu.
Gdzie koncentrator może wymuszać, a gdzie nie
Z client-to-client i tradycyjną ścieżką danych w przestrzeni użytkownika koncentrator przekazuje wewnętrzne pakiety między peerami wewnątrz procesu serwera. Nigdy nie przechodzą one przez łańcuch FORWARD jądra koncentratora, więc reguła jądra tam nie ograniczy tranzytu między peerami, jakkolwiek starannie by ją napisano. Wymuszanie musi żyć we własnej polityce serwera: kto może się łączyć, jaki plik CCD otrzymuje i czy domyślna własność wyjścia jest w ogóle przyznawana.
Dlatego domyślny iroute to szeroka decyzja o zaufaniu. Wyjście musi legalnie przyjmować ruch powrotny z dowolnych adresów internetowych i musi być maszyną, którą kontrolujesz, łatasz i monitorujesz. Przyznanie tej samej własności peerowi, któremu nie powierzyłbyś routera to błąd, do którego ten projekt zaprasza.
Członkostwo jest prawdziwą kontrolą. Każdy klient, który potrafi skonfigurować własne trasy, może spróbować użyć wyjścia, więc ograniczanie, kto może się łączyć, albo którym peerom wyjście będzie służyć, jest częścią projektu, a nie późniejszą myślą.
Bezpieczna porażka, gdy wyjście znika
Wyłącz wyjście, a wewnętrzna domyślna własność znika wraz z jego sesją. Nie może się zdarzyć, że koncentrator po cichu stanie się wyjściem zastępczym: żadnej domyślnej trasy z puli VPN do łącza koncentratora i żadnej szerokiej reguły maskowania na koncentratorze, która chętnie przekazałaby ten sam ruch. Przetestuj tę awarię celowo, a potem przetestuj odtworzenie nowym strumieniem, a nie stroną, która była już otwarta.
# on the consumer, with the exit up and then down
ip -4 route get 1.1.1.1
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com
Jeśli ruch użytkownika nadal dociera do internetu po zniknięciu wyjścia, niesie go coś innego i zamierzona granica nie istnieje. Traktuj DNS i IPv6 także jako osobne decyzje: trasa IPv4 plus maskowanie nie mówią nic o ruchu AAAA, a resolver wypchnięty przez koncentrator odpowie stamtąd, skąd zapytanie naprawdę wychodzi.