balancer - Reenvío de Nginx TCP basado en nombre de host
nginx udp (3)
Suposiciones
Si lo entiendo correctamente, efectivamente quiere que nginx escuche en una sola dirección IP y combinación de puerto TCP (por ejemplo, listen 10.0.0.1:443
), y luego, dependiendo de la característica del tráfico entrante de flujo TCP, enrute a uno de las 3 direcciones IP diferentes.
No mencionas explícitamente cómo esperas que diferencie entre los 3 dominios diferentes en juego, pero mi hipótesis es que asumes que todo es solo TLS, y debes querer utilizar algún tipo de mecanismo TLS SNI (Server Name Indication) para diferenciación basada en dominio.
Creo que la documentación relacionada con el flujo de datos proporcionada en http://nginx.org/docs/ es bastante autorizada y exhaustiva para los módulos en juego (lo estoy enumerando todo aquí, ya que aparentemente no hay un lugar central para haciendo referencia a esto todavía, p. ej., no hay referencias del módulo "core de la secuencia" a los submódulos todavía (y docs/stream/
simplemente redirige docs/
), lo que de hecho es bastante confuso, ya que cosas como http://nginx.org/r/upstream solo está documentado para aplicarse a http
, sin ninguna mención de aplicabilidad para stream
, incluso si las directivas son más o menos las mismas al final):
- 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
Responder
Tenga en cuenta que cada directiva nginx, de cada módulo, tiene un número limitado de Context
''s aplicables.
Como tal, desafortunadamente, simplemente no hay una directiva para fisgonear en SNI aquí!
Por el contrario, está documentado en stream_core
que, para citar, " Different servers must listen on different address:port pairs.
", que, como puede observar, también es contrario a cómo funciona la directiva listen
dentro del http_core
más común http_core
, y es una referencia bastante inequívoca al hecho de que actualmente no se implementa ningún tipo de soporte de SNI para listen
dentro de la stream
.
Discusión
Como un punto de discusión y una sugerencia de resolución, la suposición de que el tráfico OpenVPN es solo TLS con SNI snoopable tampoco es necesariamente correcto (pero no estoy muy familiarizado con OpenSSL o SNI):
Considere que incluso si SNI es pasivamente snoopable hoy, eso es claramente contrario a la promesa de TLS de mantener la conexión segura y, como tal, puede cambiar en una versión futura de TLS.
En aras de la discusión, si OpenVPN solo está utilizando una conexión TLS, y si NO está utilizando TLS para autenticar a los usuarios con certificados de usuario (lo que dificultaría mucho más la transmisión de MitM y, aun así, llevaría los datos de autenticación) Entonces, teóricamente, si nginx tenía soporte de SNI en la
stream
dentro de lastream
, entonces posiblemente habría sido capaz de hacerlo de manera activa con nginx (ya queproxy_ssl
ya es compatible constream_proxy
).
Lo que es más importante, creo que OpenVPN se puede ejecutar mejor a través de su propio protocolo basado en UDP, en cuyo caso, puede usar la misma dirección IP y número de puerto para una instancia de https basado en TCP y otra de OpenVPN basado en UDP sin conflicto
Al final, puede preguntar, ¿de qué forma sería útil el módulo de flujo de todos modos? Creo que su público objetivo sería, (0) balanceo de carga HTTP/2
con múltiples servidores upstream
, basado en el hash
de la dirección IP del cliente, por ejemplo, y / o, (1), de una forma más directa y directa. reemplazo agnóstico del protocolo para el stunnel
.
Con el lanzamiento del equilibrio de carga TCP para la versión de la comunidad Nginx, me gustaría mezclar los datos de paso a través de OpenVPN y SSL. La única forma de que Nginx sepa cómo enrutar el tráfico es a través de su nombre de dominio.
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
He echado un vistazo a las guías de TCP y la documentación del módulo , pero no parece estar bien referenciado. Si alguien puede indicarme la dirección correcta, estaría agradecido.
Como mencionó @Lochnair, puede usar el módulo ngx_stream_map y la variable $ server_addr para resolver este problema. Aquí está mi ejemplo.
Mi IP host es 192.168.168.22
, y uso keepalived bound 2 virtual IP to 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;
}
}
Por lo tanto, puedo visitar diferentes servicios de MySQL con el mismo puerto 3306 a través de diferentes VIP. Al igual que visitar diferentes servicios HTTP con el mismo puerto a través de diffrent server_name
.
192.168.168.238 -> 10.1.5.3
192.168.168.239 -> 10.1.5.4
Esto ahora es posible con la adición del módulo ngx_stream_ssl_preread agregado en Nginx 1.11.5 y el módulo ngx_stream_map agregado en 1.11.2.
Esto le permite a Nginx leer el TLS Client Hello y decidir en función de la extensión de SNI que utilizará.
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;
}
}