随着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 guides和module documentation,但它似乎没有被很好地引用。如果有人能指出我正确的方向,我将不胜感激。
ServerFault上的相关问题:Can a Reverse Proxy use SNI with SSL pass through?
答案 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
的方式相反,并且是一个相当明确的参考,即listen
内stream
目前没有为listen
实施任何SNI支持。
作为讨论点和解决方案的建议,假设OpenVPN流量只是具有可窥探SNI的TLS也不一定正确(但我对OpenSSL或SNI不太熟悉):
考虑到即使SNI今天被动地窥探,这显然违背了保持连接安全的TLS承诺,因此,可能会在未来的TLS版本中发生变化。
为了便于讨论,如果OpenVPN 仅使用TLS连接,并且 使用TLS来验证具有用户证书的用户<会使MitM流更难,但仍然一直带有身份验证数据),理论上,如果nginx在stream
内HTTP/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 module和variable $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