wake-up-neo.com

Nginx TCP Weiterleitung basierend auf dem Hostnamen

Mit der Veröffentlichung von TCP Load Balancing für die Nginx-Community-Version möchte ich OpenVPN- und SSL-Passthrough-Daten mischen. Die einzige Möglichkeit für Nginx, zu wissen, wie der Datenverkehr weitergeleitet wird, besteht in deren Domain Name.

 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

Ich habe mir die TCP-Anleitungen und Moduldokumentation angesehen, aber es scheint nicht gut referenziert zu sein. Wenn jemand mich in die richtige Richtung lenken kann, wäre ich dankbar.

Verwandte Frage zu ServerFault: Kann ein Reverse Proxy SNI mit SSL-Pass-Through verwenden?

41
James Wong

Annahmen

Wenn ich Sie richtig verstehe, möchten Sie, dass Nginx effektiv eine einzelne IP-Adresse und TCP Port-Kombination (z. B. listen 10.0.0.1:443) Abhört und dann, abhängig von der Eigenschaft des eingehender TCP Datenverkehr wird an eine der 3 verschiedenen IP-Adressen weitergeleitet.

Sie erwähnen nicht explizit, wie Sie erwarten, dass es zwischen den drei verschiedenen Domänen differenziert, aber ich gehe davon aus, dass es sich nur um TLS handelt und Sie eine Art TLS-SNI-Mechanismus (Server Name Indication) verwenden möchten domänenbasierte Differenzierung.

Ich würde glauben, dass die Stream-bezogene Dokumentation unter http://nginx.org/docs/ für die betreffenden Module ziemlich maßgeblich und erschöpfend ist (da ich anscheinend alles hier aufführe) Es gibt noch keinen zentralen Ort für Querverweise, z. B. noch keine Verweise vom "Stream Core" -Modul auf die Submodule (und docs/stream/ leitet nur docs/ zurück), was in der Tat ziemlich verwirrend ist , da Dinge wie http://nginx.org/r/upstream nur für http gelten, ohne dass die Anwendbarkeit für stream erwähnt wird, auch wenn Die Richtlinien sind am Ende ungefähr gleich):


Antworten

Beachten Sie, dass jede Nginx-Direktive von jedem Modul eine begrenzte Anzahl von anwendbaren Context hat.

Als solches gibt es leider einfach keine Direktive, um hier in SNI zu schnüffeln!

Im Gegenteil, es ist tatsächlich in stream_core Dokumentiert dass, um zu zitieren, "Different servers must listen on different address:port pairs.", Was, wie Sie vielleicht bemerken, auch im Widerspruch dazu steht, wie die - Die Direktive listen arbeitet innerhalb der allgemeineren http_core und ist ein ziemlich eindeutiger Hinweis auf die Tatsache, dass derzeit keine Art von SNI-Unterstützung für die listen implementiert ist. ] innerhalb von stream.


Diskussion

Als Diskussionspunkt und Lösungsvorschlag ist die Annahme, dass OpenVPN-Verkehr nur TLS mit dem Snoopable-SNI ist, ebenfalls nicht unbedingt richtig (aber ich bin mit OpenSSL oder SNI nicht allzu vertraut):

  • Selbst wenn SNI heute passiv beschnüffelbar ist, widerspricht dies eindeutig dem Versprechen von TLS, die Verbindung sicher zu halten, und kann sich daher in einer zukünftigen Version von TLS ändern.

  • Aus Gründen der Diskussion, wenn OpenVPN ist nur eine TLS-Verbindung verwendet und wenn es ist NICHT TLS zur Authentifizierung von Benutzern mit Benutzerzertifikaten verwendet (was bedeuten würde) Es ist viel schwieriger, den Stream mit M zu versehen und trotzdem die gesamten Authentifizierungsdaten zu übertragen. Theoretisch gilt: Wenn Nginx SNI-Unterstützung hatte um die listen innerhalb von stream, dann hätten Sie es möglicherweise mit nginx aktiv mitmischen können (da proxy_ssl Wird bereits in stream_proxy Unterstützt ).

Vor allem glaube ich, dass OpenVPN am besten über ein eigenes UDP-basiertes Protokoll ausgeführt werden kann. In diesem Fall können Sie dieselbe IP-Adresse und Portnummer für eine Instanz des TCP-basierten https und eine andere Instanz des UDP-basierten OpenVPN verwenden ohne einen Konflikt.

Am Ende fragen Sie sich vielleicht, für was wäre das Stream-Modul dann überhaupt nützlich? Ich glaube, seine Zielgruppe wäre (0), zum Beispiel einen Lastenausgleich HTTP/2 Mit mehreren upstream Servern, basierend auf dem hash der IP-Adresse des Clients und/oder (1) einen einfacheren und protokollunabhängigeren Ersatz für stunnel .

21
cnst

Dies ist jetzt mit dem in Nginx 1.11.5 hinzugefügten ngx_stream_ssl_preread-Modul und dem in 1.11.2 hinzugefügten ngx_stream_map-Modul möglich.

Auf diese Weise kann Nginx den TLS-Client Hello lesen und anhand der SNI-Erweiterung entscheiden, welches Backend verwendet werden soll.

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;
    }
}
58
Lochnair

Wie in @Lochnair erwähnt, können Sie ngx_stream_map module und variable $ server_addr verwenden, um dieses Problem zu beheben. Hier ist mein Beispiel.

Meine Host IP ist 192.168.168.22 und ich verwende 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;
    }
}

Somit kann ich verschiedene MySQL-Dienste mit demselben Port 3306 über verschiedene VIPs besuchen. Genau wie der Besuch eines anderen HTTP-Dienstes mit demselben Port über verschiedene server_name.

192.168.168.238 -> 10.1.5.3
192.168.168.239 -> 10.1.5.4
5
aloisio