Urmărește o cerere de la un telefon aflat într-un tren până la un backend care există doar într-o rețea privată. Telefonul rezolvă un nume public, se conectează la o adresă publică pe portul 443 și finalizează un handshake TLS normal. Nginx acceptă acea conexiune și apoi deschide una a doua, complet separată, către o adresă pe care o atinge doar printr-o interfață VPN. Nimic la telefon nu se schimbă, iar telefonul nu află niciodată că a fost implicat un tunel.

Urmărirea unei cereri prin două conexiuni

Segmentul de intrare este o tranzacție HTTP publică. Are un nume public, un certificat public și o cale de răspuns care trebuie să funcționeze pentru orice adresă are vizitatorul în acel moment. Orice modificare care tulbură acea cale sparge toți vizitatorii deodată. De aceea segmentul public este cel pe care vrei cel mai puțin să experimentezi.

Segmentul de ieșire este privat. Nginx devine client, rezolvă un nume intern sau folosește o adresă fixă și ajunge la un serviciu care ar refuza o conexiune din internet.

Public client  -> frontend public address:443   (TLS, public name)
Frontend       -> VPN private backend 10.40.0.20:8443  (TLS, private name)

Ține-le separate în minte și configurația devine evidentă. Unește-le și ajungi să încerci să faci un client public să participe la un tunel, iar de acolo vine cea mai mare parte a confuziei din acest design. Segmentul privat are voie să fie fragil. Cel public nu.

Înainte de a construi orice din asta, confirmă că publicarea aplicației este intenționată. Un proxy funcțional este și o expunere funcțională, iar modelul de autentificare trebuie să fie o decizie, nu un efect secundar.

Numele gateway-ului trebuie să supraviețuiască tunelului

Există o capcană subtilă în rezolvarea numelor. Dacă DNS-ul privat suprascrie același nume pe care profilul VPN îl folosește pentru propriul gateway, o reconectare poate ajunge să încerce să atingă acel gateway prin tunelul pe care ar trebui să îl stabilească. Clientul are nevoie ca acel nume să fie rezolvat înainte ca tunelul să existe. Odată ce se rezolvă în interiorul tunelului, conexiunea nu mai poate fi construită deloc.

Folosește nume aprobate distincte pentru gateway și backend și limitează politica resolver-ului astfel încât fiecare nume să se rezolve la adresa de care are nevoie consumatorul său. Schimbarea antetului Host al unei aplicații nu ajută aici, pentru că clientul VPN este cel care face căutarea.

Apoi gândește-te la scop. Frontend-ul are nevoie de o rută către adresa backend-ului și trebuie să continue să răspundă clienților publici. Un tunel complet pe acel host poate satisface prima cerință și poate rupe a doua.

Unde politica permite, un tunel către o destinație specifică este designul mai curat. Unde politica impune un tunel complet, frontend-ul aparține unui context de rețea separat, în loc să fie peticit cu rute ad hoc pentru vizitatori cunoscuți.

Scrie blocul de server astfel încât ambele segmente să verifice

Presupune că backend-ul prezintă un certificat pentru backend.corp.example și răspunde pe o adresă privată. Conectarea la o adresă în timp ce verifici un nume este exact scopul opțiunilor SNI și de nume ale modulului proxy.

upstream private_app {
    server 10.40.0.20:8443;
}

server {
    listen 443 ssl;
    server_name app.example.com;

    ssl_certificate     /etc/nginx/tls/app-fullchain.pem;
    ssl_certificate_key /etc/nginx/tls/app-key.pem;

    location / {
        proxy_pass https://private_app;
        proxy_ssl_server_name on;
        proxy_ssl_name backend.corp.example;
        proxy_ssl_trusted_certificate /etc/nginx/trust/backend-ca.pem;
        proxy_ssl_verify on;
        proxy_ssl_verify_depth 3;

        proxy_set_header Host app.example.com;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

Fiecare cale și adresă de mai sus este un substituent. Părțile importante sunt cele care țin verificarea pornită: un nume de verificat, un pachet de încredere față de care să îl verifici și o adâncime care corespunde lanțului aprobat.

proxy_set_header Host app.example.com este deliberat. Backend-ul ar trebui configurat să accepte acea origine publică și ar trebui să aibă încredere în antetele transmise doar de la acest frontend. Suprascrierea X-Forwarded-For cu adresa clientului este corectă pentru un singur proxy și greșită pentru un lanț, așa că un deployment cu mai multe proxy-uri are nevoie de propria aranjare a adreselor de încredere.

Fă originea publică să se comporte ca atare

Aplicația ar trebui să emită URL-uri absolute, redirecționări și cookie-uri pentru originea publică. Dacă redirecționează un vizitator către backend.corp.example, acel vizitator este trimis către un nume pe care nu îl poate rezolva.

Remediază asta prin setarea de URL extern suportată de aplicație și o politică de redirecționare revizuită, nu prin rescrierea numelor de host în corpurile răspunsurilor. Înlocuirea de șiruri în conținut arbitrar rupe sarcinile semnate și creează erori greu de urmărit.

Cookie-urile merită aceeași grijă. Un cookie limitat la numele privat nu va fi trimis niciodată de un browser care cunoaște doar cel public.

Confirmă și că Nginx poate deține într-adevăr socket-ul public. Un serviciu web existent pe 443 trebuie să se mute sau frontend-ul trebuie să locuiască în altă parte, pentru că două procese nu pot lega aceeași adresă și același port.

Verifică fiecare segment acolo unde rulează de fapt

Validează configurația cu parserul instalat înainte de orice reîncărcare, ca o greșeală de tastare să nu dărâme listener-ul care servește chiar acum.

Apoi testează segmentul backend de pe hostul frontend-ului însuși, în același context de rețea pe care îl folosește Nginx. Un ping prin tunel demonstrează accesibilitatea și nimic despre TLS, autorizare sau aplicație.

În final fă o cerere publică nouă către numele de host real cu verificare normală a certificatului și observă calea de răspuns, nu doar corpul.

Păstrează ambele observații împreună cu configurația implementată și notează ce se întâmplă când tunelul nu este disponibil. Frontend-ul ar trebui să returneze o eroare de upstream controlată, nu să cadă în tăcere pe alt backend pe care nimeni nu l-a documentat. Un 502 cu o linie clară de log este un rezultat bun. O pagină din cache de la o origine neintenționată nu este.

Articole similare