Jak działa Tailscale? Architektura i zasada działania
Wyjaśnienie techniczne działania Tailscale. Analiza tuneli WireGuard, serwera koordynującego, mechanizmów NAT traversal oraz przekaźników DERP w prywatnej sieci tailnet.
Czym jest Tailscale?
Tailscale to VPN, który łączy urządzenia bezpośrednio ze sobą, zamiast kierować cały ruch przez jeden zarządzany gateway. Każdy węzeł uruchamia WireGuard, dzięki czemu pakiety są przesyłane w formie zaszyfrowanej między serwerami, a elementy pośredniczące nie mają możliwości ich odczytania. Hostowany serwer koordynujący odpowiada za nawiązywanie połączeń. Przechowuje on i dystrybuuje klucze publiczne oraz informuje każdy węzeł o lokalizacji pozostałych. Przesyła również zdefiniowane reguły dostępu.
Ten podział stanowi podstawę architektury. Płaszczyzna danych działa w modelu peer-to-peer i jest szyfrowana między węzłami. Płaszczyzna sterowania to usługa utrzymywana przez Tailscale. Każde istotne pytanie dotyczące Tailscale, w tym kwestie dotyczące zaufania, wynikają z tych dwóch faktów. Jeśli użytkownik zbudował już ręcznie VPN oparty na WireGuard na serwerze VPS, Tailscale oferuje ten sam tunel, automatyzując dystrybucję kluczy oraz przechodzenie przez firewalle.
Jak działa Tailscale?
Prywatna sieć węzłów nazywana jest tailnet. W momencie dołączenia maszyny do sieci zachodzą cztery procesy.
- Uruchamia się demon
tailscaled, generuje parę kluczy WireGuard i przechowuje swój stan w/var/lib/tailscale/tailscaled.state. Klucz prywatny pozostaje na danej maszynie. Dokumentacja Tailscale jest w tej kwestii jednoznaczna: „klucz prywatny nigdy, pod żadnym pozorem nie opuszcza swojego węzła”. - Węzeł loguje się do serwera koordynującego i przesyła swój klucz publiczny oraz adresy, pod którymi jest osiągalny. Tailscale określa ten serwer jako „wspólną skrzynkę odbiorczą dla kluczy publicznych”.
- Serwer koordynujący odsyła mapę sieci: klucz publiczny, adres wewnątrz tailnet, nazwę maszyny oraz potencjalne punkty końcowe każdego węzła, z którym dany węzeł może się komunikować.
- Każda para węzłów próbuje następnie nawiązać bezpośredni tunel WireGuard między sobą. W przypadku niepowodzenia pakiety są przesyłane przez przekaźnik.
Każdy węzeł otrzymuje stały adres z puli 100.64.0.0/10, czyli zakresu carrier-grade NAT od 100.64.0.0 do 100.127.255.255. Tailscale korzysta z tego zakresu, ponieważ jest on zarezerwowany dla infrastruktury dostawców, dzięki czemu rzadko dochodzi do kolizji z prywatnymi adresami używanymi już przez serwery. W systemie Linux tunel widoczny jest jako interfejs o nazwie tailscale0.
Implementacja WireGuard działa wewnątrz tailscaled w przestrzeni użytkownika (userspace), a nie jako moduł jądra. Dlatego Tailscale uruchamia się w wirtualizacji kontenerowej, gdzie sudo modprobe wireguard kończy się błędem Operation not supported. Oznacza to również, że maksymalna przepustowość na danej maszynie jest niższa niż w przypadku WireGuard działającego w jądrze, co jest jednym z kompromisów omówionych w Tailscale w porównaniu do standardowego WireGuard.
Dwa polecenia pozwalają sprawdzić bieżący stan połączeń.
tailscale ip -4
tailscale statustailscale status wyświetla jedną linię dla każdego węzła, przy czym kluczowa jest ostatnia kolumna.
100.101.102.103 web-1 you@ linux -
100.101.102.104 db-1 you@ linux active; direct 198.51.100.24:41641
100.101.102.105 ci-runner you@ linux active; relay "fra"direct z podanym adresem i portem oznacza, że dwie maszyny znalazły ścieżkę do siebie i ruch odbywa się bezpośrednio (peer to peer). relay "fra" oznacza, że ruch przechodzi przez przekaźnik Tailscale we Frankfurcie. - oznacza brak aktywnej sesji z danym węzłem, co jest stanem normalnym.
Co widzi, a czego nie widzi serwer koordynacji
Serwer koordynacji przechowuje klucze publiczne oraz metadane. Zna nazwy maszyn, informacje o tym, który użytkownik lub tag jest właścicielem danego węzła, adresy tailnet każdego węzła, publiczne adresy, pod którymi węzły są osiągalne, czas ostatniej aktywności oraz zdefiniowany plik polityk. Stanowi to pełną mapę infrastruktury.
Serwer nie posiada kluczy prywatnych, więc nie może deszyfrować ruchu między dwoma węzłami. Szyfrowanie odbywa się w modelu end-to-end między peerami WireGuard, a serwer koordynacji nie jest jednym z nich.
Serwer może natomiast przekazywać klucze. Każdemu serwerowi koordynacji, niezależnie od tego, czy jest hostowany zewnętrznie, czy uruchomiony samodzielnie, powierza się zadanie informowania węzłów, które klucze publiczne należą do danej sieci tailnet. Jest to kluczowy element modelu zagrożeń opisany poniżej oraz powód, dla którego istnieje Headscale, otwartoźródłowy serwer koordynacji do samodzielnego hostowania.
Jak dwa serwery za różnymi zaporami sieciowymi komunikują się bezpośrednio
NAT (network address translation) umożliwia wielu maszynom współdzielenie jednego publicznego adresu IP. Twój VPS zazwyczaj posiada własny publiczny adres, jednak inne maszyny, które chcesz włączyć do sieci tailnet, często go nie mają: serwer domowy, serwer kompilacji w sieci biurowej czy urządzenie za zaporą dostawcy, której nie można edytować.
Tailscale wyznacza ścieżkę, wykorzystując techniki oparte na standardach STUN (session traversal utilities for NAT) oraz ICE. Każdy węzeł wysyła mały pakiet UDP do serwera STUN i otrzymuje informację o publicznym adresie oraz porcie przypisanym do tego gniazda przez router. Oba węzły zgłaszają te kandydatury do serwera koordynacyjnego, który przekazuje je drugiej stronie. Następnie oba węzły rozpoczynają jednoczesne wysyłanie pakietów do siebie nawzajem. Każdy router najpierw rejestruje pakiet wychodzący, dzięki czemu tworzy mapowanie i akceptuje odpowiedź przychodzącą z tego samego adresu. Żadna ze stron nie wymaga reguły zapory dla ruchu przychodzącego.
Porty są ściśle określone. Bezpośrednie tunele WireGuard wykorzystują protokół UDP z portem źródłowym, który domyślnie ma wartość 41641. STUN działa przez UDP 3478, łącząc się z serwerami przekaźnikowymi Tailscale. Połączenie kontrolne oraz wszelkie przesyłane dane korzystają z HTTPS na porcie TCP 443. W większości przypadków nie trzeba otwierać żadnych portów przychodzących, choć w sieciach z restrykcyjnym NAT, zezwolenie na ruch przychodzący UDP 41641 zwiększa szansę na nawiązanie bezpośredniego połączenia.
tailscale netcheckOdczytaj dwie linie tego raportu. UDP: true oznacza, że ruch UDP w ogóle opuszcza maszynę, a UDP: false oznacza, że każde połączenie z tego węzła będzie przekaźnikowane. MappingVariesByDestIP: true oznacza, że router przypisuje inny publiczny port dla każdego miejsca docelowego, przez co powyższa metoda przewidywania adresu nie zadziała, a takie węzły zazwyczaj pozostają w trybie przekaźnikowania.
Kiedy Tailscale korzysta z przekaźnika DERP
DERP (designated encrypted relay for packets) stanowi mechanizm awaryjny. Tailscale utrzymuje przekaźniki w wielu regionach, dostępne przez TCP 443. Węzeł, który nie może nawiązać bezpośredniego połączenia, przesyła swoje pakiety WireGuard za pośrednictwem jednego z nich.
Pakiety pozostają zaszyfrowane. Tailscale jasno deklaruje: „serwer DERP nie ma możliwości odszyfrowania ruchu. Przekazuje on jedynie w sposób ślepy zaszyfrowane pakiety między węzłami”. Przekaźnik widzi jedynie tekst zaszyfrowany oraz informacje o tym, które węzły się komunikują.
Przekaźniki obsługują również pierwsze pakiety większości połączeń. Ustalenie bezpośredniej ścieżki wymaga czasu, dlatego sesja często rozpoczyna się w trybie przekaźnikowym, a następnie przechodzi w tryb bezpośredni, gdy węzły się odnajdą. Można to zaobserwować.
tailscale ping db-1Pierwsze odpowiedzi pojawiają się jako via DERP(fra), a kolejna linia raportuje komunikat w rodzaju via 198.51.100.24:41641. Ta zmiana oznacza przejście na bezpośredni tunel. Jeśli stan ten nie ulega zmianie, należy uruchomić tailscale netcheck na obu końcach połączenia. Ścieżka przekaźnikowa nadal działa. Wiąże się to jednak z wyższymi opóźnieniami, ponieważ każdy pakiet musi zostać przekierowany przez trzecią maszynę.
Dołączanie serwera VPS do sieci tailnet
Skrypt instalacyjny obsługuje systemy Ubuntu oraz Debian.
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale upsudo tailscale up wyświetla adres URL. Należy go otworzyć, uwierzytelnić się, a węzeł pojawi się w konsoli administracyjnej. Następnie należy potwierdzić, że demon uruchamia się ponownie po restarcie systemu, ponieważ jest to krok często pomijany.
sudo systemctl is-enabled tailscaled
tailscale statusis-enabled powinno wyświetlić enabled, a tailscale status powinno wymienić nowy węzeł wraz z jego adresem 100.x. W przypadku serwera konfigurowanego za pomocą skryptu, interaktywny adres URL nie jest użyteczny. Należy wygenerować klucz uwierzytelniający (auth key) w konsoli administracyjnej i przekazać go wraz z tagiem określającym typ maszyny.
sudo tailscale up --auth-key=tskey-auth-REPLACE-ME --advertise-tags=tag:serverOtagowany węzeł jest przypisany do tagu, a nie do osoby, która wykonała polecenie, dzięki czemu działa on nadal po usunięciu konta użytkownika. Tag musi zostać najpierw zadeklarowany w pliku polityki w sekcji tagOwners, w przeciwnym razie polecenie zostanie odrzucone. Tagowanie zmienia również sposób, w jaki maszyna jest liczona w ramach planu, ponieważ zasoby otagowane są rozliczane oddzielnie od urządzeń osobistych, a zakres planu darmowego określa limity w tym zakresie.
Dla zarządzania flotą istotne są dwa ustawienia. Klucze węzłów wygasają domyślnie po 180 dniach (stan na sierpień 2026), a po wygaśnięciu klucza „połączenia do/z danego punktu końcowego przestaną działać” do momentu ponownego zalogowania. W przypadku serwerów bezobsługowych należy otworzyć wiersz maszyny w konsoli administracyjnej i wybrać opcję Disable Key Expiry. MagicDNS, włączony domyślnie dla sieci tailnet utworzonych 20 października 2022 lub później, nadaje każdemu węzłowi nazwę typu db-1.yak-bebop.ts.net, rozwiązywaną przez lokalny resolver pod adresem 100.100.100.100. Należy używać nazw zamiast adresów, ponieważ przebudowany węzeł otrzymuje nowy adres, zachowując swoją nazwę.
Jeśli sama instalacja kończy się niepowodzeniem w menedżerze apt lub w repozytorium, typowe błędy instalacji Tailscale na Ubuntu zawierają rozwiązania tych problemów.
Dostęp do usługi powiązanej z localhost
W tym miejscu przydatna staje się sieć tailnet, ale to tutaj użytkownicy napotykają problemy. Dołączenie do sieci tailnet nie sprawia, że usługa działająca na interfejsie loopback staje się osiągalna.
ss -tlnp | grep 3000Jeśli polecenie to zwróci 127.0.0.1:3000, gniazdo akceptuje wyłącznie pakiety, których miejscem docelowym jest 127.0.0.1. Żądanie z innego węzła dociera zaadresowane na adres 100.x tego węzła, więc jądro systemu nie posiada nasłuchującego procesu dla tego adresu i odpowiada komunikatem TCP reset. Klient zgłasza błąd Connection refused. Tunel działa poprawnie. Problemem jest konfiguracja nasłuchiwania.
Istnieją dwa poprawne rozwiązania. Powiąż usługę z adresem tailnet węzła, co pozwoli utrzymać ją poza publicznym interfejsem bez pośrednictwa proxy: przekaż --bind 100.101.102.104 lub odpowiednią opcję w konfiguracji, a w przypadku kontenera opublikuj port jako -p 100.101.102.104:3000:3000. Alternatywnie pozostaw usługę na interfejsie loopback i umieść przed nią Tailscale.
tailscale serve 3000Rozwiązanie to przekazuje żądania do http://127.0.0.1:3000 i udostępnia je wewnątrz sieci tailnet pod nazwą ts.net przez HTTPS, po uprzednim włączeniu certyfikatów HTTPS dla sieci tailnet. Usługa pozostaje prywatna dla Twoich węzłów. Publiczną wersją tego samego mechanizmu jest Funnel, a artykuł Tailscale serve a funnel wyjaśnia, który z nich wybrać.
Dwa powiązane zadania posiadają własne strony. Dostęp do całej sieci prywatnej, w której nie zainstalowano Tailscale, wymaga konfiguracji routera podsieci na VPS, natomiast kierowanie wychodzącego ruchu internetowego węzła przez inny węzeł wymaga konfiguracji węzła wyjściowego (exit node).
Zamykanie niepotrzebnych portów
Gdy administrator uzyskuje dostęp do serwera przez tailnet, publiczny port 22 staje się zbędny. To główna korzyść: zamknięty port jest odporny na ataki typu brute force, a logi przestają zapełniać się nieudanymi próbami logowania.
Kolejność działań ma znaczenie. Najpierw należy skonfigurować dostęp przez tailnet, potwierdzić możliwość zalogowania się z drugiej sesji, a dopiero potem usunąć regułę publiczną.
sudo ufw allow in on tailscale0
sudo ufw status verboseNastępnie należy usunąć publiczną regułę SSH i nawiązać połączenie, używając nazwy MagicDNS. Należy pamiętać, jak działa ufw allow in on tailscale0: ufa ono każdemu ruchowi przychodzącemu przez tunel, dlatego to plik polityki Tailscale, a nie ufw, staje się głównym mechanizmem kontroli dostępu. Politykę należy przygotować z uwzględnieniem tego faktu.
Ostrzeżenie dla użytkowników kontenerów: opublikowany port Docker instaluje własne reguły NAT i omija ufw, więc ufw deny go nie zamknie. Publikowanie portów Docker z pominięciem ufw wyjaśnia ten mechanizm. Publikowanie usług na adresie tailnet, zgodnie z powyższym opisem, pozwala uniknąć tego problemu.
Co chroni Tailscale, a czego nie chroni
Warto to jasno określić, ponieważ wersja marketingowa zaciera te granice.
Chronione: ruch między dwoma węzłami jest szyfrowany metodą end-to-end za pomocą WireGuard i żaden przekaźnik pośredniczący nie może go odczytać. Klucze prywatne nigdy nie opuszczają maszyny, na której zostały wygenerowane. Węzły nie wymagają otwartych portów przychodzących, więc w Internecie nie ma nic na portach 22 czy 5432, co można by przeskanować. Dostęp między węzłami jest określany przez plik polityki, a nie przez to, kto zna adres.
Niechronione: serwer koordynujący widzi graf urządzeń. Te metadane same w sobie są wrażliwe, ponieważ nazwy maszyn, właściciele, adresy i czasy aktywności opisują infrastrukturę. Serwer dystrybuuje również klucze, co stanowi większe ryzyko. Tailscale stwierdza to wprost: „Gdyby Tailscale było złośliwe i potajemnie dodało nowe węzły do sieci, mogłoby wysyłać lub odbierać ruch do istniejących węzłów w postaci jawnej”. Dostawca SSO znajduje się na tej samej ścieżce zaufania, ponieważ każdy, kto może wygenerować tam tożsamość, może dodać węzeł. Ponadto przejęty węzeł jest równorzędnym elementem wewnątrz tailnet, więc jego zasięg ogranicza jedynie polityka dostępu. To, czy stanowi to akceptowalne ryzyko, zależy od tego, przed kim się bronisz, a pełny model zaufania omawia każdy z tych przypadków, w tym możliwości skradzionego konta tożsamości.
Istnieją dwie odpowiedzi na ryzyko związane z dystrybucją kluczy. Pierwszą jest tailnet lock, który wymaga, aby istniejące zaufane węzły kryptograficznie podpisały nowy węzeł, zanim inne węzły go zaakceptują. Płaszczyzna kontrolna, która dodaje węzeł bez ważnego podpisu, jest ignorowana. Konsola administracyjna generuje dokładną linię tailscale lock init dla węzłów podpisujących, a każdy węzeł może potwierdzić to, co widzi.
tailscale lock statusWszystkie węzły powinny zgłaszać ten sam zestaw zaufanych kluczy podpisujących. Drugą odpowiedzią jest samodzielne uruchomienie płaszczyzny kontrolnej. Samodzielnie hostowany serwer koordynujący Headscale używa tego samego protokołu dla tych samych klientów, co przenosi graf urządzeń i dystrybucję kluczy na posiadany sprzęt. Wówczas odpowiadasz również za dostępność tego serwera. Jeśli nadal porównujesz samodzielnie hostowane płaszczyzny kontrolne, zamiast zdecydować się na tę, NetBird jest osobnym rozwiązaniem typu mesh VPN, którego serwer uruchamiasz w całości na własnym VPS.
Jedno ustawienie domyślne do zmiany pierwszego dnia. Nowy tailnet jest domyślnie permisywny: „domyślny plik polityki tailnet umożliwia komunikację między wszystkimi urządzeniami wewnątrz sieci”. Gdy tylko dodasz sekcję acls, model zmienia się na domyślną odmowę i przepuszczane są tylko Twoje reguły.
{
"tagOwners": {
"tag:server": ["autogroup:admin"]
},
"acls": [
{"action": "accept", "src": ["autogroup:member"], "dst": ["tag:server:22"]}
]
}Ta polityka pozwala członkom tailnet na dostęp do SSH na otagowanych serwerach i niczego więcej. Dodawaj regułę dla każdej usługi zamiast pozostawiać symbol wieloznaczny, ponieważ symbol wieloznaczny oznacza, że jeden skradziony klucz z laptopa daje dostęp do bazy danych.
Tryby awarii i komunikaty, które zobaczysz
tailscale status zawsze wyświetla relay. Dwa węzły nie nawiązały bezpośredniego połączenia. Uruchom tailscale netcheck na obu końcach. UDP: false oznacza, że ruch UDP jest blokowany na wyjściu, więc działać może tylko przekaźnik (relay). MappingVariesByDestIP: true oznacza, że na drodze znajduje się restrykcyjny NAT; zezwolenie na ruch przychodzący UDP 41641 po kontrolowanej stronie często rozwiązuje ten problem.
Węzeł, który działał miesiącami, zniknął. Jego klucz węzła wygasł zgodnie z domyślnym okresem 180 dni. Maszyna jest widoczna jako wygasła w konsoli administracyjnej, a wykonanie sudo tailscale up na danym urządzeniu przywraca jego działanie. Wyłącz wygasanie kluczy na serwerach, aby uniknąć powtórzenia tej sytuacji.
Peery są widoczne na liście, ale połączenia kończą się przekroczeniem czasu oczekiwania (timeout). Łączność działa, ale polityka bezpieczeństwa blokuje ruch. Sprawdź sekcję acls pod kątem reguły obejmującej dane źródło, miejsce docelowe i port. Odrzucony pakiet jest porzucany zamiast otrzymywać odpowiedź, dlatego zamiast Connection refused występuje timeout.
Nazwy MagicDNS nie są rozwiązywane. ping db-1 kończy się niepowodzeniem, podczas gdy ping 100.101.102.104 działa. Coś zastąpiło /etc/resolv.conf, więc zapytania nigdy nie docierają do resolvera stub pod adresem 100.100.100.100. Sprawdź cat /etc/resolv.conf pod kątem 100.100.100.100 i zweryfikuj, co jeszcze na maszynie zapisuje ten plik. Jest to ten sam typ problemu, co awaria DNS wewnątrz tunelu WireGuard.
tailscale up odrzuca Twój tag. Tag nie został zadeklarowany w sekcji tagOwners w pliku polityki. Dodaj go tam, a następnie uruchom polecenie ponownie.
FAQ
Czy Tailscale to VPN czy sieć typu mesh?
Oba określenia są poprawne i opisują różne warstwy działania. Tunele oparte są na protokole WireGuard, co czyni rozwiązanie siecią VPN. Topologia to mesh, ponieważ każdy węzeł buduje tunel bezpośrednio do każdego innego węzła, z którym się komunikuje, zamiast przesyłać wszystkie pakiety przez jeden centralny serwer. Serwer koordynujący znajduje się w ścieżce sterowania, a nie w ścieżce danych, więc w przypadku utraty łączności z nim istniejące tunele nadal przesyłają ruch. Podczas awarii serwera koordynującego niemożliwe jest jedynie dołączanie nowych węzłów oraz wprowadzanie zmian w kluczach lub politykach.
Czy Tailscale może odczytać mój ruch?
Nie, nie może odczytać jego zawartości. Ruch jest szyfrowany end-to-end między węzłami za pomocą WireGuard, klucze prywatne nigdy nie opuszczają węzłów, a przekaźnik DERP przekazuje pakiety, których nie jest w stanie odszyfrować. Tailscale widzi metadane: nazwy maszyn, właścicieli, klucze publiczne, adresy punktów końcowych oraz status online każdego węzła. Firma dystrybuuje również klucze, więc skompromitowany serwer koordynujący mógłby próbować dodać węzeł, któremu Twoja flota by zaufała. Mechanizm tailnet lock blokuje takie próby, wymagając podpisów z Twoich własnych zaufanych węzłów, a Headscale całkowicie eliminuje potrzebę korzystania z hostowanej płaszczyzny sterowania.
Czy muszę otwierać porty w zaporze dla Tailscale?
Prawie nigdy dla ruchu przychodzącego. Wytyczne Tailscale wskazują, że "w większości przypadków nie trzeba otwierać żadnych portów w zaporze". Dla ruchu wychodzącego węzeł potrzebuje dostępu przez TCP 443 do serwera koordynującego i przekaźników, oraz UDP 3478 dla STUN. Tunele bezpośrednie używają protokołu UDP z portem źródłowym, który domyślnie ma wartość 41641. Zezwolenie na ruch przychodzący UDP 41641 jest opcjonalne i pomaga jedynie w nawiązywaniu bezpośrednich połączeń w trudnych warunkach sieciowych.
Dlaczego inne węzły nie mogą połączyć się z moją usługą na porcie 3000?
W pierwszej kolejności sprawdź adres powiązania (bind address) za pomocą ss -tlnp. Proces nasłuchujący na 127.0.0.1:3000 odrzuca połączenia przychodzące na adres tailnet 100.x, ponieważ gniazdo akceptuje tylko połączenia lokalne (loopback), a klient otrzymuje Connection refused. Powiąż usługę z adresem tailnet lub uruchom tailscale serve 3000, aby przekierować ruch. Jeśli usługa nasłuchuje już na 0.0.0.0, a połączenie kończy się przekroczeniem czasu oczekiwania zamiast odrzuceniem, przyczyną jest reguła polityki lub lokalna zapora sieciowa, a nie adres powiązania.
Czy powinienem uruchomić Headscale zamiast serwera koordynującego Tailscale?
Uruchom Headscale, gdy graf urządzeń lub dystrybucja kluczy muszą pozostać w infrastrukturze, którą kontrolujesz, lub gdy tailnet musi działać bez zależności od zewnętrznej usługi. Klienci i protokół pozostają takie same. Kosztem jest konieczność samodzielnego utrzymywania serwera koordynującego, którego awaria uniemożliwi dołączanie nowych węzłów i stosowanie zmian w politykach. W przypadku małej floty, korzystanie z hostowanej płaszczyzny sterowania z włączonym tailnet lock jest zazwyczaj korzystniejszym rozwiązaniem.