Nginx попереду, приватний бекенд за VPN
Браузер говорить з Nginx, і Nginx відкриває друге з’єднання в тунель. Спрямуйте обидва, перевірте обидва сертифікати й залишайте публічні переспрямування публічними.
Простежте один запит від телефона в потязі до бекенду, який існує лише в приватній мережі. Телефон розв’язує публічне ім’я, підключається до публічної адреси на порту 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 використовує для власного шлюзу, повторне підключення може зрештою намагатися досягти цього шлюзу крізь тунель, який він сам має встановити. Клієнтові потрібно, щоб це ім’я розв’язалося до того, як тунель існує. Щойно воно розв’язується всередині тунелю, з’єднання вже неможливо побудувати.
Використовуйте окремі затверджені імена для шлюзу й бекенду та обмежте політику резолвера так, щоб кожне ім’я розв’язувалося на адресу, потрібну його споживачеві. Зміна заголовка Host застосунку тут не допоможе, бо пошук виконує клієнт VPN.
Потім подумайте про масштаб. Frontend потребує маршруту до адреси бекенду, і він також має продовжувати відповідати публічним клієнтам. Повний тунель на цьому хості може задовольнити першу вимогу й зламати другу.
Там, де політика це дозволяє, тунель до конкретного призначення — чистіший дизайн. Там, де політика вимагає повного тунелю, frontend належить до окремого мережевого контексту, а не латається ad hoc маршрутами для відомих відвідувачів.
Написати блок сервера так, щоб обидві частини перевіряли
Припустімо, що бекенд подає сертифікат для 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 — це навмисно. Бекенд слід налаштувати приймати це публічне походження, і він має довіряти пересланим заголовкам лише від цього frontend. Перезапис X-Forwarded-For адресою клієнта правильний для одного проксі й хибний для ланцюжка, тож розгортання з кількома проксі потребує власного впорядкування довірених адрес.
Змусити публічне походження поводитися як таке
Застосунок має видавати абсолютні URL, переспрямування й cookie для публічного походження. Якщо він переспрямовує відвідувача на backend.corp.example, цей відвідувач потрапляє на ім’я, яке не може розв’язати.
Виправте це через підтримуване налаштування зовнішнього URL застосунку та переглянуту політику переспрямування, а не через переписування імен хостів у тілах відповідей. Заміна рядків у довільному вмісті ламає підписані корисні навантаження й створює помилки, які важко відстежити.
Cookie заслуговують такої самої уваги. Cookie, обмежена приватним ім’ям, ніколи не буде надіслана браузером, який знає лише публічне.
Також підтвердьте, що Nginx справді може володіти публічним сокетом. Наявна вебслужба на 443 має переміститися, або frontend має жити в іншому місці, бо два процеси не можуть зайняти ту саму адресу й порт.
Перевірити кожну частину там, де вона насправді працює
Провалідуйте конфігурацію встановленим парсером перед кожним перезавантаженням, щоб друкарська помилка не поклала слухача, який зараз обслуговує.
Потім перевірте частину бекенду з самого хоста frontend, у тому самому мережевому контексті, який використовує Nginx. Ping крізь тунель доводить доступність і нічого про TLS, авторизацію чи застосунок.
Нарешті зробіть свіжий публічний запит до справжнього імені хоста зі звичайною перевіркою сертифіката й спостерігайте за зворотним шляхом, а не лише за тілом відповіді.
Збережіть обидва спостереження разом із розгорнутою конфігурацією й запишіть, що відбувається, коли тунель недоступний. Frontend має повертати контрольовану помилку upstream, а не тихо перемикатися на інший бекенд, який ніхто не задокументував. 502 із чітким рядком у лозі — добрий результат. Закешована сторінка з небажаного походження — ні.