web-dev-qa-db-fra.com

Nginx TCP transfert basé sur le nom d'hôte

Avec la sortie de TCP Équilibrage de la charge pour la version de la communauté Nginx, j'aimerais mélanger les données d'intercommunication OpenVPN et SSL. Le seul moyen pour Nginx de savoir comment acheminer le trafic consiste à nom de domaine.

 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

J'ai jeté un œil au guides TCP et au documentation du module , mais cela ne semble pas bien référencé. Si quelqu'un peut me diriger dans la bonne direction, je vous en serais reconnaissant.

Question associée sur ServerFault: n proxy inverse peut-il utiliser SNI avec SSL?

41
James Wong

Hypothèses

Si je vous ai bien compris, vous voulez réellement que nginx écoute à une adresse IP unique et à une combinaison de ports TCP (par exemple, listen 10.0.0.1:443), puis, en fonction de la caractéristique du trafic de flux entrant TCP, dirigez-le vers l'une des 3 adresses IP différentes.

Vous ne mentionnez pas explicitement la manière dont vous envisagez de différencier les 3 domaines en jeu, mais mon hypothèse est que vous supposez qu'il ne s'agit que de TLS et que vous devez vouloir utiliser une sorte de mécanisme TLS SNI (Indication du nom de serveur) pour: différenciation par domaine.

Je pense que la documentation relative au flux fournie sur http://nginx.org/docs/ fait assez autorité et est exhaustive pour les modules en jeu (je les répertorie tous ici, car apparemment il n’existe pas encore d’endroit central pour les références croisées, par exemple, aucune référence du module "stream core" aux sous-modules (et docs/stream/ _ redirige simplement docs/), ce qui est en effet assez déroutant, car des choses comme http://nginx.org/r/upstream ne sont documentées que pour s'appliquer à http, sans aucune mention de leur applicabilité à stream, même si les directives sont à peu près les mêmes à la fin):


Répondre

Notez que chaque directive nginx, à partir de chaque module, a un nombre limité de Context 'applicables.

En tant que tel, malheureusement, il n’ya simplement aucune directive de fouiller dans SNI ici!

Au contraire, c'est en fait documenté dans stream_core que, pour citer, "Different servers must listen on different address:port pairs. ", ce qui, comme vous pouvez le constater, va également à l’encontre de la manière dont la directive listen fonctionne dans le plus commun http_core , et est une référence univoque au fait qu'aucun type de support SNI n'est actuellement implémenté pour le listen à l'intérieur de stream.


Discussion

En tant que point de discussion et suggestion de résolution, l'hypothèse selon laquelle le trafic OpenVPN n'est que TLS avec le SNI pouvant être surveillé n'est pas nécessairement correcte (mais je ne connais pas trop OpenSSL ou SNI):

  • Considérez que même si SNI est passivement snoopable aujourd'hui, cela est clairement contraire à la promesse de TLS de maintenir la connexion sécurisée et, en tant que tel, pourrait changer dans une future version de TLS.

  • Par souci de discussion, si OpenVPN utilise seulement une connexion TLS, et si cette connexion n’est PAS utilisant TLS pour authentifier les utilisateurs avec des certificats d'utilisateur (ce qui rendrait beaucoup plus difficile le MitM du flux, tout en conservant les données d'authentification), puis, en théorie, si nginx disposait du support SNI autour de listen dans stream, vous auriez probablement pu le contourner activement avec nginx (depuis proxy_ssl est déjà pris en charge dans stream_proxy ).

Plus important encore, je pense qu'OpenVPN peut être mieux exécuté sur son propre protocole basé sur UDP. Dans ce cas, vous pouvez utiliser la même adresse IP et le même numéro de port pour une instance du https basé sur TCP et une autre de l'OpenVPN basé sur UDP. sans conflit.

En fin de compte, vous pouvez vous demander, à quoi servirait le module de flux de toute façon? Je crois que son public cible serait (0), l’équilibrage de charge HTTP/2 avec plusieurs serveurs upstream, basés sur le hash de l'adresse IP du client, par exemple, et/ou, (1), un remplacement plus simple et indépendant du protocole pour stunnel .

21
cnst

C’est maintenant possible avec l’ajout du module module ngx_stream_ssl_preread ajouté dans Nginx 1.11.5 et du module module ngx_stream_map ajouté à la version 1.11.2.

Cela permet à Nginx de lire le message d'accueil du client TLS et de décider en fonction de l'extension SNI du backend à utiliser.

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

Comme @Lochnair l’a mentionné, vous pouvez utiliser module ngx_stream_map et variable $ server_addr pour résoudre ce problème. Voici mon exemple.

Mon hôte IP est 192.168.168.22, et j'utilise keepalived lié 2 adresse IP virtuelle à 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;
    }
}

Ainsi, je peux visiter différents services MySQL avec le même port 3306 via différents VIP. C'est comme si vous visitiez différents services HTTP avec le même port via différents server_name.

192.168.168.238 -> 10.1.5.3
192.168.168.239 -> 10.1.5.4
5
aloisio