Разрешить подключение TLS passthrough в контексте stream NGINX
NGINX может отклонить ретрансляцию TCP TLS, не завершая TLS. Поместите правило разрешения в stream, а не в HTTP location, и проверьте, какой адрес пира приходит.
Передача зашифрованных байтов без владения сертификатом — это и есть работа релея TLS passthrough, и такая схема удобна и одновременно немного запутанна. Поскольку ничего не расшифровывается, нет ничего в форме HTTP, по чему можно фильтровать, поэтому обычный блок location и его правила доступа здесь бесполезны. Решение, доступное NGINX, касается самого соединения: какой сетевой пир постучался и нужно ли его вообще пропускать.
Решение относится к stream, а не к HTTP
В контексте stream NGINX работает с TCP-сессиями. Он видит адрес пира соединения и может применить к нему упорядоченные правила allow и deny, прежде чем хоть один байт достигнет backend. Это полноценное решение о допуске, и оно не требует закрытого ключа, сертификата или разбора HTTP.
| Mechanism | What it decides | Where it lives |
|---|---|---|
| stream allow/deny | Whether this TCP peer is relayed | stream server block |
| ssl_preread map | Which backend a named connection goes to | stream, with the preread module |
| HTTP location rule | Whether an HTTP request is served | http server block |
Эти три строки никогда не заменяют друг друга. Копирование директивы доступа HTTP в любой файл, где упоминается порт 443, ничего не меняет, потому что сервер stream никогда не читает контекст http.
Примите соединение, прежде чем пересылать его
Предположим, есть существующая установка с доступными модулями stream и stream-access, клиенты VPN держат адреса источника в 10.90.0.0/24, а backend на 192.168.50.20:443 уже предоставляет нужную идентичность HTTPS.
stream {
server {
listen 443;
allow 10.90.0.0/24;
deny all;
proxy_pass 192.168.50.20:443;
}
}
Читайте это как фрагмент нового listener, а не как прямую замену работающей конфигурации. Сначала просмотрите существующие listener и include, потому что второй listener на уже занятом порту падает при reload, а не во время запроса.
Обратите внимание, чего нет: нет listen ... ssl, нет директивы сертификата. NGINX пересылает зашифрованный поток, а backend завершает TLS и предъявляет сертификат, который проверяет клиент. Клиент, не прошедший allowlist, получает отклонённую TCP-сессию, а не страницу HTTP 403, и любой инструмент, ожидающий код состояния, сообщит что-то бесполезное.
Что покрывает allowlist, а что нет
Если релей уже выбирает backend по имени сервера, ssl_preread и map могут продолжать эту работу при условии, что модуль доступен. Имя по-прежнему предоставляет клиент, и ничто в предъявлении имени не доказывает, что клиент присоединился к VPN.
Правило из примера покрывает весь listener stream. Это делает его непригодным для одной точки входа, обслуживающей смесь публичных сайтов и сайтов только для VPN, где понадобилось бы исключение для каждого имени; отдельные listener обычно понятнее, чем зависящая от имени политика, которую никто не может прочитать.
Стоит назвать ещё два пробела. Совпадение префикса ничего не говорит о том, через какой интерфейс пришёл пакет, а правило HTTP в другом месте не может ограничить этот listener. Оба — причины держать политику входа на уровне хоста в поле зрения, а не считать одну строку allow всей границей. Запишите границу как два контроля, которые должны совпадать, чтобы любое изменение в любом из них проходило проверку.
Чей адрес NGINX на самом деле видит?
Балансировщик нагрузки, другой proxy или source NAT могут поставить посредника перед NGINX, поэтому наблюдаемый пир — не внутренний адрес клиента VPN. Проверьте реальный путь, прежде чем выбирать префикс, потому что allowlist, написанный для неверного адреса, либо пропускает всех за транслятором, либо никого.
Протокол PROXY и обработка реального IP — законные ответы, и настраивать их следует только для совместимых, явно доверенных отправителей. Оба механизма верят тому, что говорит отправитель, и именно поэтому список отправителей должен быть коротким. Доверие к заголовку идентичности от любого интернет-клиента не делает сведения об источнике снова надёжными; оно превращает правильный отказ в поддельный допуск.
Проверьте оба исхода перед reload
Проверьте конфигурацию, выполните reload, затем проверьте дважды: новую разрешённую сессию, которая должна работать, и отклонённую, которая не должна. Сохраните обычную проверку сертификата backend в разрешённом тесте, потому что релей, который подключается, но не может проверить backend, решил одну проблему и создал другую.
nginx -t
ss -tnp | grep ':443'
Проверьте также маршрут до backend и ответ отдельно. Прохождение allowlist доказывает лишь то, что NGINX принял сессию; оно не может заставить недоступный backend ответить и не может исправить обратный путь, которого не было до начала работы.