HAProxy 302 către o aplicație privată: cine deschide al doilea salt?
O redirecționare returnează a doua conexiune browserului. Trimite cererea către un backend, apoi corectează redirecționările absolute și cookie-urile aplicației.
Cineva îndreaptă un host HAProxy public către o aplicație internă și întreabă de ce browserul nu poate urma. Proxy-ul ajunge la aplicație prin VPN-ul său, redirecționarea returnează un antet Location, iar browserul încearcă apoi o adresă care există doar în interiorul tunelului. Din exterior pagina pur și simplu se blochează. Decizia este despre ce parte deschide a doua conexiune, și exact asta separă o redirecționare de un backend.
Două căi care arată la fel în configurație
O redirecționare încheie schimbul cu un răspuns. Clientul citește Location și începe o cerere nouă de unde se află, adică din rețeaua pe care nu ai controlat-o niciodată. Proxy-ul nu mai este implicat. Tot ce urmează depinde de DNS-ul, ruta și încrederea clientului însuși, așa că rezultatul variază în funcție de locul în care utilizatorul a deschis browserul:
GET /app client -> proxy
302 Location proxy -> client (http://10.40.0.20:8080/)
GET /app client -> 10.40.0.20:8080 client network, no route
Un backend al proxy-ului ține a doua etapă pe partea serverului. Proxy-ul o deschide pe calea pe care o are deja, iar clientul continuă să vorbească cu un singur nume public. Dacă tunelul cade, clientul primește o eroare în locul unui URL pe care nu îl poate urma. Această diferență este și ceea ce face defecțiunea diagnosticabilă, pentru că eroarea apare acolo unde sunt jurnalele:
GET /app client -> proxy
GET /app proxy -> 10.40.0.20:8080 over the VPN
response proxy -> client
Documentația de redirecționare a HAProxy trasează exact această linie: a răspunde clientului cu o redirecționare este o acțiune diferită de a trimite cererea către un backend. Cele două directive stau la câteva rânduri distanță într-un fișier de configurare și se comportă complet diferit. Una încheie cererea, cealaltă începe una secundă care aparține proxy-ului.
De ce are nevoie browserul și ce nu îi dă VPN-ul serverului
Cererea browserului însuși are nevoie de patru lucruri pe care poate nu le are: un răspuns DNS pentru numele privat, o rută către adresa respectivă, permisiunea de a ajunge la serviciul care ascultă și încrederea în certificatul pe care acel serviciu îl prezintă. Hostul proxy-ului le are pe toate patru prin tunelul său. Niciunul nu trece la un client care nu a fost niciodată în acel tunel, pentru că tunelul este o proprietate a hostului, nu a URL-ului.
Acest gol explică simptomul aproape-funcționează. Prima cerere reușește, bara de adrese se schimbă, iar apoi conexiunea se blochează sau eșuează în timp ce proxy-ul este sănătos tot timpul. În configurația proxy-ului nimic nu este stricat, iar ajustarea ei nu ajută un client care nu are cale către 10.40.0.20. Fie clientul primește o rută, fie proxy-ul trebuie să facă conexiunea în numele lui.
Când doar proxy-ul poate ajunge la aplicație
Elimină acțiunea de redirecționare și trimite acele cereri către un backend care indică adresa privată. Frontend-ul își păstrează numele public, în timp ce backend-ul decide unde merge cererea de fapt. Nimic din calea clientului nu se schimbă, pentru că proxy-ul încetează să descrie o destinație și începe să fie una. Pentru o aplicație care ascultă intenționat pe portul intern 8080, blocul de server este scurt:
backend private_app
mode http
option httpchk GET /health
server app1 10.40.0.20:8080 check
Citește acel bloc ca o afirmație despre tabelul de rutare al proxy-ului: 10.40.0.20 trebuie să fie accesibil din hostul proxy-ului însuși, ceea ce aici înseamnă prin interfața VPN și nu prin ruta implicită. Cuvântul-cheie check raportează doar ce face acea rută, iar calea de sănătate trebuie să existe în aplicație, altfel fiecare verificare marchează serverul ca indisponibil. O verificare reușită nu dovedește nimic despre client și nu înlocuiește politica ce decide cine poate folosi backend-ul.
Fragmentul nu este un frontend complet. Păstrează ascultătorul, autentificarea, selecția hostului și a căii permise, și regula care decide cine poate ajunge deloc la aplicație. Aliniază și protocolul backend-ului, pentru că un backend HTTPS necesită verificarea certificatului și o configurație de identitate a serverului, iar omiterea acelei verificări transformă o conexiune privată într-una neverificată.
Confirmă calea de întoarcere în ambele sensuri înainte de a declara victoria. Aplicația vede acum o conexiune de la proxy, deci jurnalele ei și orice metadate ale adresei clientului descriu proxy-ul, dacă nu aranjezi altfel. Decide cum sunt tratate identitatea și adresele transmise, și testează decizia în loc să o presupui.
Redirecționările aplicației înseși încă indică spre interior
Transmiterea primei cereri este doar jumătate din integrare. Aplicația poate emite redirecționări absolute, URL-uri de resurse codificate fix sau cookie-uri limitate la numele pe care îl cunoaște intern, iar fiecare dintre ele trimite browserul înapoi la o adresă pe care nu o poate atinge. Un antet Location care indică numele intern arată identic cu problema originală, motiv pentru care este diagnosticat greșit ca o defecțiune a proxy-ului.
Setează URL-ul extern documentat al aplicației și opțiunile ei de proxy, apoi aliniază antetul Host cu hostul virtual și cu așteptările certificatului. Un cookie limitat la numele intern nu va reveni prin numele public, iar bucla de autentificare care urmează se citește ca o eroare de autentificare. Schimbă mai întâi URL-ul extern, retestează dintr-un profil de browser curat și așteaptă-te ca setările pe jumătate migrate să producă o autentificare care reușește și o sesiune care moare imediat.
Ai încredere în metadatele transmise doar de la proxy-ul desemnat. Un antet pe care orice client îl poate seta nu trebuie să devină niciodată o valoare de autorizare, altfel proxy-ul devine o cale prin care apelantul revendică identitatea altcuiva. Acea regulă este independentă de întrebarea redirecționării și supraviețuiește oricărei reproiectări ulterioare.
Decide ce design ai de fapt
Dacă fiecare utilizator are deja acces VPN autorizat, o redirecționare către un URL privat poate fi un design deliberat. Confirmă explicit acea cerință în loc să o presupui, pentru că cele două designuri deservesc populații complet diferite. Unul dintre ele nu funcționează deloc dintr-o rețea de hotel, iar celălalt expune aplicația oricui poate încărca URL-ul public. Acea distincție aparține notei de design.
Rulează verificarea de pe o mașină din rețeaua utilizatorilor și urmărește fiecare salt pe care îl face browserul, nu doar primul răspuns. Urmărește bara de adrese și fiecare cerere ulterioară, pentru că un singur URL absolut dintr-o etichetă de resursă este suficient pentru a rupe un flux pe care primul salt l-a trecut. Autentificarea trebuie să funcționeze prin numele public, iar resursele care ar trebui refuzate trebuie să rămână refuzate.
Publicarea unui proxy invers este o decizie de acces separată, nu o extindere transparentă a VPN-ului proxy-ului către oricine poate încărca URL-ul său public. Cele două arată la fel pe o diagramă și diferă în cine este lăsat să intre. A decide acea întrebare după implementare este modul în care o aplicație internă devine publică din greșeală.
Notează care dintre cele două l-ai construit și de ce. Următoarea persoană care atinge configurația o va presupune pe cealaltă. O singură redirecționare adăugată pentru comoditate este suficientă pentru a recrea defecțiunea originală, iar simptomul va arăta exact ca cel pe care tocmai l-ai corectat.