Nginx基于主机名的TCP转发

时间:2016-01-12 10:46:38

标签: nginx tcp stream load-balancing virtualhost

随着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 guidesmodule documentation,但它似乎没有被很好地引用。如果有人能指出我正确的方向,我将不胜感激。

ServerFault上的相关问题:Can a Reverse Proxy use SNI with SSL pass through?

3 个答案:

答案 0 :(得分:49)

现在可以添加Nginx 1.11.5中添加的ngx_stream_ssl_preread module和1.11.2中添加的ngx_stream_map module

这允许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;
    }
}

答案 1 :(得分:18)

假设

如果我理解正确,你实际上希望nginx能够监听单个IP地址和TCP端口组合(例如,listen 10.0.0.1:443),然后根据传入TCP流流量的特征来路由它到3个不同的IP地址之一。

您没有明确提及您希望如何区分3个不同的域名,但我的假设是您认为它只是TLS,并且必须要使用某种类型的基于域的区分的TLS SNI(服务器名称指示)机制。

我认为http://nginx.org/docs/提供的与流相关的文档对于所涉及的模块是非常权威和详尽的(我在这里列出了所有这些,因为显然没有中心用于交叉引用的地方,例如,还没有来自"流核心"模块的引用到子模块(并且docs/stream/只是重定向回docs/),这确实令人困惑,因为像http://nginx.org/r/upstream这样的东西只记录为适用于http,而没有提及stream的适用性,即使指令最终大致相同):

答案

请注意,每个模块中的每个nginx指令都包含有限数量的适用Context

因此,遗憾的是,这里根本没有指示窥探SNI!

相反,it's actually documented in stream_core引用" Different servers must listen on different address:port pairs.",正如您所注意到的那样,也与listen directive works within the more-common http_core的方式相反,并且是一个相当明确的参考,即listenstream目前没有为listen实施任何SNI支持。

讨论

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

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

  • 为了便于讨论,如果OpenVPN 使用TLS连接,并且 使用TLS来验证具有用户证书的用户<会使MitM流更难,但仍然一直带有身份验证数据),理论上,如果nginx在streamHTTP/2内有{SN}支持那么你可能已经能够用nginx主动MitM它(从proxy_ssl is already supported in stream_proxy开始)。

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

最后,您可能会问,流模块对于什么有用呢?我相信它的目标受众是(0),基于客户端IP地址的upstream,负载均衡hash与多个if (!$fp) { //can't go on } 服务器,和/或者,(1),stunnel更直接且与协议无关的替代。

答案 2 :(得分:5)

AS @Lochnair提到,您可以使用ngx_stream_map modulevariable $server_addr来解决此问题。这是我的例子。

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