67

随着 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 上的相关问题:反向代理可以使用 SNI 和 SSL 传递吗?

4

3 回答 3

89

现在可以通过添加在 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;
    }
}
于 2016-10-19T15:03:21.220 回答
33

假设

如果我理解正确,您实际上希望 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内部实现stream


讨论

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

  • 考虑一下,即使 SNI 现在是被动可窥探的,这显然与 TLS 保持连接安全的承诺背道而驰,因此,可能会在未来版本的 TLS 中发生变化。

  • 为了便于讨论,如果 OpenVPN使用 TLS 连接,并且使用 TLS 来使用用户证书对用户进行身份验证(这将使 MitM 流变得更加困难,但仍然始终携带身份验证数据) ,那么,理论上,如果 nginx在inside周围确实有 SNI 支持,那么您可能已经能够使用 nginx 主动 MitM 它(因为已经支持 in)。listenstreamproxy_sslstream_proxy

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

最后,您可能会问,流模块到底有什么用处呢?我相信它的目标受众是,(0),基于客户端 IP 地址的HTTP/2多台服务器的负载平衡,和/或,(1),一个更直接且与协议无关的替代品.upstreamhashstunnel

于 2016-01-23T00:28:09.003 回答
8

正如@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 服务。就像通过 diffrent 访问具有相同端口的不同 HTTP 服务一样server_name

192.168.168.238 -> 10.1.5.3
192.168.168.239 -> 10.1.5.4
于 2017-06-29T09:24:41.117 回答