Передача зашифрованных байтов без владения сертификатом — это и есть работа релея TLS passthrough, и такая схема удобна и одновременно немного запутанна. Поскольку ничего не расшифровывается, нет ничего в форме HTTP, по чему можно фильтровать, поэтому обычный блок location и его правила доступа здесь бесполезны. Решение, доступное NGINX, касается самого соединения: какой сетевой пир постучался и нужно ли его вообще пропускать.

Решение относится к stream, а не к HTTP

В контексте stream NGINX работает с TCP-сессиями. Он видит адрес пира соединения и может применить к нему упорядоченные правила allow и deny, прежде чем хоть один байт достигнет backend. Это полноценное решение о допуске, и оно не требует закрытого ключа, сертификата или разбора HTTP.

MechanismWhat it decidesWhere it lives
stream allow/denyWhether this TCP peer is relayedstream server block
ssl_preread mapWhich backend a named connection goes tostream, with the preread module
HTTP location ruleWhether an HTTP request is servedhttp 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 ответить и не может исправить обратный путь, которого не было до начала работы.

Похожие статьи