Manejo de solicitudes http y https usando un solo puerto con nginx

16

Me preguntaba si nginx es capaz de manejar solicitudes http y https en el mismo puerto . [*]

Esto es lo que estoy tratando de hacer. Estoy ejecutando un servidor web (lighttpd) que maneja solicitudes http y un programa en C que sirve una sección particular del árbol de documentos a través de https. Estos dos procesos se ejecutan en el mismo servidor.

En el nivel de firewall, solo puedo tener un puerto que reenvíe el tráfico a este servidor . Entonces, lo que me gustaría hacer es configurar nginx en este servidor para que escuche las solicitudes en un solo puerto y luego:

A) redirige todas las solicitudes http://myhost.com/ * para que vayan a localhost: 8080 (donde lighttpd está escuchando)

B) si un usuario solicita una URL que comienza con, por ejemplo, https: // myhost.com/app, envía esa solicitud a localhost: 8008 (programa C). Tenga en cuenta que en este caso, el tráfico entre el navegador remoto y nginx debe estar encriptado.

¿Crees que esto podría ser posible? Si es así, ¿cómo se puede hacer?

Sé cómo hacer esto usando dos puertos diferentes. El desafío que enfrento es hacer esto con un solo puerto (desafortunadamente, no tengo control sobre la configuración del firewall en este entorno en particular, por lo que es una restricción que no puedo evitar). El uso de técnicas como el puerto inverso hacia adelante a través de ssh para evitar el firewall tampoco funcionará, porque esto debería funcionar para usuarios remotos que no tienen más que un navegador web y un enlace a Internet.

Si esto está más allá de las capacidades de nginx, ¿conoce algún otro producto que pueda cumplir con estos requisitos? (hasta ahora no he tenido éxito al configurar esto con lighttpd y pound). También preferiría evitar Apache (aunque estoy dispuesto a usarlo si es la única opción posible).

Gracias de antemano, Alex

[*] Para ser claros, estoy hablando de manejar conexiones HTTP encriptadas y no encriptadas a través del mismo puerto. No importa si el cifrado se realiza a través de SSL o TLS.

alemartini
fuente
Las solicitudes HTTPS van al puerto 443 de forma predeterminada, por lo que incluso si puede hacer que esto funcione (y creo que es posible con un poco de piratería informática ), necesitaría usar yourhost.com y yourhost.com:80 como enlaces (o yourhost.com:443 y yourhost.com ).
Zanchey
Ok, soy nuevo en Server Fault y no sé si puedo eliminar mi propia pregunta. Como parece que el problema no se ha formulado con suficiente claridad, en su lugar abriré una nueva pregunta. De todos modos, muchas gracias a todos los que han contribuido con sugerencias útiles para este problema.
alemartini

Respuestas:

18

Según el artículo de Wikipedia sobre códigos de estado, Nginx tiene un código de error personalizado cuando el tráfico http se envía al puerto https (código de error 497)

Y de acuerdo con los documentos de nginx en error_page , puede definir un URI que se mostrará para un error específico.
Por lo tanto, podemos crear una uri a la que se enviarán los clientes cuando se genere el código de error 497.

nginx.conf

#lets assume your IP address is 89.89.89.89 and also that you want nginx to listen on port 7000 and your app is running on port 3000

server {
    listen 7000 ssl;

    ssl_certificate /path/to/ssl_certificate.cer;
    ssl_certificate_key /path/to/ssl_certificate_key.key;
    ssl_client_certificate /path/to/ssl_client_certificate.cer;

    error_page 497 301 =307 https://89.89.89.89:7000$request_uri;

    location / {
        proxy_pass http://89.89.89.89:3000/;

        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Protocol $scheme;
    }
}

Sin embargo, si un cliente realiza una solicitud a través de cualquier otro método, excepto un GET, esa solicitud se convertirá en un GET. Por lo tanto, para preservar el método de solicitud por el que entró el cliente; Usamos redireccionamientos de procesamiento de errores como se muestra en los documentos de nginx en error_page

Y es por eso que usamos la 301 =307redirección.

Usando el archivo nginx.conf que se muestra aquí, podemos hacer que http y https escuchen en el mismo puerto

Komu
fuente
17

Para aquellos que podrían estar buscando:

Agregue ssl on;y error_page 497 $request_uri;a la definición de su servidor.

HoverHell
fuente
8
Poca mejora en la respuesta de HoverHells: agregue ssl on;y error_page 497 =200 $request_uri;a la definición de su servidor. Esto cambiará el código de estado a 200.
Mawi12345
Esta respuesta de falla de servicio explica por qué funciona esta solución.
SiliconMind
4

Si quisiera ser realmente inteligente, podría usar un proxy de conexión para detectar los primeros bytes de la secuencia de datos entrantes y transferir la conexión según el contenido del byte 0: si es 0x16 (el SSL / TLS ' byte de apretón de manos), pase la conexión al lado SSL, si es un carácter alfabético, haga HTTP normal. Se aplica mi comentario sobre la numeración de puertos .

Zanchey
fuente
2

Sí, es posible, pero necesita parchear el código fuente nginx (HoverHell tiene una solución sin parches). Nginx trata esto como una configuración incorrecta en lugar de una configuración válida.

La variable $ ssl_session_id se puede usar para diferir entre la conexión simple y la conexión SSL.

Parche contra nginx-0.7.65:

--- src/http/ngx_http_request.c-orig    2011-05-03 15:47:09.000000000 +0200
+++ src/http/ngx_http_request.c 2011-05-03 15:44:01.000000000 +0200
@@ -1545,12 +1545,14 @@

    c = r->connection;

+    /* disable plain http over https port warning
     if (r->plain_http) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "client sent plain HTTP request to HTTPS port");
         ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
         return;
     }
+    */

#if (NGX_HTTP_SSL)

Configuración del servidor:

server {
    listen 80;
    index index.html;

    location / {
        root html;
        if ($ssl_session_id) {
            root html_ssl;
        }
    }

    ssl on;
    ssl_certificate cert.crt;
    ssl_certificate_key cert.key;
}
yaplik
fuente
1

No creo que haya nada que pueda manejar dos protocolos diferentes en un solo puerto ...

Tengo curiosidad por saber por qué solo puede reenviar un puerto, pero aparte de eso ... no es lo ideal, pero si estuviera en su lugar, serviría todo a través de https.

William Hilsum
fuente
Hola Wil, y gracias por tu respuesta! Creo que servir todo a través de https podría ser una opción, aunque me gustaría poder configurarlo de la manera que he descrito. Tal vez esto podría hacerse si el servidor web frontal (que actúa como un proxy inverso) podría establecer una sesión http normal y luego actualizarla a https sin cambiar los puertos. Creo que este comportamiento se describe en RFC2817 (actualización a TLS con HTTP / 1.1), pero no estoy seguro de si nginx u otros servidores web saben cómo lidiar con ese estándar.
alemartini
No tengo tiempo para leer un RFC completo (¡y no estoy seguro de ser lo suficientemente inteligente como para entenderlo!) Pero, ¿estás hablando de la negociación estándar antes de que se establezca la sesión segura o se realicen diferentes sesiones? Creo que entiendo un poco más: el servidor está sirviendo en dos puertos y es el proxy que hace referencia a la solicitud, suena genial, pero nunca lo he visto hecho. ¿Quizás una solución podría ser mediante la creación de un único sitio seguro en un puerto y tener un directorio virtual completo que simplemente herede / importe el otro sitio web? No supera todos los problemas, pero puede funcionar: S
William Hilsum
1

No puede admitir tanto HTTP como HTTPS en el mismo puerto, porque ambos extremos de la conexión esperan hablar un cierto idioma, y ​​no son lo suficientemente inteligentes como para saber si el otro extremo está hablando algo más.

Como sugiere su comentario a la respuesta de Wil, podría usar la actualización TLS (creo que las nuevas versiones de nginx lo admiten, aunque no lo he intentado), pero eso no está ejecutando HTTP y HTTPS, solo está ejecutando HTTP con la actualización TLS. El problema sigue siendo la compatibilidad con el navegador: la mayoría de los navegadores (todavía) no lo admiten. Si tiene un grupo limitado de clientes, entonces esta es una posibilidad, sin embargo.

womble
fuente
1
El tráfico HTTP cifrado y no cifrado se puede manejar a través de un solo puerto. Lo que me gustaría saber es si esto es posible utilizando nginx u otros productos (como lighttpd) que actúan como proxies inversos. Probablemente Apache pueda manejar este tipo de configuración, pero olvidé mencionar en mi pregunta original que preferiría no tener que usar Apache (aunque lo haría si no hay otra opción en Linux plataforma para lograr esto).
alemartini
Como dije en mi respuesta, "Creo que la nueva versión de soporte de nginx [actualización de TLS], aunque no lo he intentado". Si necesita que lea el manual por usted, entonces no tiene suerte.
womble
Lamento haber dado la impresión de que necesito que alguien lea un manual para mí. Parece que el problema (y las preguntas al respecto) no se describieron con la suficiente precisión (mi error), lo que condujo a diferentes interpretaciones de lo que estaba preguntando o necesitando. Así que decidí abrir una nueva pregunta sobre este tema y tratar de evitar cualquier posible confusión con respecto al problema o las preguntas específicas al respecto. De todos modos, gracias por su tiempo y por compartir sus ideas.
alemartini
0

No estoy seguro de cómo lo logra, pero CUPSD responde tanto a http como a https en el puerto 631. Si nginx no puede hacerlo ahora, tal vez puedan aprender de cómo el equipo de CUPS lo logra, pero CUPS está bajo el GPL, por lo que nginx puede tener que buscar cambiar su licencia si quieren implementar dicha capacidad y no pueden encontrar el código para hacerlo en otro lugar.

Ryan Grange
fuente
0

En teoría, podría tener una página web accesible a través de HTTP, que puede abrir un WebSocket en https: 443 en cualquier lugar que desee. El protocolo de enlace inicial de WebSocket es HTTP. Entonces, sí, es posible hacer que una página de aspecto inseguro sea capaz de una comunicación segura. Podrías hacer esto con la Biblioteca Netty .

djangofan
fuente
0

Esto finalmente es posible hacerlo correctamente desde 1.15.2. Vea la información aquí .

En su nginx.conf agregue un bloque como este (fuera del bloque http):

stream {
    upstream http {
        server localhost:8000;
    }

    upstream https {
        server localhost:8001;
    }

    map $ssl_preread_protocol $upstream {
        default https;
        "" http;
    }

    server {
        listen 8080;
        listen [::]:8080;
        proxy_pass $upstream;
        ssl_preread on;
    }
}

Luego puede crear su bloque de servidor normal, pero escuchando en estos puertos diferentes:

server {
    listen 8000;
    listen [::]:8000;
    listen 8001 ssl;
    listen [::]:8001 ssl;
...

De esta manera, el bloque de flujo puede leer previamente y detectar si es TLS o no (en el puerto 8080 en este ejemplo), y luego el proxy lo pasa al puerto del servidor correcto localmente.

Sam Bull
fuente