Подтвердить что ты не робот

Перенаправление Nginx TCP на основе имени хоста

С выпуском балансировки нагрузки по протоколу TCP для версии сообщества Nginx я бы хотел смешать сквозные данные OpenVPN и SSL. Единственный способ для Nginx узнать, как маршрутизировать трафик, - через его доменное имя.

 vpn1.app.com ─┬─► nginx at 10.0.0.1 ─┬─► vpn1  at 10.0.0.3
 vpn2.app.com ─┤                      ├─► vpn2  at 10.0.0.4
https.app.com ─┘                      └─► https at 10.0.0.5

Я ознакомился с руководствами по TCP и документацией к модулю, но на них нет ссылок. Если кто-нибудь может указать мне правильное направление, я был бы благодарен.

Схожий вопрос по ServerFault: может ли обратный прокси-сервер использовать SNI с прохождением SSL?

4b9b3361

Ответ 1

Предположения

Если вы правильно поняли, вы действительно хотите, чтобы nginx прослушивал один IP-адрес и комбинацию TCP-портов (например, listen 10.0.0.1:443), а затем, в зависимости от характеристики входящего потока трафика TCP, направьте его на один из 3 разных IP-адресов.

Вы явно не указываете, как вы ожидаете, что он будет проводить различие между тремя разными доменами, но мое предположение состоит в том, что вы принимаете все как раз TLS и должны использовать какой-то SNI TLS (имя сервера) ) механизм доменной дифференциации.

Я полагаю, что связанная с потоком документация, представленная в http://nginx.org/docs/, является достаточно авторитетной и исчерпывающей для модулей (я перечисляю все ее здесь, поскольку, по-видимому, нет центрального места для перекрестных ссылок на это, например, нет ссылок от модуля "ядро потока" на подмодули еще (и docs/stream/ просто перенаправляет назад docs/), что действительно довольно запутанно, так как материал например http://nginx.org/r/upstream, будет документирован для применения к http без упоминания о применимости к stream, даже если директивы примерно совпадают в конце)


Ответ

Обратите внимание, что каждая директива nginx из каждого модуля имеет ограниченное число применимых Context.

Как таковой, к сожалению, нет просто директивы для snoop в SNI здесь!

Наоборот, он фактически задокументирован в stream_core, который, цитируя, "Different servers must listen on different address:port pairs.", который, как вы можете заметить, также противоречит как директива listen работает в более общем http_core, и является довольно однозначной ссылкой на тот факт, что в настоящее время не поддерживается никакая поддержка SNI для listen в пределах stream.


Обсуждение

В качестве точки обсуждения и предложения по разрешению предположение, что трафик OpenVPN является просто TLS со snoopable SNI, также не обязательно корректен (но я не слишком хорошо знаком с OpenSSL или SNI):

  • Учтите, что даже если сегодня SNI пассивно snoopable, что явно противоречит обещанию TLS для обеспечения безопасности соединения и, как таковое, может измениться в будущей версии TLS.

  • Для обсуждения, если OpenVPN просто использует TLS-соединение, и если он НЕ использует TLS для аутентификации пользователей с пользовательскими сертификатами (что затрудняет MitM поток, но все же переносит все данные аутентификации), то теоретически, если nginx имел поддержку SNI вокруг listen внутри stream, то вы, возможно, могли бы активно MitM с помощью nginx (поскольку proxy_ssl уже поддерживается в stream_proxy).

Самое главное, я считаю, что OpenVPN лучше всего запускать на своем собственном UDP-протоколе, и в этом случае вы можете использовать один и тот же IP-адрес и номер порта для одного экземпляра https на основе TCP и еще один из UDP на основе OpenVPN без конфликта.

В конце концов, вы можете спросить, что будет использовать модуль потока в любом случае? Я полагаю, что его целевой аудиторией будет (0), балансировка нагрузки HTTP/2 с несколькими серверами upstream на основе hash IP-адреса клиента, например, и/или (1), более простая и протокольно-агностическая замена для stunnel.

Ответ 2

Теперь это возможно с добавлением модуля ngx_stream_ssl_preread, добавленного в Nginx 1.11.5, и модуль ngx_stream_map добавлен в 1.11.2.

Это позволяет Nginx читать клиент TLS Hello и решать на основе расширения SNI, которое использует бэкэнд.

stream {

    map $ssl_preread_server_name $name {
        vpn1.app.com vpn1_backend;
        vpn2.app.com vpn2_backend;
        https.app.com https_backend;
        default https_default_backend;
    }

    upstream vpn1_backend {
        server 10.0.0.3:443;
    }

    upstream vpn2_backend {
        server 10.0.0.4:443;
    }

    upstream https_backend {
        server 10.0.0.5:443;
    }

    upstream https_default_backend {
        server 127.0.0.1:443;
    }

    server {
        listen 10.0.0.1:443;
        proxy_pass $name;
        ssl_preread on;
    }
}

Ответ 3

AS @Lochnair отметил, что вы можете использовать модуль ngx_stream_map и переменную $server_addr, чтобы решить эту проблему. Вот мой пример.

Мой IP-адрес хоста 192.168.168.22, и я использую keepalived bound 2 virtual IP для eth0.

$sudo ip a
...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 5c:f3:fc:b9:f0:84 brd ff:ff:ff:ff:ff:ff
inet 192.168.168.22/24 brd 192.168.168.255 scope global eth0
   valid_lft forever preferred_lft forever
inet 192.168.168.238/32 scope global eth0
   valid_lft forever preferred_lft forever
inet 192.168.168.239/32 scope global eth0
   valid_lft forever preferred_lft forever

$nginx -v
nginx version: nginx/1.13.2

$cat /etc/nginx/nginx.conf
...
stream {
    upstream pod53{
        server 10.1.5.3:3306;
    }
    upstream pod54{
        server 10.1.5.4:3306;
    }

    map $server_addr $x {
        192.168.168.238 pod53;
        192.168.168.239 pod54;
    }
    server {
        listen 3306;
        proxy_pass $x;
    }
}

Таким образом, я могу посетить другой сервис MySQL с тем же портом 3306 через разные VIP-персоны. Так же, как посещение различных HTTP-сервисов с одним и тем же портом через diffrent server_name.

192.168.168.238 -> 10.1.5.3
192.168.168.239 -> 10.1.5.4