[{"content":"Po uruchomieniu HAProxy przed Traefikiem usługi były dostępne, ale część logów pokazywała adres routera albo kontenera proxy zamiast adresu użytkownika. Samo poprawne kierowanie ruchu nie wystarczało: informację o kliencie trzeba było przekazać przez kolejne warstwy, a następnie odpowiednio zinterpretować.\nW poprzednim artykule opisałem uruchomienie HAProxy na EdgeRouterze i rozdzielenie ruchu między dwa serwery. Tutaj skupiam się na adresach klientów, logach i granicach zaufania.\nDomeny i adresacja są przykładowe. Router ma adres 10.10.10.254, a sieci Docker korzystają z zakresów 10.20.X.0/24. Nie są to rzeczywiste podsieci mojego wdrożenia. Przed zastosowaniem konfiguracji należy dopasować adresy do własnej infrastruktury.\nDlaczego aplikacja widzi adres proxy Ścieżka połączenia wygląda następująco:\nKlient → HAProxy na routerze → Traefik → aplikacja lub serwer WWW Każde proxy otwiera nowe połączenie do następnego elementu. Bez dodatkowej konfiguracji backend widzi więc adres bezpośredniego nadawcy TCP.\nW moim układzie wykorzystuję dwa mechanizmy:\nOdcinek Sposób przekazania informacji o kliencie HAProxy → Traefik PROXY protocol v2 Traefik → aplikacja Nagłówki HTTP, przede wszystkim X-Forwarded-For PROXY protocol nie zmienia źródła pakietów. Podobnie nagłówek HTTP nie tworzy bezpośredniego połączenia aplikacji z użytkownikiem. Oba mechanizmy przekazują metadane, które odbiorca musi świadomie wykorzystać.\nW LAN lokalny DNS nadal kieruje klientów bezpośrednio do serwerów. Poprawnym adresem w logu może być wtedy prywatny adres klienta. Celem nie jest publiczne IP w każdym wpisie, lecz rzeczywiste źródło danego żądania.\nHAProxy → Traefik: PROXY protocol HAProxy przekazuje HTTPS bez odszyfrowania. Na tym etapie informację o kliencie wysyłam przez PROXY protocol v2, dodając send-proxy-v2 do backendów:\nbackend https_157 mode tcp server srv157 10.10.10.157:443 check send-proxy-v2 backend https_251 mode tcp server srv251 10.10.10.251:443 check send-proxy-v2 backend http_157 mode http server srv157 10.10.10.157:80 check send-proxy-v2 backend http_251 mode http server srv251 10.10.10.251:80 check send-proxy-v2 Po stronie Traefika uzupełniłem konfigurację statyczną TOML:\n[entryPoints] [entryPoints.web] address = \u0026#34;:80\u0026#34; [entryPoints.web.proxyProtocol] trustedIPs = [\u0026#34;10.10.10.254/32\u0026#34;] [entryPoints.websecure] address = \u0026#34;:443\u0026#34; [entryPoints.websecure.proxyProtocol] trustedIPs = [\u0026#34;10.10.10.254/32\u0026#34;] To fragment do połączenia z istniejącą konfiguracją. Najpierw przygotowałem odbiór w Traefiku i zrestartowałem go po zmianie konfiguracji statycznej. Dopiero potem włączyłem wysyłanie PROXY protocol w HAProxy.\nNie włączałem proxyProtocol.insecure. Traefik ufa informacjom przekazanym przez router, nie przez dowolnego klienta. Na publicznych listenerach HAProxy nie dodawałem accept-proxy.\nEntryPointy nadal przyjmują zwykłe połączenia bez PROXY protocol, dzięki czemu bezpośredni dostęp z LAN pozostaje możliwy.\nZaufanie do PROXY protocol i ustawienia forwardedHeaders.trustedIPs to osobne mechanizmy. Nie rozszerzałem zaufania do nagłówków HTTP na całe sieci tylko po to, aby naprawić logi.\nForgejo: zaufanie do Traefika W istniejącej sekcji [security] pliku app.ini ustawiłem:\n[security] REVERSE_PROXY_LIMIT = 1 REVERSE_PROXY_TRUSTED_PROXIES = 10.20.10.4/32 10.20.10.4 oznacza przykładowy adres Traefika w sieci Forgejo. Po wskazaniu właściwego adresu i restarcie aplikacja zaczęła poprawnie rozpoznawać klientów.\nBezpośrednim HTTP reverse proxy dla Forgejo jest Traefik. HAProxy przekazuje wcześniej informację przez PROXY protocol, dlatego nie zwiększałem mechanicznie limitu do dwóch tylko dlatego, że na schemacie są dwa proxy.\nNie włączałem przy tym uwierzytelniania przez nagłówki reverse proxy. Rozpoznawanie IP i logowanie użytkowników przez nagłówki to różne funkcje.\nDynamiczny adres proxy w odseparowanych stackach Traefik jest dołączany do sieci poszczególnych stacków przez traefik_network_connector. Dzięki temu aplikacje nie muszą współdzielić jednej dużej sieci, ale adres Traefika może zmieniać się po odtworzeniu kontenera.\nW takim układzie alternatywą dla pojedynczego /32 jest stała podsieć stacka i zaufanie do tego zakresu:\nnetworks: default: driver: bridge ipam: config: - subnet: 10.20.10.0/24 [security] REVERSE_PROXY_LIMIT = 1 REVERSE_PROXY_TRUSTED_PROXIES = 10.20.10.0/24 Stała jest podsieć, a nie adres samego Traefika.\nTo kompromis: zaufanie obejmuje także inne kontenery w tej sieci. Taki zakres powinien być wydzielony i kontrolowany. Nie ma potrzeby ufać całemu 10.0.0.0/8 ani wszystkim sieciom Docker.\nKolizja podsieci Docker Przy ręcznym wyborze zakresu pojawił się błąd:\ninvalid pool request: Pool overlaps with other one on this address space Nowa podsieć nakładała się na istniejącą. Zamiast usuwać sieci innych usług, sprawdziłem ich zakresy i tablicę routingu hosta:\ndocker network inspect $(docker network ls -q) \\ --format \u0026#39;{{.Name}}: {{range .IPAM.Config}}{{.Subnet}} {{end}}\u0026#39; ip -4 route Adresację trzeba dopasować również do sieci LAN i VPN, nie tylko do istniejących sieci Docker. Wszystkie zakresy pokazane w tym artykule są przykładami, nie propozycją uniwersalnej adresacji.\nMatomo: aplikacja i Apache mają osobne logi Matomo miało już ustawienia proxy w config/config.ini.php:\n[General] proxy_client_headers[] = \u0026#34;HTTP_X_FORWARDED_FOR\u0026#34; proxy_host_headers[] = \u0026#34;HTTP_X_FORWARDED_HOST\u0026#34; proxy_scheme_headers[] = \u0026#34;HTTP_X_FORWARDED_PROTO\u0026#34; Mimo tego docker logs matomo nadal pokazywało adres proxy. Przyczyną było to, że oglądałem logi dostępu Apache, a nie dane o odwiedzających rozpoznawane przez Matomo.\nZmiana konfiguracji aplikacji PHP nie zmienia automatycznie sposobu logowania w serwerze WWW.\nW moim kontenerze moduł mod_remoteip nie był załadowany, a VirtualHost używał formatu combined:\nCustomLog ${APACHE_LOG_DIR}/access.log combined Trwała konfiguracja mod_remoteip Na hoście przygotowałem /opt/matomo/apache/remoteip.conf:\nLoadModule remoteip_module /usr/lib/apache2/modules/mod_remoteip.so RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 10.20.20.4 LogFormat \u0026#34;%a %l %u %t \\\u0026#34;%r\\\u0026#34; %\u0026gt;s %O \\\u0026#34;%{Referer}i\\\u0026#34; \\\u0026#34;%{User-Agent}i\\\u0026#34;\u0026#34; combined 10.20.20.4 to przykładowy adres Traefika w sieci Matomo. Przy kontrolowanej, stałej podsieci można wskazać odpowiedni CIDR zamiast jednego adresu.\nRemoteIPInternalProxy pozwala uwzględniać również prywatne adresy klientów LAN przekazane przez zaufane proxy. Format %a zapisuje adres po przetworzeniu przez moduł. Oryginalny adres połączenia można dodatkowo logować jako %{c}a.\nDo istniejącej listy wolumenów dodałem:\nvolumes: - /opt/matomo/apache/remoteip.conf:/etc/apache2/conf-enabled/zz-remoteip.conf:ro To rozwiązanie odpowiada używanemu przeze mnie wariantowi obrazu Apache. Przed zastosowaniem go w innym obrazie trzeba sprawdzić lokalizację modułu i sposób wczytywania konfiguracji. Jeśli moduł jest już włączony, nie należy ładować go ponownie.\nNie zmieniałem entrypoint, command ani VirtualHosta. Nadpisałem używany format combined, zamiast dodawać drugi CustomLog i dublować wpisy.\nPo odtworzeniu kontenera nowe logi Apache pokazywały właściwe adresy. Ostrzeżenie o braku ServerName nie było przyczyną problemu.\nTa zmiana nie przepisuje historii i nie zastępuje ustawień prywatności ani anonimizacji IP w Matomo.\nNginx: ten sam problem, inny moduł Podczas uruchamiania bloga za Traefikiem podobną konfigurację zastosowałem w Nginx. Na odcinku Traefik → Nginx korzystam z nagłówka HTTP, nie z PROXY protocol:\nset_real_ip_from 10.20.50.0/24; real_ip_header X-Forwarded-For; real_ip_recursive on; Zakres 10.20.50.0/24 oznacza tutaj wydzieloną, zaufaną sieć bloga, w której Traefik łączy się z Nginx.\nPo przetworzeniu nagłówka $remote_addr zawiera adres klienta. Do diagnostyki przydaje się jednoczesne zapisanie adresu bezpośredniego proxy:\nlog_format blog \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#39; \u0026#39;\u0026#34;$request\u0026#34; $status $body_bytes_sent \u0026#39; \u0026#39;\u0026#34;$http_referer\u0026#34; \u0026#34;$http_user_agent\u0026#34; \u0026#39; \u0026#39;proxy=$realip_remote_addr xff=\u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; Definicję formatu umieszczam w kontekście http, a we właściwym bloku server wybieram go przez:\naccess_log /var/log/nginx/access.log blog; W oficjalnym obrazie Nginx pliki z /etc/nginx/conf.d/ są standardowo dołączane w kontekście http, więc można umieścić tam definicję formatu i blok serwera.\nNie dodawałem proxy_protocol do listen 80, ponieważ Traefik przekazuje do tego backendu zwykłe żądania HTTP.\nPozostałe usługi Nextcloud Dla Nextclouda istotne jest zaufanie do Traefika jako bezpośredniego reverse proxy. Fragment istniejącej tablicy konfiguracji może wyglądać tak:\n\u0026#39;trusted_proxies\u0026#39; =\u0026gt; [ \u0026#39;10.20.30.4\u0026#39;, ], Adres musi odpowiadać proxy widzianemu przez Nextcloud. Nie jest to osobny plik PHP do wklejenia, lecz fragment config/config.php.\nW moim wdrożeniu po poprawieniu połączenia HAProxy–Traefik Nextcloud pokazywał właściwe adresy, więc nie przebudowywałem jego działającej konfiguracji.\nAuthelia Authelia również zaczęła pokazywać właściwy adres po poprawieniu przekazywania informacji między HAProxy a Traefikiem.\nNie dodawałem nowej opcji trusted_proxies do jej konfiguracji. Zachowałem dotychczasową integrację uwierzytelniania. Zaufanie do PROXY protocol na entrypoincie Traefika i zaufanie do nagłówków w obsłudze ForwardAuth to osobne zagadnienia.\nHeadscale i Headplane Tutaj ważne było rozdzielenie dwóch komponentów:\nHeadscale obsługuje klientów i udostępnia API; Headplane jest panelem administracyjnym, który sam wykonuje żądania do API Headscale. Dla wersji Headscale obsługujących trusted_proxies konfiguracja zaufania znajduje się na głównym poziomie config.yaml:\ntrusted_proxies: - \u0026#34;10.20.40.4/32\u0026#34; Można też wskazać świadomie zaufaną, wydzieloną podsieć:\ntrusted_proxies: - \u0026#34;10.20.40.0/24\u0026#34; Headscale wykorzystuje nagłówki adresu klienta tylko od zaufanego nadawcy. Proxy musi zapewniać wiarygodne wartości, również dla nagłówków innych niż X-Forwarded-For. W obsługiwanych wersjach kolejność sprawdzania to True-Client-IP, X-Real-IP, a następnie X-Forwarded-For.\nNie dodawałem PROXY protocol między Traefikiem a Headscale. Na tym odcinku korzystam z HTTP i nagłówków.\nNie każdy prywatny adres w API oznacza jednak błąd:\nPrzeglądarka → Traefik → Headplane | +→ API Headscale Gdy Headplane wywołuje API, klientem tego konkretnego połączenia jest Headplane. Prywatny adres jego kontenera może być prawidłowy — nie każda operacja jest bezpośrednim przekazaniem żądania przeglądarki.\nWpisy z /machine/register, /machine/map czy /api/v1/node, które analizowałem, pochodziły z Headscale. Nie traktowałem ich jako dowodu nieprawidłowego działania loggera Headplane. Obsługa adresów w logach poszczególnych ścieżek, w tym połączeń Noise, zależy również od wersji Headscale.\nGranice zaufania Najważniejsza jest nie sama nazwa nagłówka, lecz ustalenie, kto może dostarczać informacje o kliencie:\nTraefik ufa PROXY protocol od routera. Aplikacja ufa nagłówkom od swojego reverse proxy. Apache i Nginx przetwarzają adresy tylko od wskazanych proxy. Backendów nie należy udostępniać tak, aby niezaufany klient mógł ominąć proxy i samodzielnie zadeklarować dowolne IP.\nZaufanie do podsieci ułatwia pracę z dynamicznymi kontenerami, ale rozszerza grupę nadawców. Dlatego powinno dotyczyć konkretnej, kontrolowanej sieci, a nie całej prywatnej adresacji.\nPoprawne logowanie adresów nie oznacza też, że trzeba przechowywać je bez ograniczeń. Retencja logów, rotacja i ustawienia prywatności analityki pozostają osobnymi decyzjami.\nEfekt Nie była to jedna globalna poprawka. Na wejściu przekazałem metadane przez PROXY protocol, dalej wykorzystałem nagłówki HTTP, a na końcu skonfigurowałem ich interpretację w aplikacjach i serwerach WWW.\nForgejo potrzebowało listy zaufanych proxy. W Matomo kluczowe było rozróżnienie danych aplikacji od logów Apache. Nginx wymagał konfiguracji modułu Real IP. Nextcloud i Authelia korzystały już z poprawionej ścieżki, a przy Headscale trzeba było dodatkowo rozróżnić użytkownika, klienta VPN i serwerowe wywołanie API.\nW efekcie logi pokazują rzeczywiste źródła żądań zamiast jednego adresu routera lub kontenera — bez bezwarunkowego zaufania do nagłówków dostarczanych z Internetu.\n","permalink":"https://blog.majchrowski.waw.pl/posts/prawdziwe-ip-za-haproxy-i-traefikiem/","summary":"Usługi działały, ale logi pokazywały adres routera albo kontenera proxy. Rozwiązaniem było połączenie PROXY protocol, nagłówków HTTP i zaufania do właściwego reverse proxy.","title":"Prawdziwe IP klientów za HAProxy i Traefikiem"},{"content":"Na EdgeRouterze 4 uruchomiłem HAProxy, które rozdziela ruch HTTP i HTTPS między dwa serwery z Traefikiem. Dzięki temu usługi na obu serwerach są dostępne z Internetu na standardowych portach — bez dopisywania :444 do adresu.\nCertyfikaty i routing do aplikacji pozostały w Traefiku. Router odpowiada tylko za wybór serwera: dla HTTP na podstawie nagłówka Host, a dla HTTPS na podstawie SNI.\nPoniżej opisuję powód tej zmiany, docelowy przepływ ruchu, kompilację HAProxy dla EdgeOS i konfigurację całego rozwiązania.\nDomeny i adresacja zostały zmienione. Sieć lokalna w przykładach to 10.10.10.0/24. Nazwy polityk firewalla, interfejsy i numery reguł odpowiadają mojemu wdrożeniu — nie są uniwersalną konfiguracją do wklejenia na dowolnym routerze.\nProblem: dwa serwery i różne adresy usług Początkowo publiczne porty 80 i 443 były przekierowane na pierwszy serwer, 10.10.10.251. Drugi serwer, docker1 pod adresem 10.10.10.157, miał osobne przekierowanie z publicznego portu 444 na swój port 443.\nDostęp do usługi na drugim serwerze wyglądał więc tak:\nhttps://usluga1.example.com:444/ W sieci lokalnej korzystam z widoków DNS. Ta sama nazwa rozwiązuje się bezpośrednio na prywatny adres właściwego serwera. Lokalnie używałem więc:\nhttps://usluga1.example.com/ Usługa działała, ale jej adres zależał od miejsca połączenia:\nz Internetu: publiczny adres routera i port 444; z LAN: prywatny adres serwera i standardowy port 443. Komplikowało to również SSO z Authelią. Callbacki i adresy powrotne musiały uwzględniać wariant z portem albo bez niego.\nChciałem, żeby użytkownik zawsze korzystał z jednego adresu:\nhttps://usluga1.example.com/ Bez zmiany portu w przeglądarce i bez przenoszenia wszystkich usług na jeden serwer.\nDocelowa architektura HAProxy przyjmuje ruch z Internetu na routerze i wybiera serwer na podstawie nazwy domeny.\nInternet / publiczny DNS | | publiczny adres routera, TCP/80 i TCP/443 v EdgeRouter 4 / HAProxy | +-- wybrane domeny --\u0026gt; 10.10.10.157 --\u0026gt; Traefik --\u0026gt; usługi | +-- pozostałe ------\u0026gt; 10.10.10.251 --\u0026gt; Traefik --\u0026gt; usługi LAN / lokalny widok DNS | +-- prywatny adres właściwego serwera --\u0026gt; Traefik --\u0026gt; usługi Lokalny DNS nadal kieruje klientów bezpośrednio do serwerów. Standardowy ruch z LAN nie musi przechodzić przez HAProxy ani przez publiczny adres routera.\nZ perspektywy użytkownika URL pozostaje jednak identyczny. Zmienia się odpowiedź DNS i ścieżka sieciowa, ale nie schemat, nazwa hosta ani port.\nPodział odpowiedzialności HAProxy i Traefik pełnią różne role:\nElement Zadanie HAProxy na EdgeRouterze Wybór serwera docelowego według domeny Traefik na serwerach Obsługa certyfikatów, terminacja TLS i routing do aplikacji Lokalny DNS Bezpośrednie kierowanie klientów LAN do właściwego serwera Dla HTTP HAProxy odczytuje nagłówek Host.\nDla HTTPS odczytuje SNI z TLS ClientHello, a następnie przekazuje zaszyfrowany strumień do odpowiedniego Traefika. Router nie odszyfrowuje ruchu i nie przechowuje certyfikatów usług.\nMiędzy HAProxy a Traefikiem wykorzystuję dodatkowo PROXY protocol v2 do przekazywania informacji o połączeniu klienta.\nKompilacja HAProxy dla EdgeOS Środowisko i architektura Router działał na EdgeOS z następującym środowiskiem:\nElement Wartość Sprzęt EdgeRouter 4 Baza systemu Debian 9 Stretch Kernel 4.9.79-UBNT glibc 2.24 Architektura raportowana przez kernel mips64 Architektura userspace według dpkg mips Istotne było rozróżnienie architektury kernela i userspace. Samo mips64 w wyniku polecenia sprawdzającego kernel nie oznaczało, że potrzebuję 64-bitowej binarki.\nHAProxy 3.4.0 zbudowałem poza routerem, na Ubuntu 20.04, przy użyciu mips-linux-gnu-gcc 9.4.0.\nWynikiem był statycznie linkowany ELF 32-bit big-endian, MIPS32 rel2. Na EdgeOS nie instalowałem kompilatora ani nie aktualizowałem bibliotek systemowych.\nWersja 3.4.0 jest wersją wykorzystaną w tym wdrożeniu, a nie zaleceniem pomijania późniejszych poprawek.\nPrzygotowanie środowiska budowania Na komputerze kompilującym zainstalowałem narzędzia:\nsudo apt-get update sudo apt-get install --no-install-recommends \\ make \\ gcc-mips-linux-gnu \\ binutils-mips-linux-gnu \\ libc6-dev-mips-cross \\ git Miałem już klon repozytorium źródeł HAProxy w ~/haproxy. Z niego przygotowałem osobny katalog dla wybranego wydania:\ncd ~/haproxy git fetch --tags origin git worktree add --detach ../haproxy-3.4.0 v3.4.0 cd ../haproxy-3.4.0 Jeżeli odtwarzasz ten proces od zera, najpierw przygotuj lokalny klon repozytorium źródeł. Powyższy fragment zaczyna się od istniejącego katalogu ~/haproxy.\nBudowanie binarki Końcowe polecenie kompilacji wyglądało następująco:\nmake SHELL=\u0026#39;/bin/bash -o pipefail\u0026#39; clean make -j2 \\ SHELL=\u0026#39;/bin/bash -o pipefail\u0026#39; \\ TARGET=linux-glibc-legacy \\ CPU=generic \\ CC=mips-linux-gnu-gcc \\ LD=mips-linux-gnu-gcc \\ AR=mips-linux-gnu-ar \\ NM=mips-linux-gnu-nm \\ LDFLAGS=\u0026#39;-static\u0026#39; \\ ADDLIB=\u0026#39;-lpthread\u0026#39; \\ USE_THREAD=1 \\ USE_OPENSSL= \\ USE_LUA= \\ USE_PCRE= \\ USE_PCRE2= \\ USE_ZLIB= \\ USE_LIBCRYPT= \\ USE_CRYPT_H= \\ USE_DL= \\ USE_BACKTRACE= \\ USE_GETADDRINFO= OpenSSL nie było potrzebne, ponieważ HAProxy nie kończy połączeń TLS. Router wybiera backend na podstawie ClientHello, a właściwy handshake i obsługa certyfikatu odbywają się w Traefiku.\nBackendy wskazałem adresami IP. Statyczna glibc pozostawia ograniczenia związane z NSS, więc samo statyczne linkowanie nie oznacza pełnej niezależności każdej funkcji programu od bibliotek systemu docelowego.\nProblemy podczas kompilacji Do działającej binarki doprowadziły trzy istotne poprawki:\nProblem Zmiana make: command: Command not found Wymuszenie Basha przez SHELL='/bin/bash -o pipefail' Brak symboli pthread_* podczas statycznego linkowania Dodanie ADDLIB='-lpthread' Odwołanie do thread_cpus_enabled_at_boot przy wyłączonych wątkach Przebudowanie z USE_THREAD=1 Binarka ma obsługę wielu wątków, ale w konfiguracji uruchomieniowej ustawiłem nbthread 1. Zmiana tej wartości nie wymaga ponownej kompilacji.\nPrzeniesienie binarki na router Wynikowy plik haproxy przeniosłem na router do katalogu domowego użytkownika.\nDalsze polecenia zakładają więc, że na EdgeRouterze istnieje plik:\n$HOME/haproxy Docelowo binarka trafi do /config/haproxy/bin/haproxy, razem z pozostałymi plikami tego wdrożenia.\nPrzygotowanie routera Przeniesienie panelu administracyjnego GUI EdgeOS zajmowało porty 80 i 443, na których miało nasłuchiwać HAProxy.\nPanel przeniosłem na HTTP/8080 i HTTPS/8443:\nconfigure set service gui http-port 8080 set service gui https-port 8443 commit save exit Po zmianie panel HTTPS jest dostępny pod adresem:\n[https://10.10.10.254:8443/](https://10.10.10.254:8443/) Portów administracyjnych nie dodałem do wyjątku WAN dla HAProxy.\nUsunięcie starych przekierowań Poprzedni układ opierał się na trzech regułach:\nReguła Poprzednia funkcja 2 HTTP/80 → 10.10.10.251:80 3 HTTPS/443 → 10.10.10.251:443, reguła obejmowała TCP i UDP 15 HTTPS/444 → 10.10.10.157:443 Usunąłem je, ponieważ połączenia na portach 80 i 443 miał od tej pory przyjmować proces działający na samym routerze.\nDostęp dopuściłem w polityce local interfejsu WAN pppoe0. W mojej konfiguracji polityka ta nazywa się ibs-local.\nconfigure delete port-forward rule 2 delete port-forward rule 3 delete port-forward rule 15 set firewall name ibs-local rule 60 description \u0026#39;HTTP HTTPS TCP to HAProxy\u0026#39; set firewall name ibs-local rule 60 action accept set firewall name ibs-local rule 60 protocol tcp set firewall name ibs-local rule 60 destination port 80,443 set firewall name ibs-local rule 60 log disable compare commit save exit Pozostałe przekierowania i reguły masquerade zostały bez zmian.\nUsunięcie reguły 3 objęło również stare UDP/443. Nowa konfiguracja obsługuje TCP/80 i TCP/443, bez HTTP/3/QUIC.\nPrzeniesienie obsługi z DNAT do procesu na routerze zmienia miejsce filtrowania ruchu. W tym wdrożeniu potrzebny był dostęp w polityce local, a nie tylko reguły dotyczące ruchu przekazywanego przez router.\nDostęp do HAProxy z LAN W polityce home-local, przypiętej do interfejsu VLAN HOME, roboczą regułę zastąpiłem dostępem do TCP/80 i TCP/443:\nconfigure delete firewall name home-local rule 25 set firewall name home-local rule 25 description \u0026#39;HTTP HTTPS to HAProxy\u0026#39; set firewall name home-local rule 25 action accept set firewall name home-local rule 25 protocol tcp set firewall name home-local rule 25 destination port 80,443 set firewall name home-local rule 25 log disable commit save exit Ta reguła umożliwia dostęp do samego HAProxy z LAN. Nie zmienia jednak podstawowego przepływu ruchu: lokalne widoki DNS nadal wskazują prywatne adresy serwerów, więc standardowe połączenia do usług omijają routerowe proxy.\nNumery 25 i 60 są właściwe dla mojego wdrożenia. Przed zastosowaniem podobnej konfiguracji trzeba dopasować nazwy polityk, kolejność i numery reguł do własnego firewalla.\nPołączenie z Traefikiem Przygotowanie odbioru PROXY protocol Po dodaniu HAProxy przed Traefikiem chciałem zachować informację o rzeczywistym adresie klienta. Między routerem a Traefikiem wykorzystałem do tego PROXY protocol v2.\nNie jest to terminacja TLS. Informacja o połączeniu jest przekazywana do Traefika, ale handshake TLS i certyfikat nadal obsługuje serwer docelowy.\nNajpierw przygotowałem odbiór po stronie Traefika. W jego statycznej konfiguracji TOML uzupełniłem entrypointy:\n[entryPoints] [entryPoints.web] address = \u0026#34;:80\u0026#34; [entryPoints.web.proxyProtocol] trustedIPs = [\u0026#34;10.10.10.254/32\u0026#34;] [entryPoints.websecure] address = \u0026#34;:443\u0026#34; [entryPoints.websecure.proxyProtocol] trustedIPs = [\u0026#34;10.10.10.254/32\u0026#34;] To fragment do połączenia z istniejącą konfiguracją, a nie zamiennik całego pliku Traefika.\nPozostały routing, przekierowanie HTTP na HTTPS oraz obsługa certyfikatów zostały zachowane.\nTraefik ufa informacjom PROXY protocol wyłącznie od routera, czyli 10.10.10.254. Jednocześnie entrypointy przyjmują zwykłe połączenia bez tego nagłówka, co pozwala zachować bezpośredni dostęp klientów z lokalnej sieci.\nPo zmianie konfiguracji statycznej zrestartowałem Traefika. Dopiero później włączyłem wysyłanie PROXY protocol w HAProxy.\nKonfiguracja HAProxy Plik /config/haproxy.cfg zawiera osobne frontendy dla HTTP i HTTPS.\nWybrane domeny kieruję na 10.10.10.157. Pozostały ruch trafia do domyślnego backendu na 10.10.10.251.\nglobal nbthread 1 maxconn 256 log stdout format raw local0 defaults log global timeout connect 5s timeout client 60s timeout server 60s frontend https_in bind :443 mode tcp option tcplog tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } acl do_157 req.ssl_sni -i usluga1.example.com acl do_157 req.ssl_sni -i usluga2.example.com acl do_157 req.ssl_sni -i usluga3.example.com acl do_157 req.ssl_sni -i usluga4.example.com acl do_157 req.ssl_sni -i usluga5.example.com acl do_157 req.ssl_sni -i usluga6.example.com use_backend https_157 if do_157 default_backend https_251 backend https_157 mode tcp server srv157 10.10.10.157:443 check send-proxy-v2 backend https_251 mode tcp server srv251 10.10.10.251:443 check send-proxy-v2 frontend http_in bind :80 mode http option httplog acl do_157 hdr(host),lower,field(1,:) -m str usluga1.example.com acl do_157 hdr(host),lower,field(1,:) -m str usluga2.example.com acl do_157 hdr(host),lower,field(1,:) -m str usluga3.example.com acl do_157 hdr(host),lower,field(1,:) -m str usluga4.example.com acl do_157 hdr(host),lower,field(1,:) -m str usluga5.example.com acl do_157 hdr(host),lower,field(1,:) -m str usluga6.example.com use_backend http_157 if do_157 default_backend http_251 backend http_157 mode http server srv157 10.10.10.157:80 check send-proxy-v2 backend http_251 mode http server srv251 10.10.10.251:80 check send-proxy-v2 Kilka szczegółów tej konfiguracji jest istotnych:\nsend-proxy-v2 włącza przekazywanie informacji o kliencie do Traefika. Odbiorca musi być wcześniej przygotowany do jego obsługi. Na publicznych frontendach nie dodawałem accept-proxy. default_backend obejmuje także połączenia, które nie pasują do listy domen. HAProxy nie wymusza przekierowania HTTP na HTTPS. Pozostawiłem to Traefikowi. bind :80 i bind :443 nie przypinają usługi wyłącznie do WAN. Dostęp do listenerów na lokalnych adresach IPv4 kontroluje firewall. Obsługa IPv6 nie jest częścią tej konfiguracji. Uruchamianie jako usługa Rozmieszczenie plików Binarkę, konfigurację i definicję usługi umieściłem w /config:\n/config/haproxy/bin/haproxy /config/haproxy.cfg /config/haproxy/haproxy-edge.service Skopiowaną wcześniej do katalogu domowego binarkę zainstalowałem następująco:\nsudo mkdir -p /config/haproxy/bin sudo install -o root -g root -m 0755 \\ \u0026#34;$HOME/haproxy\u0026#34; \\ /config/haproxy/bin/haproxy sudo chown root:root /config/haproxy.cfg sudo chmod 0644 /config/haproxy.cfg Polecenia zakładają, że konfiguracja HAProxy jest już zapisana w /config/haproxy.cfg.\nDefinicja systemd Plik /config/haproxy/haproxy-edge.service:\n[Unit] Description=HAProxy on EdgeRouter Wants=network-online.target After=network-online.target [Service] Type=simple ExecStartPre=/config/haproxy/bin/haproxy -c -f /config/haproxy.cfg ExecStart=/config/haproxy/bin/haproxy -W -db -f /config/haproxy.cfg ExecReload=/config/haproxy/bin/haproxy -c -f /config/haproxy.cfg ExecReload=/bin/kill -USR2 $MAINPID Restart=on-failure RestartSec=5s KillMode=mixed KillSignal=SIGUSR1 TimeoutStopSec=60s StandardOutput=journal StandardError=journal SyslogIdentifier=haproxy-edge [Install] WantedBy=multi-user.target HAProxy działa w trybie master-worker, na pierwszym planie pod kontrolą systemd.\nPrzed startem i przeładowaniem wykonywane jest sprawdzenie konfiguracji. Przeładowanie odbywa się przez sygnał USR2 do procesu master, a nieoczekiwane zakończenie procesu powoduje ponowienie startu.\nPo zatrzymaniu instancji uruchamianej wcześniej ręcznie zarejestrowałem usługę:\nsudo ln -sfn \\ /config/haproxy/haproxy-edge.service \\ /etc/systemd/system/haproxy-edge.service sudo systemctl daemon-reload sudo systemctl enable haproxy-edge.service sudo systemctl start haproxy-edge.service Odtwarzanie rejestracji po zmianie firmware Pliki wdrożenia pozostają w /config, ale rejestracja usługi znajduje się również w /etc.\nDo jej odtworzenia po zmianie obrazu firmware przygotowałem skrypt:\n/config/scripts/post-config.d/90-haproxy-edge Zawartość:\n#!/bin/sh set -eu ln -sfn \\ /config/haproxy/haproxy-edge.service \\ /etc/systemd/system/haproxy-edge.service systemctl daemon-reload systemctl enable haproxy-edge.service systemctl --no-block start haproxy-edge.service Nadałem mu uprawnienia do wykonania:\nsudo chmod 0755 \\ /config/scripts/post-config.d/90-haproxy-edge Skrypt odtwarza dowiązanie do definicji usługi, przeładowuje konfigurację systemd i uruchamia HAProxy.\nNie zmienia to faktu, że własna binarka i hook startowy są rozszerzeniem utrzymywanym samodzielnie, a nie wbudowaną funkcją EdgeOS.\nBieżąca obsługa Przeładowanie konfiguracji:\nsudo systemctl reload haproxy-edge Stan usługi:\nsudo systemctl status haproxy-edge --no-pager -l Podgląd logów:\nsudo journalctl -u haproxy-edge -f Restart:\nsudo systemctl restart haproxy-edge Efekt i ograniczenia Po zmianie oba serwery korzystają z jednego publicznego wejścia i standardowych portów.\nWybrane domeny trafiają na 10.10.10.157, a reszta na dotychczasowy serwer 10.10.10.251. Z adresów usług zniknął port :444.\nLokalne widoki DNS nadal zapewniają bezpośredni dostęp po prywatnych adresach. URL usługi jest jednak identyczny w LAN i w Internecie, co uprościło również adresy powrotne SSO.\nCertyfikaty oraz routing do aplikacji pozostały w Traefiku. EdgeRouter dostał warstwę wyboru serwera bez dokładania kolejnej maszyny na wejściu do sieci.\nRozwiązanie ma jednak jasno określone granice:\nobsługuje TCP/80 i TCP/443, bez HTTP/3/QUIC; nie obejmuje konfiguracji IPv6; nie kończy TLS na routerze; wymaga zgodnej konfiguracji PROXY protocol po stronie HAProxy i Traefika; opiera się na własnej binarce i samodzielnie utrzymywanej integracji z EdgeOS; przetwarza strumień na CPU routera — nie jest sprzętowo offloadowanym DNAT. W moim przypadku najważniejszy efekt to jeden spójny adres każdej usługi, niezależnie od miejsca połączenia, przy zachowaniu dwóch osobnych serwerów i dotychczasowej obsługi aplikacji w Traefiku.\n","permalink":"https://blog.majchrowski.waw.pl/posts/haproxy-na-edgeos/","summary":"Dwa serwery z Traefikiem, jeden publiczny adres IP i problem portu :444. Opisuję, jak uruchomiłem HAProxy na EdgeRouterze 4, rozdzieliłem ruch według domen i zachowałem rzeczywiste adresy klientów dzięki PROXY protocol.","title":"HAProxy na EdgeRouter 4: dwa serwery i jeden publiczny adres"}]