Чому Port-Share змушує всіх відвідувачів сайту виглядати як localhost
Бекенд бачить крок переадресації, а не браузер. Перевірте, де губиться адреса джерела, і потім вирішіть, який компонент може за неї ручатися.
Кожен рядок у журналі доступу показує ту саму адресу loopback, хоча сторінка завантажується і сертифікат перевіряється правильно. Нічого не зламано в сенсі недоступності, і саме тому це триває місяцями. Зламалося зіставлення: веб-бекенд записує адресу кроку, який переслав з'єднання, і кожне наступне рішення, що довіряло журналу, успадкувало ту саму неправильну відповідь.
Перевірки нижче йдуть від кабелю всередину. Зупиніться на першій, яка пояснює побачене значення, бо кожен наступний крок припускає, що попередні вже правильні. Жодна з них не вимагає вгадувати конфігурацію фронтенду.
Стежте за двома з'єднаннями, а не за рядком журналу
Port-Share мультиплексує два протоколи на одному слухачі TCP. OpenVPN перевіряє перші байти вхідного з'єднання, залишає його, коли воно схоже на його власний протокол, і передає все інше другому призначенню. Це призначення зазвичай вебсервер на тому самому хості, тому прийнятий сокет вебсервера має процес OpenVPN як партнера.
ss -ltnp 'sport = :443'
ss -tnp 'dport = :8080'
Перша команда показує, кому належить публічний слухач. Друга показує з'єднання, що приходять на порт бекенду, і адреса їхнього партнера — це те, що надрукує журнал доступу. Якщо цей партнер — loopback, журнал точний, а інформація загубилася шаром раніше. Перейменування поля в журналі або ввімкнення модуля IP клієнта не поверне адресу, яку з'єднання ніколи не несло.
Це перевірка, яка вирішує все інше. Якщо партнер бекенду — адреса браузера, то спільне використання порту не ваша проблема, а формату журналу.
Пересилання транспорту — це не вставлення заголовка
Пересилач переміщує байти. Він не розбирає HTTP, тому не може вставити заголовок у запит, який ніколи не читає як запит, і не може нічого додати до потоку TLS, який пропускає недоторканим. Модуль, що очікує заголовок проксі, і далі чекатиме на той, який не виробляє жоден компонент.
Початкову адресу можуть нести два механізми, і обидва вимагають згоди на обох кінцях. Протокол PROXY додає невеликий заголовок на початок з'єднання, і бекенд має бути налаштований його очікувати; бекенд, який його не очікує, сприйме ці байти як початок запиту і зазнає невдачі в заплутаний спосіб. Довірені заголовки проксі на кшталт X-Forwarded-For працюють, коли проксі, що розуміє HTTP, їх встановлює, а бекенду повідомляють, які партнери можуть їх підтверджувати.
Жодне не є автоматичним, і жодне не є безпечним за замовчуванням. Бекенд, який вірить кожному вхідному пересланому заголовку, дозволяє клієнту вибрати власну адресу, що перетворює неточність у журналі на обхід контролю доступу. Запустіть демонстрацію один раз на тестовому хості, перш ніж довіряти якомусь заголовку на виробничому шляху.
curl -sS -H 'X-Forwarded-For: 127.0.0.1' https://site.example.com/health
grep -F '127.0.0.1' /var/log/nginx/access.log | tail -n 3
Необов'язковий каталог зіставлення джерел
Спільне використання порту має необов'язковий аргумент каталогу, який записує зіставлення між початковою адресою джерела та пересильним з'єднанням на платформах, де цю функцію реалізовано. Очікується, що вебсторона звертатиметься до цього зіставлення; це не заголовок, і воно не вставляється в запит.
Цей задум варто знати, бо він пояснює, чому одні розгортання повідомляють справжні адреси, а інші ні, за зовні однакової конфігурації фронтенду. Різниця на боці отримувача, і зазвичай вона живе в модулі або патчі, який більшість пакованих вебсерверів не вмикає за замовчуванням. Прочитайте в документації свого вебсервера, який саме хук він пропонує, і підтвердьте, що модуль завантажено, а не припускайте це.
Якщо ви підете цим шляхом, розглядайте це як інтеграцію з двох половин: зіставлення, яке пише пересилач, і пошук, який виконує бекенд. Перевірте обидва і підтвердьте, що каталог переживає перезапуск і не зростає безмежно на зайнятому слухачі.
Вирішіть, який компонент ручається за адресу
Перш ніж змінювати конфігурацію, назвіть компонент, якому дозволено стверджувати адресу клієнта, і компоненти, яким дозволено йому вірити. Одна записана відповідь запобігає звичайному дрейфу, коли три різні шари довіряють кожен своєму джерелу.
Три схеми покривають більшість потреб. Тримайте публічний слухач і вебслухач окремо, щоб вебсервер бачив справжніх партнерів і проблеми зіставлення не було. Поставте попереду зворотний проксі, що розуміє HTTP, завершіть або розберіть запит там і дозвольте йому встановити пересланий заголовок для бекенду, який довіряє лише цьому проксі. Або залиште спільне використання порту і прийміть, що журнали показують крок, а правила доступу пишуться проти ідентичності застосунку, а не адреси.
Що не працює — це трактування пересланої адреси loopback як доказу того, що запит виник на цій машині. Цей висновок прямо зворотний: адреса присутня саме тому, що локальний компонент встановив з'єднання. Правило, написане так, впускає кожного, хто може досягти слухача.
Перевіряйте правило, а не сторінку
Сторінка, що завантажується, доводить, що шлях пересилання працює, і нічого не каже про зіставлення. Перевірте споживача адреси: правило доступу, обмеження швидкості, запис аудиту, географічний пошук.
curl -sS -o /dev/null -w '%{http_code}\n' https://site.example.com/
tail -n 5 /var/log/nginx/access.log
grep -c '' /var/log/nginx/access.log
Порівняйте дозволений зовнішній запит із записами і фронтенду, і бекенду та підтвердьте, що правило, яке має відмовляти, досі відмовляє, а те, яке має допускати, досі допускає. Якщо рядок журналу правильний, але правило поводиться так, ніби всі локальні, правило читає інше поле, не те, яке ви виправили.
Завершіть, записавши ланцюг зіставлення в нотатки про розгортання: який слухач, який пересилач, який заголовок або зіставлення і який бекенд налаштований йому довіряти. Наступній людині, яка прочитає рядок журналу, знадобляться всі чотири, і саме відсутність цієї нотатки змушує той самий тікет відкриватися знову через рік. Тримайте її поруч із конфігурацією, а не в гілці чату.