基于主机名的Nginx TCP转发

随着Nginx社区版本的TCP负载平衡的发布,我想混合使用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的相关问题:反向代理可以通过SSL使用SNI传递吗?

3个解决方案
60 votes

现在可以通过在Nginx 1.11.5中添加的ngx_stream_ssl_preread模块和在1.11.2中添加的ngx_stream_map模块来实现。

这使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;
    }
}
Lochnair answered 2020-08-08T19:55:07Z
21 votes

假设条件

如果我理解正确,您实际上希望nginx侦听单个IP地址和TCP端口组合(例如stunnel),然后根据传入TCP流流量的特性,将其路由到3个不同IP之一 地址。

您没有明确提到如何期望它区分所涉及的3个不同域,但是我的假设是,您假设它都只是TLS,并且必须要使用某种TLS SNI(服务器名称指示)机制来实现。 基于域的区分。

我相信[http://nginx.org/docs/]上提供的与流相关的文档对于所涉及的模块是相当权威和详尽的(我将其全部列出在这里,因为显然没有中央模块可用于 交叉引用,例如,还没有从“流核心”模块到子模块的引用(和stunnel只是重定向回upstream),这确实很令人困惑,因为类似[http://nginx.org/r/ 上游]仅记录到适用于hash,而没有提及适用于stream,即使指令的末尾大致相同):

  • [HTTP://Nginx.org/docs/stream/农广校_stream_core_module.HTML]
  • [HTTP://Nginx.org/docs/stream/农广校_stream_access_module.HTML]
  • [HTTP://Nginx.org/docs/stream/农广校_stream_limit_con你_module.HTML]
  • [HTTP://Nginx.org/docs/stream/农广校_stream_proxy_module.HTML]
  • [HTTP://Nginx.org/docs/stream/农广校_stream_收视率_module.HTML]
  • [HTTP://Nginx.org/docs/stream/农广校_stream_upstream_module.HTML]

回答

请注意,每个模块中的每个nginx指令都有一定数量的适用stunnel

因此,不幸的是,这里根本没有任何指令可以监听到SNI!

相反,实际上在stunnel中有记录,引用为“ stunnel”,您可能会注意到,它也与stunnel指令在更常见的upstream中的工作方式相反,并且是对该事实的明确表述。 当前没有为hash中的upstream实现任何类型的SNI支持。


讨论区

作为讨论点和解决建议,关于OpenVPN流量只是具有可监听SNI的TLS的假设也不一定正确(但是我对OpenSSL或SNI不太熟悉):

  • 考虑一下,即使现在SNI可以被动侦听,这也明显违背了TLS保证连接安全的承诺,因此,在TLS的未来版本中可能会发生变化。

  • 为了便于讨论,如果OpenVPN仅使用TLS连接,并且不使用TLS通过用户证书对用户进行身份验证(这将使MitM流更加困难,但始终携带身份验证数据) ,那么从理论上讲,如果nginx在upstream内的stunnel周围确实有SNI支持,那么您可能可以通过nginx积极地进行MitM(因为upstream已经支持stunnel)。

最重要的是,我认为OpenVPN可能最好在其自己的基于UDP的协议上运行,在这种情况下,您可以为一个基于TCP的https和另一个基于UDP的OpenVPN实例使用相同的IP地址和端口号 没有冲突。

最后,您可能会问,无论如何,流模块对您有用吗? 我相信它的目标受众将是(0)基于多个客户端IP地址的3002764139094868868994和多个3002764139094868868993服务器进行负载平衡,和/或(1)一种更加简单易用的协议- 不可替代的stunnel替代品。

cnst answered 2020-08-08T19:54:42Z
5 votes

如@Lochnair所述,您可以使用ngx_stream_map模块和$ server_addr变量来解决此问题。 这是我的例子。

我的主机IP是server_name,我使用keepalived绑定2虚拟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;
    }
}

因此,我可以通过不同的VIP使用相同的端口3306访问不同的MySQL服务。 就像通过不同的server_name通过相同的端口访问不同的HTTP服务一样。

192.168.168.238 -> 10.1.5.3
192.168.168.239 -> 10.1.5.4
aloisio answered 2020-08-08T19:55:36Z
translate from https://stackoverflow.com:/questions/34741571/nginx-tcp-forwarding-based-on-hostname