Jak dostać się do bazy danych, która nasłuchuje tylko na localhost?
Instancja Postgres na zdalnym serwerze celowo wiąże się z 127.0.0.1. Nic w sieci nie może jej dosięgnąć, tylko procesy działające na tej samej maszynie. Twój laptop nie jest tą maszyną, więc psql -h remote-server -p 5432 kończy się błędem, zanim w ogóle dojdzie do uwierzytelnienia. Otwarcie tego portu na świat przekreślałoby sens trzymania bazy na loopbacku.
Chcesz na chwilę pożyczyć widok, jaki zdalna maszyna ma na 127.0.0.1, z własnego terminala. W momencie logowania SSH ma już otwarte, szyfrowane i uwierzytelnione połączenie między dwiema maszynami. Port forwarding wykorzystuje to połączenie do przesyłania ruchu spoza twojej powłoki.
Jak działa port forwarding SSH pod maską?
Zwykła sesja SSH wygląda jak jedna rura: naciśnięcia klawiszy idą w jedną stronę, wynik terminala wraca. Pod spodem protokół multipleksuje kilka niezależnych kanałów przez to jedno szyfrowane połączenie. Powłoka to jeden kanał. Przekierowane połączenie TCP to kolejny.
Poproś ssh o przekierowanie portu, a on otworzy nowy kanał dla każdego połączenia nawiązanego z tym portem, przeniesie surowe bajty przez istniejącą sesję i przekaże je po drugiej stronie. Bez nowego certyfikatu, bez drugiego logowania. Tunel dziedziczy dokładnie taki dostęp, jaki ma już logowanie SSH.
-L, -R i -D nie są trzema różnymi mechanizmami — to ten sam mechanizm przekierowania kanałów, tylko skierowany w różne strony.
Jak używać lokalnego przekierowania portów SSH (-L)?
| |
Czytaj argument -L jako local_port:destination_host:destination_port. To otwiera port 5432 na twoim laptopie. Wszystko, co się z nim połączy, jest przekierowywane przez tunel do localhost:5432 widzianego z perspektywy db-host, czyli instancji Postgres, związanej z własnym loopbackiem. W drugim terminalu:
| |
psql myśli, że rozmawia z lokalną bazą danych. W rzeczywistości rozmawia z interfejsem loopback db-host przez SSH.
Host docelowy wcale nie musi być tym samym serwerem, do którego się logujesz przez SSH:
| |
To przekierowuje przez jumpbox do trzeciej maszyny, do której jumpbox ma dostęp, a ty nie. To jest to, co większość ludzi ma na myśli, mówiąc “tunel SSH”: maszyna z dostępem, którego ty nie masz, przekierowująca cię do sieci, której inaczej byś nie dotknął. Jeśli masz już lokalnie uruchomiony Postgres, port 5432 na twoim laptopie jest zajęty; wybierz inny lokalny port, na przykład 15432, i przekaż -p 15432 do psql.
Jak udostępnić lokalny port przez zdalne przekierowanie SSH (-R)?
-R robi to samo w drugą stronę. Składnia to remote_port:destination_host:destination_port, ale nasłuch otwiera się po stronie zdalnej.
| |
Teraz public-host nasłuchuje na porcie 8080, a każde połączenie do niego wraca przez tunel do localhost:3000 na twojej maszynie: twojego lokalnego serwera deweloperskiego. Dostajesz osiągalny adres dla dostawcy webhooków albo kolegi bez otwierania portu we własnym firewallu, pożyczając maszynę, która już go ma.
Jedno domyślne zachowanie zwykle zaskakuje. sshd wiąże porty przekierowane zdalnie z interfejsem loopback zdalnego hosta, więc curl public-host:8080 z trzeciej maszyny kończy się błędem, mimo że przekierowanie działa. Tylko procesy na samym public-host mogą je osiągnąć. Zmiana tego wymaga GatewayPorts yes w sshd_config tamtego serwera, a to ustawienie należy do administratora, nie do ciebie. Żadna flaga w twoim poleceniu ssh tego nie nadpisze.
Jak zamienić tunel SSH w proxy SOCKS (-D)?
-L i -R przekierowują każde do stałego celu, ustalonego przy uruchomieniu polecenia. -D w ogóle nie wybiera celu:
| |
To otwiera proxy SOCKS na localhost:1080. Każda aplikacja skierowana na ten port sama wybiera cel dla każdego połączenia, a ssh przekazuje je dalej tak, jakby to remote-host się z nimi łączył:
| |
Część -hostname ma znaczenie. Przy zwykłym --socks5 curl rozwiązuje internal-service.corp na twoim laptopie, gdzie ta nazwa nie istnieje, i kończy się błędem, zanim proxy zostanie w ogóle użyte. --socks5-hostname wysyła nazwę przez tunel, tak żeby to remote-host ją rozwiązał.
Przeglądarki działają tak samo, gdy tylko ustawisz proxy w ich ustawieniach sieciowych; Firefox dodatkowo wymaga włączenia opcji “proxy DNS when using SOCKS v5”. Każda strona widzi wtedy IP remote-host i przechodzi przez jego reguły firewalla. Tak dostajesz się do całej wewnętrznej sieci przez jeden jump box albo sprawdzasz, co strona serwuje innemu regionowi, bez pisania osobnego przekierowania dla każdego celu.
Który typ przekierowania SSH wybrać: -L, -R czy -D?
-L lokalne | -R zdalne | -D dynamiczne | |
|---|---|---|---|
| Nasłuch otwiera się na | Twojej maszynie | Zdalnym hoście | Twojej maszynie |
| Cel | Stały, ustawiony w wierszu poleceń | Stały, ustawiony w wierszu poleceń | Wybierany dla każdego połączenia przez aplikację |
| Typowe zastosowanie | Dotarcie do usługi widocznej tylko dla zdalnego hosta | Udostępnienie twojej lokalnej usługi stronie zdalnej | Kierowanie dowolnego ruchu przez sieć zdalnego hosta |
| Potrzebne po drugiej stronie | Nic poza zwykłym logowaniem SSH | GatewayPorts, jeśli inni mają mieć dostęp | Nic poza zwykłym logowaniem SSH |
Jak utrzymać tunel SSH otwarty w tle?
Szybko pojawiają się dwa problemy: wpisywanie pełnego polecenia za każdym razem i tunel, który umiera przy zamknięciu terminala. ~/.ssh/config rozwiązuje pierwszy:
| |
ssh db-tunnel otwiera teraz oba przekierowania naraz. LocalForward, RemoteForward i DynamicForward odpowiadają -L, -R i -D — jedna linia na przekierowanie, tyle ile chcesz, w jednym bloku Host.
Dla przekierowania, które ma działać bez nadzoru, dodaj -N (nie uruchamiaj żadnego zdalnego polecenia, tylko trzymaj tunel) i -f (przejdź w tło po uwierzytelnieniu), a do tego keepalive, żeby cicho zerwane połączenie zostało zauważone zamiast wisieć:
| |
To i tak nie połączy się ponownie, gdy padnie sieć. ssh kończy działanie i przekierowanie znika. autossh opakowuje to samo polecenie i uruchamia je ponownie, gdy połączenie umiera:
| |
-M 0 wyłącza własny port monitorujący autossh i pozwala keepalive ServerAlive* wykryć martwe połączenie. Na nowoczesnym OpenSSH warto skonfigurować to raz i zapomnieć o zrywanych tunelach.
Kiedy nie warto używać tuneli SSH?
Przekierowany port dziedziczy dostęp logowania SSH, które go otworzyło. Wygodne, gdy to ty trzymasz klucz, i realne zagrożenie, jeśli klucz albo sesja zostaną przejęte, bo tunel oddaje wszystko, do czego to logowanie ma dostęp. AllowTcpForwarding w sshd_config to przełącznik po stronie serwera (domyślnie yes). Ustawienie go na no zamyka tę drogę, ale każdy z dostępem do powłoki na maszynie nadal może uruchomić własny forwarder, więc pomaga to tylko wtedy, gdy ograniczysz też dostęp do powłoki.
Tunele też przestają się skalować powyżej garstki osób czy usług. Każdy tunel to osobny proces przywiązany do terminala albo instancja autossh. Nie ma centralnego miejsca, żeby zobaczyć, kto ma otwarty tunel, odebrać dostęp bez dotykania każdej konfiguracji klienta, czy zastosować jedną politykę dla wszystkich użytkowników. W tym momencie zamień stos osobistych tuneli na VPN albo na proxy świadome tożsamości przed usługą.
To nie to samo narzędzie co reverse proxy czy Cloudflare Tunnel, choć efekt jest podobny: coś, co wcześniej nie było osiągalne, staje się osiągalne. Reverse proxy stoi na stałe przed usługą i jest zwrócony w stronę publicznego internetu. Cloudflare Tunnel jest bliżej -R: wychodzące połączenie z twojej strony, które staje się osiągalne z zewnątrz, ale przez brzeg sieci Cloudflare, ze stabilną nazwą hosta i bez portu serwera do zarządzania. Reverse proxy i Cloudflare Tunnel polegają na certyfikatach, jak opisano w jak działa uzgadnianie TLS — tunel SSH żadnego nie potrzebuje. Szyfrowaniem jest sama sesja SSH, uwierzytelniana twoim kluczem, a nie certyfikatem, któremu ufałaby przeglądarka.
Co skonfigurować najpierw?
Weź tunel, który przepisujesz najczęściej, i zamień go dziś w blok Host w ~/.ssh/config. Dotarcie do czegoś widocznego tylko dla zdalnego hosta to -L. Udostępnienie lokalnej usługi przez maszynę z publicznym adresem to -R. Kierowanie ruchem aplikacji przez zdalną sieć bez wybierania celów z góry to -D. Gdy już jest w konfiguracji, ssh -N -f db-tunnel to wszystko, co musisz wpisać, i przestajesz mylić numery portów o 23:00.