基于主机名的Nginx TCP转发

随着Nginx社区版本的TCP负载平衡的发布,我想混合OpenVPN和SSL传递数据。 Nginx知道如何路由stream量的唯一方法是通过他们的域名。

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指南和模块文档 ,但似乎没有很好的参考。 如果有人能指出我正确的方向,我将不胜感激。

谢谢!

假设

如果我正确地理解了你的话,你就需要nginx监听单个IP地址和TCP端口的组合(例如listen 10.0.0.1:443 ),然后根据传入的TCP流量的特征,将它路由到一个3个不同的IP地址。

你没有明确地提到你期望它如何区分这三个不同的领域,但我的假设是,你认为它们都只是TLS,并且必须要采用某种TLS SNI(服务器名称指示)机制基于领域的分化。

我相信http://nginx.org/docs/上提供的与流相关的文档对于所涉及的模块来说是相当权威和详尽的(我在这里列出了所有这些文档,因为显然,还引用了这个,例如,没有从“流核心”模块到子模块的引用(和docs/stream/只是重新引导docs/ ),这确实是相当混乱的,因为像http://nginx.org/r / upstream仅记录为适用于http ,没有提及stream的适用性,即使指令在最后大致相同):


回答

请注意,来自每个模块的每个nginx指令的适用Context的数量是有限的。

因此,不幸的是,这里根本就没有指示要窥探SNI!

相反, 它实际上是在stream_core中记录的,引用“ Different servers must listen on different address:port pairs. ”,正如你可能注意到的,这也与listen指令在更常见的http_core ,并且对目前没有任何类型的SNI支持在内部进行listen的事实是相当明确的参考。


讨论

作为一个讨论点和解决方案的建议,假设OpenVPN流量只是Snoopable SNI的TLS也不一定是正确的(但我不太熟悉OpenSSL或SNI):

  • 考虑到即使SNI今天被动地被窥探,这显然也违背了TLS保证连接安全的承诺,并且在未来的TLS版本中可能会发生变化。

  • 为了讨论起见,如果OpenVPN只是使用TLS连接,并且如果它使用TLS对具有用户证书的用户进行认证(这将使得MitM流更难,但仍然携带认证数据) ,那么,理论上, 如果nginxstreamlisten周围确实有SNI支持 ,那么你可能已经能够用nginx主动MitM了(因为stream_proxy已经支持proxy_ssl了 )。

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

最后,你可能会问,流模块对于反正有什么用处呢? 我相信它的目标受众将是,(0),例如,和/或(1),基于客户端的IP地址的hash来平衡HTTP/2与多个upstream服务器的平衡,协议不可替代的stunnel

现在可以添加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; } } 

AS @Lochnair提到,你可以使用ngx_stream_map模块和变量$ server_addr来解决这个问题。 这是我的例子。

我的主机IP是192.168.168.22 ,我使用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