.. _nginx-multiplexacion: Multiplexación ************** Los puertos **80** y **443** son puertos a los que comúnmente todas las redes, por muy restrictivas que sean, dejan salir ya que de lo contrario ni siquiera se permitiría la navegación en ellas. Por esto motivo, puede darse el caso de que, para asegurarnos el acceso, tengamos interés es que nuestros servicios |SSH| o |VPN| sean accesibles a través de ellos, además del acceso al propio ;program:`nginx`. Cómo multiplexemos dependerá del grado de evasión que deseemos: #. A través del puerto **80** podemos dar acceso, además de al servidor web, a |SSH| y |VPN| usando Websockets_ gracias a :ref:`wstunnel `. Con esta técnica puede llegarse a saber que tráfico se está enviando, pero, en principio, forma parte del estándar |HTML|\ 5, usar Websockets_ para construir cualquier aplicación bidireccional, así que es probable que un proxy *web* no nos pusiera reparos. #. A través del puerto **443** tenemos varias posibilidades: a. Enviar |SSH| y |VPN| sin encapsular con |SSL|, lo cual hace distinguibles los tres protocolos y, en consecuencia, detectables por un *proxy* intermedio. #. Encapsular los tres con |SSL| y: - O distinguirlos gracias al :ref:`SNI `. - O desencapsularlos y distinguirlos después. Esta solución es indetectable. #. Como por el puerto **80**, usar Websockets_ para tunelizar |SSH| y |VPN|. Esta solución también implica que se encapsulen toso los tráficos con |SSL| y, por tanto, la solución es también indetectable. Un cuadro resumen con las alternativas posibles es el siguiente: .. table:: :class: compar-80-443 +--------+------------+------------+--------------+-----------------------+ | Puerto | Técnica | Detectable | Penalización | Herramientas | +========+============+============+==============+=======================+ | 80 | Websockets | Sí | Mínima | nginx+wstunnel | +--------+------------+------------+--------------+-----------------------+ | 443 | En crudo | Sí | Ninguna | sslh ó haproxy | | +------------+------------+--------------+-----------------------+ | | |SNI| | No | Mucha | nginx ó haproxy | | +------------+------------+--------------+-----------------------+ | | |SSL| | No | Mucha | haproxy ó nginx\ [#]_ | | +------------+------------+--------------+-----------------------+ | | Websockets | Sí | Mucha | nginx+wstunnel | +--------+------------+------------+--------------+-----------------------+ .. warning:: Si la |VPN| es :ref:`Wireguard `, que utiliza |UDP|, sólo es posible usar Websockets_. .. _nginx-multi-crudo: En crudo ======== :program:`nginx` tiene dos limitaciones para discriminar tráfico en crudo, esto es, tráfico de distinto tipo dirigido al puerto **443**: - Sólo es capaz de distinguir tráfico |SSL|, de aquel que no lo es. Por tanto, sólo podríamos añadir un tráfico extra al |HTTP|\ s. - Puede analizarse el protocolo (con `$ssl_preread_protocol `_) sólo a partir de la versión *1.15.2* (o sea, no hasta Bullseye_). .. seealso:: El `sitio oficial de nginx `_ incluye un artículo que `explica la técnica `_. Para usar esta estrategia es más conveniente usar :ref:`sslh ` o :ref:`haproxy ` si se piensa combinar con otras. .. _nginx-websockets: Websockets ========== La solución más sencilla y, en principio, bastante eficaz es utilizar :ref:`wstunnel ` y facilitar la conexión de |SSH| y |VPN| con Websockets_. Para ello, debemos tenerlo arrancado en un puerto de la interfaz local, por ejemplo\ [#]_:: # wstunnel -v --server ws://127.0.0.1:8080 .. warning:: Al no restringir con :kbd:`--restrictHost` a ningún servicio, el túnel podrá usarlo cualquiera para tunelizar cualquier aplicación situada en cualquier otra máquina. Si por el contrario restringimos, no podremos usar la instancia para tunelizar dos o más servicios. y preparar :program:`nginx` para permitir el protocolo. Esto implica definir el fichero :file:`snippets/websocket.conf`: .. code-block:: nginx proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $x_forwarded_proto; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; y una configuración del sitio *ws.example.net*, que será el que usemos en el cliente: .. code-block:: nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; } map $https $x_forwarded_proto { default http; on https; } server { listen 80; listen 443 ssl; server_name ws.example.net; include snippets/snakeoil.conf; location / { proxy_pass http://127.0.0.1:8080; include snippets/websocket.conf; } } En la configuración aceptamos tanto conexiones cifradas (para lo cual en el cliente deberíamos usar el protocolo *wss*) como conexiones sin cifrar. En ambos casos, la parte servidor de :program:`wstunnel` escucharía usando el protocolo *ws*, y es :program:`nginx` el que se encarga del cifrado. .. note:: Si la conexión se hubiera identificado con una ruta, no habría más que haber cambiado la localización: .. code-block:: nginx location ^~ /wireguard/ { proxy_pass http://127.0.0.1:8080; include snippets/websocket.conf; } .. _nginx-tunnel-ssl: |SSL| ===== Una alternativa a *Websocket* es cifrar el tráfico en el cliente (|TCP| por supuesto) con |SSL| y enviarlo al puerto **443** del servidor. Usado esta técnica, el tráfico es indistinguible de |HTTP|\ s y, en consecuencia, pasará cualquier restricción. Para discriminar en el servidor los tráficos hay dos alternativas: - Usar :ref:`SNI ` y hacerlo reconocible, precisamente, por el nombre. Por ejemplo, el tráfico |SSH| puede ir dirigido a *ssh.example.net*, el |VPN| a *vpn.example.net*, y el |HTTP|\ s a cualquier otro nombre. - Descifrar el tráfico primero y analizar el tráfico, ya descifrado. Para esta estrategia deberíamos usar un *proxy* como :ref:`haproxy `. Aunque la primera alternativa es posible con :program:`nginx`, es necesario incluir un directiva :kbd:`proxy_timeout` que indica el tiempo que se mantiene abierta la conexión sin que se envíen ni reciban datos. Este tiemmpo debería ser completamente distinto para conexiones |HTTP|\ s (de pocos segundos) que para |SSH| en que puede haber periodos muertos de tiempo. Desgraciadamente, como no puede usarse una variable, es imposible hacer una configuración adecuada. En cualquier caso, se relata la configuración. .. code-block:: nginx :emphasize-lines: 16 # Añadido a nginx.conf stream { map $ssl_server_name $backend { "ssh.example.net" 127.1.1.1:22; default 127.1.1.1:80; } server { listen 192.168.0.13:443 ssl; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; include "snippets/snakeoil.conf"; proxy_connect_timeout 2s; proxy_timeout 5s; # Este es el problema proxy_pass $backend; } } Y en *127.1.1.1:80* podría colocarse el sitio web. .. warning:: Evite esta configuración: está incompleta (:program:`nginx` debería ejecutarse, además, de modo transparente) y la temporización es inaceptable. Para implementar esta solución es mejor echar mano de :ref:`haproxy `. .. rubric:: Notas al pie .. [#] :program:`nginx` sólo permite distinguir entre tráfico |SSL| y tráfico no |SSL|. .. [#] Lo mejor es crear un servicio para :ref:`systemd ` como se :ref:`ilustra al tratar sobre wstunnel `. .. |SSL| replace:: :abbr:`SSL (Secure Socket Layer)` .. |UDP| replace:: :abbr:`UDP (User Datagram Protocol)` .. |TCP| replace:: :abbr:`UDP (Transmission Control Protocol)` .. |HTML| replace:: :abbr:`HTML (HyperText Markup Language)` .. |SNI| replace:: :abbr:`SNI (Server Name Indication)` .. _el proxy wstunnel: https://github.com/erebe/wstunnel .. _Github: https://github.com .. _Websockets: https://v0ctor.me/websocket