基于主机名的 Nginx TCP 转发
Nginx TCP forwarding based on hostname
随着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 and the module documentation,但它似乎没有被很好地引用。如果有人能指出正确的方向,我将不胜感激。
ServerFault 相关问题:Can a Reverse Proxy use SNI with SSL pass through?
假设
如果我没理解错的话,您实际上是希望 nginx 监听单个 IP 地址和 TCP 端口组合(例如 listen 10.0.0.1:443
),然后根据传入 TCP 流流量的特征,将其路由到 3 个不同的 IP 地址之一。
您没有明确提及您希望它如何区分所涉及的 3 个不同域,但我的假设是您假设它只是 TLS,并且必须使用某种 TLS SNI(服务器domain-based 区分的名称指示)机制。
我认为 http://nginx.org/docs/ is quite authoritative and exhaustive for the modules at stake (I'm listing all of it here, since apparently there's no central place for cross-referencing this yet, e.g., no references from the "stream core" module to the submodules yet (and docs/stream/
just redirects back docs/
), which is indeed quite confusing, since stuff like http://nginx.org/r/upstream 中提供的 stream-related 文档仅记录为适用于 http
,而没有提及对 stream
的适用性,即使最后指令大致相同):
- http://nginx.org/docs/stream/ngx_stream_core_module.html
- http://nginx.org/docs/stream/ngx_stream_access_module.html
- http://nginx.org/docs/stream/ngx_stream_limit_conn_module.html
- http://nginx.org/docs/stream/ngx_stream_proxy_module.html
- http://nginx.org/docs/stream/ngx_stream_ssl_module.html
- http://nginx.org/docs/stream/ngx_stream_upstream_module.html
回答
请注意,每个模块中的每个 nginx 指令都有有限数量的适用 Context
。
因此,不幸的是,这里根本没有窥探 SNI 的指令!
相反,it's actually documented in stream_core
that, to quote, "Different servers must listen on different address:port pairs.
", which, as you may note, is also contrary to how the listen
directive works within the more-common http_core
,并且是一个相当明确的参考,即目前在 stream
.[=40 中的 listen
没有实现任何类型的 SNI 支持。 =]
讨论
作为讨论点和解决建议,假设 OpenVPN 流量只是带有可窥探 SNI 的 TLS 也不一定正确(但我对 OpenSSL 或 SNI 不太熟悉):
考虑到即使 SNI 今天是被动窥探的,这显然与 TLS 保持连接安全的承诺背道而驰,因此,可能会在 TLS 的未来版本中改变。
为了便于讨论,如果 OpenVPN 是 只是使用 TLS 连接,并且 不是 使用 TLS 通过用户证书对用户进行身份验证(这会使流中的 MitM 变得更加困难,但仍然始终携带身份验证数据),然后,理论上,如果 nginx 确实支持 SNI 在 stream
内的 listen
附近,那么你可能已经能够使用 nginx 主动中间人攻击它(自 proxy_ssl
is already supported in stream_proxy
)。
最重要的是,我认为 OpenVPN 最好是 运行 自己的 UDP-based 协议,在这种情况下,您可以对 [=] 的一个实例使用相同的 IP 地址和端口号100=] https 和另一个 UDP-based OpenVPN 没有冲突。
最后,你可能会问,stream 模块到底有什么用呢?我相信它的目标受众是,(0),负载平衡 HTTP/2
与多个 upstream
服务器,基于客户端 IP-address 的 hash
,例如, and/or, (1),stunnel
.
的更直接和 protocol-agnostic 替代
现在可以通过添加 1.11.2 中添加的 ngx_stream_ssl_preread module added in Nginx 1.11.5 and the ngx_stream_map module 来实现。
这允许 Nginx 读取 TLS Client 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 所述,您可以使用ngx_stream_map module and variable $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
随着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 and the module documentation,但它似乎没有被很好地引用。如果有人能指出正确的方向,我将不胜感激。
ServerFault 相关问题:Can a Reverse Proxy use SNI with SSL pass through?
假设
如果我没理解错的话,您实际上是希望 nginx 监听单个 IP 地址和 TCP 端口组合(例如 listen 10.0.0.1:443
),然后根据传入 TCP 流流量的特征,将其路由到 3 个不同的 IP 地址之一。
您没有明确提及您希望它如何区分所涉及的 3 个不同域,但我的假设是您假设它只是 TLS,并且必须使用某种 TLS SNI(服务器domain-based 区分的名称指示)机制。
我认为 http://nginx.org/docs/ is quite authoritative and exhaustive for the modules at stake (I'm listing all of it here, since apparently there's no central place for cross-referencing this yet, e.g., no references from the "stream core" module to the submodules yet (and docs/stream/
just redirects back docs/
), which is indeed quite confusing, since stuff like http://nginx.org/r/upstream 中提供的 stream-related 文档仅记录为适用于 http
,而没有提及对 stream
的适用性,即使最后指令大致相同):
- http://nginx.org/docs/stream/ngx_stream_core_module.html
- http://nginx.org/docs/stream/ngx_stream_access_module.html
- http://nginx.org/docs/stream/ngx_stream_limit_conn_module.html
- http://nginx.org/docs/stream/ngx_stream_proxy_module.html
- http://nginx.org/docs/stream/ngx_stream_ssl_module.html
- http://nginx.org/docs/stream/ngx_stream_upstream_module.html
回答
请注意,每个模块中的每个 nginx 指令都有有限数量的适用 Context
。
因此,不幸的是,这里根本没有窥探 SNI 的指令!
相反,it's actually documented in stream_core
that, to quote, "Different servers must listen on different address:port pairs.
", which, as you may note, is also contrary to how the listen
directive works within the more-common http_core
,并且是一个相当明确的参考,即目前在 stream
.[=40 中的 listen
没有实现任何类型的 SNI 支持。 =]
讨论
作为讨论点和解决建议,假设 OpenVPN 流量只是带有可窥探 SNI 的 TLS 也不一定正确(但我对 OpenSSL 或 SNI 不太熟悉):
考虑到即使 SNI 今天是被动窥探的,这显然与 TLS 保持连接安全的承诺背道而驰,因此,可能会在 TLS 的未来版本中改变。
为了便于讨论,如果 OpenVPN 是 只是使用 TLS 连接,并且 不是 使用 TLS 通过用户证书对用户进行身份验证(这会使流中的 MitM 变得更加困难,但仍然始终携带身份验证数据),然后,理论上,如果 nginx 确实支持 SNI 在
stream
内的listen
附近,那么你可能已经能够使用 nginx 主动中间人攻击它(自proxy_ssl
is already supported instream_proxy
)。
最重要的是,我认为 OpenVPN 最好是 运行 自己的 UDP-based 协议,在这种情况下,您可以对 [=] 的一个实例使用相同的 IP 地址和端口号100=] https 和另一个 UDP-based OpenVPN 没有冲突。
最后,你可能会问,stream 模块到底有什么用呢?我相信它的目标受众是,(0),负载平衡 HTTP/2
与多个 upstream
服务器,基于客户端 IP-address 的 hash
,例如, and/or, (1),stunnel
.
现在可以通过添加 1.11.2 中添加的 ngx_stream_ssl_preread module added in Nginx 1.11.5 and the ngx_stream_map module 来实现。
这允许 Nginx 读取 TLS Client 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 所述,您可以使用ngx_stream_map module and variable $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
.
192.168.168.238 -> 10.1.5.3
192.168.168.239 -> 10.1.5.4