Nginx отпред, частен backend зад VPN
Браузърът говори с Nginx, а Nginx отваря втора връзка в тунела. Насочи и двете, провери и двата сертификата и запази публичните пренасочвания публични.
Проследи една заявка от телефон във влак до backend, който съществува само в частна мрежа. Телефонът резолвва публично име, свързва се с публичен адрес на порт 443 и завършва нормално TLS ръкостискане. Nginx приема тази връзка и след това отваря втора, напълно отделна, към адрес, до който достига само през VPN интерфейс. Нищо по телефона не се променя и телефонът никога не научава, че е участвал тунел.
Проследяване на една заявка през две връзки
Входящата част е публична HTTP транзакция. Тя има публично име, публичен сертификат и път за отговор, който трябва да работи за всеки адрес, който посетителят има в момента. Всяка промяна, която наруши този път, чупи всички посетители наведнъж. Затова публичната част е тази, с която най-малко искаш да експериментираш.
Изходящата част е частна. Nginx става клиент, резолвва вътрешно име или използва фиксиран адрес и достига услуга, която би отказала връзка от интернет.
Public client -> frontend public address:443 (TLS, public name)
Frontend -> VPN private backend 10.40.0.20:8443 (TLS, private name)
Дръж ги отделно в главата си и конфигурацията става очевидна. Смесиш ли ги, в крайна сметка ще се опитваш да накараш публичен клиент да участва в тунел, а оттам идва по-голямата част от объркването в този дизайн. Частната част може да е крехка. Публичната не.
Преди да построиш каквото и да е от това, потвърди, че публикуването на приложението е умишлено. Работещ прокси е също работещо излагане, а моделът за удостоверяване трябва да е решение, а не страничен ефект.
Името на шлюза трябва да оцелее след тунела
В резолвването на имена се крие фина капан. Ако частният DNS презапише същото име, което VPN профилът използва за собствения си шлюз, повторното свързване може в крайна сметка да се опита да достигне този шлюз през тунела, който сам трябва да установи. Клиентът се нуждае това име да бъде резолвнато, преди тунелът да съществува. Щом се резолвне вътре в тунела, връзката никога не може да бъде изградена.
Използвай отделни одобрени имена за шлюза и backend и ограничи политиката на резолвера така, че всяко име да се резолвва до адреса, който неговият потребител има нужда. Промяната на Host заглавката на приложение не помага тук, защото търсенето го прави VPN клиентът.
След това помисли за обхвата. Frontend има нужда от маршрут до адреса на backend и също трябва да продължи да отговаря на публични клиенти. Пълен тунел на този хост може да изпълни първото изискване и да счупи второто.
Където политиката позволява, тунел до конкретна дестинация е по-чистият дизайн. Където политиката изисква пълен тунел, frontend принадлежи в отделен мрежов контекст, вместо да бъде кърпен с ad hoc маршрути за известни посетители.
Да се напише блокът на сървъра така, че двете части да проверяват
Да предположим, че backend представя сертификат за backend.corp.example и отговаря на частен адрес. Свързването към адрес, докато се проверява име, е точно това, за което служат SNI и именните опции на прокси модула.
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;
}
}
Всеки път и адрес по-горе е заместител. Важни са частите, които държат проверката включена: име за проверка, доверителен пакет, с който да се провери, и дълбочина, която съответства на одобрената верига.
proxy_set_header Host app.example.com е умишлено. Backend трябва да бъде конфигуриран да приема този публичен произход и да се доверява на препратени заглавки само от този frontend. Презаписването на X-Forwarded-For с адреса на клиента е правилно за един прокси и грешно за верига, така че внедряване с няколко проксита се нуждае от собствена подредба на доверени адреси.
Да накараш публичния произход да се държи като такъв
Приложението трябва да издава абсолютни URL адреси, пренасочвания и бисквитки за публичния произход. Ако пренасочи посетител към backend.corp.example, този посетител бива изпратен на име, което не може да резолвне.
Поправи това чрез поддържаната настройка за външен URL на приложението и прегледана политика за пренасочване, а не чрез пренаписване на имена на хостове в телата на отговорите. Замяната на низове в произволно съдържание чупи подписани товари и създава грешки, които трудно се проследяват.
Бисквитките заслужават същата грижа. Бисквитка, ограничена до частното име, никога няма да бъде изпратена от браузър, който познава само публичното.
Потвърди също, че Nginx наистина може да притежава публичния сокет. Съществуваща уеб услуга на 443 трябва да се премести или frontend трябва да живее другаде, защото два процеса не могат да завържат един и същ адрес и порт.
Да се провери всяка част там, където реално работи
Валидирай конфигурацията с инсталирания парсер преди всяко презареждане, за да не свали печатна грешка слушателя, който обслужва в момента.
След това тествай частта на backend от самия хост на frontend, в същия мрежов контекст, който използва Nginx. Ping през тунела доказва достъпност и нищо за TLS, оторизация или приложението.
Накрая направи нова публична заявка към истинското име на хоста с нормална проверка на сертификата и наблюдавай пътя на отговора, а не само тялото.
Запази двете наблюдения заедно с внедрената конфигурация и запиши какво се случва, когато тунелът не е наличен. Frontend трябва да върне контролирана upstream грешка, а не тихо да премине към друг backend, който никой не е документирал. 502 с ясен ред в лога е добър резултат. Кеширана страница от нежелан произход не е.