Manipulando solicitações http e https usando uma única porta com nginx

16

Eu queria saber se o nginx é capaz de lidar com solicitações http e https na mesma porta . [*]

É isso que estou tentando fazer. Estou executando um servidor web (lighttpd) que manipula solicitações http e um programa C que serve uma seção específica da árvore de documentos por https. Esses dois processos são executados no mesmo servidor.

No nível do firewall, eu posso ter apenas uma porta encaminhando tráfego para este servidor . Então, o que eu gostaria de fazer é configurar o nginx neste servidor para que ele escute solicitações em uma única porta e, em seguida:

A) redireciona todas as solicitações http://myhost.com/ * para que elas acessem o localhost: 8080 (onde o lighttpd está escutando)

B) se um usuário solicitar uma URL iniciando com, por exemplo, https: // myhost.com/app, enviará essa solicitação para localhost: 8008 (programa C). Observe que, nesse caso, o tráfego entre o navegador remoto e o nginx deve ser criptografado.

Você acha que isso seria possível? Se for, como pode ser feito?

Eu sei como fazer isso usando duas portas diferentes. O desafio que enfrento é fazer isso com apenas uma única porta (infelizmente, não tenho controle sobre a configuração do firewall nesse ambiente específico, portanto, essa é uma restrição que não posso evitar). O uso de técnicas como a porta reversa em relação ao ssh para ignorar o firewall também não funcionará, porque isso deve funcionar para usuários remotos com nada mais que um navegador da web e um link da Internet.

Se isso estiver além dos recursos do nginx, você conhece algum outro produto que atenda a esses requisitos? (até agora não consegui configurar isso com lighttpd e pound). Eu também preferiria evitar o Apache (embora eu esteja disposto a usá-lo se for a única opção possível).

Agradecemos antecipadamente, Alex

[*] Só para esclarecer, estou falando sobre como lidar com conexões HTTP criptografadas e não criptografadas pela mesma porta. Não importa se a criptografia é feita através de SSL ou TLS.

alemartini
fonte
As solicitações HTTPS vão para a porta 443 por padrão, portanto, mesmo se você conseguir fazer isso funcionar (e acho que é possível com um pouco de hackeamento), você precisará usar yourhost.com e yourhost.com:80 como os links (ou yourhost.com:443 e yourhost.com ).
Zanchey
Ok, sou novo no Server Fault e não sei se posso excluir minha própria pergunta. Como parece que o problema não foi formulado com clareza suficiente, vou abrir uma nova pergunta. De qualquer forma, muito obrigado a todos que contribuíram com sugestões úteis para este problema.
31289 alemartini

Respostas:

17

Para aqueles que possam estar pesquisando:

Adicione ssl on;e error_page 497 $request_uri;à sua definição de servidor.

HoverHell
fonte
8
Pequena melhoria na resposta do HoverHells: adicione ssl on;e error_page 497 =200 $request_uri;à sua definição de servidor. Isso irá alterar o código de status para 200.
Mawi12345
Esta resposta a falhas de serviço explica por que essa solução funciona.
SiliconMind
17

De acordo com o artigo da wikipedia sobre códigos de status, o Nginx possui um código de erro personalizado quando o tráfego http é enviado para a porta https (código de erro 497)

E de acordo com os documentos do nginx em error_page , você pode definir um URI que será mostrado para um erro específico.
Assim, podemos criar uma uri para a qual os clientes serão enviados quando o código de erro 497 for gerado.

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

No entanto, se um cliente fizer uma solicitação por qualquer outro método, exceto um GET, esse pedido será transformado em um GET. Assim, para preservar o método de solicitação pelo qual o cliente entrou; usamos redirecionamentos de processamento de erros, conforme mostrado nos documentos nginx em error_page

E é por isso que usamos o 301 =307redirecionamento.

Usando o arquivo nginx.conf mostrado aqui, podemos ter http e https escutando na mesma porta

Komu
fonte
4

Se você quiser ser realmente esperto, poderá usar um proxy de conexão para detectar os primeiros bytes do fluxo de dados recebidos e enviar a conexão com base no conteúdo do byte 0: se for 0x16 (o SSL / TLS ' handshake 'byte), passe a conexão para o lado SSL; se for um caractere alfabético, faça HTTP normal. Meu comentário sobre a numeração de portas se aplica .

Zanchey
fonte
2

Sim, é possível, mas precisa corrigir o código-fonte nginx (o HoverHell possui solução sem o patch). O Nginx trata isso como configuração incorreta e não como configuração válida.

A variável $ ssl_session_id pode ser usada para diferir entre conexão simples e ssl.

Patch contra o 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)

Configuração do 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
fonte
1

Eu não acho que exista algo que possa lidar com dois protocolos diferentes em uma única porta ...

Estou curioso para saber por que você pode encaminhar apenas uma porta, mas isso de lado ... não é o ideal, mas se eu estivesse no seu lugar, eu serviria tudo por https.

William Hilsum
fonte
Olá Wil, e obrigado pela sua resposta! Penso que servir tudo em https poderia ser uma opção, embora eu gostaria de poder configurá-lo da maneira que descrevi. Talvez isso possa ser feito se o servidor Web front (atuando como um proxy reverso) puder estabelecer uma sessão http normal e depois atualizá-la para https sem alterar as portas. Eu acho que esse comportamento é descrito na RFC2817 (atualizando para TLS dentro do HTTP / 1.1), mas não tenho certeza se o nginx ou outros servidores da Web sabem como lidar com esse padrão.
28289 alemartini
Não tenho tempo para ler uma RFC inteira (e não tenho certeza de que sou inteligente o suficiente para entendê-la!), Mas você está falando sobre a negociação padrão antes que a sessão segura seja estabelecida ou em sessões diferentes? Acho que entendi um pouco mais - o servidor está servindo em duas portas e é o proxy que refere a solicitação - parece legal, mas nunca vi isso feito. Talvez uma solução seja criar um site seguro único em uma porta e ter um diretório virtual inteiro que simplesmente herda / importa o outro site? Ele não supera todos os problemas, mas pode funcionar: S
William Hilsum
1

Você não pode suportar HTTP e HTTPS na mesma porta, porque as duas extremidades da conexão esperam falar um determinado idioma e não são inteligentes o suficiente para resolver se a outra extremidade está falando outra coisa.

Como sugeriu seu comentário à resposta de Wil, você pode usar a atualização TLS (acredito que as versões mais recentes do nginx o suportam, embora ainda não tenha tentado), mas isso não está executando HTTP e HTTPS, está executando HTTP com atualização TLS. O problema ainda é o suporte ao navegador - a maioria dos navegadores (ainda) não o suporta. Se você tem um pool limitado de clientes, essa é uma possibilidade, no entanto.

mulher
fonte
1
O tráfego HTTP criptografado e não criptografado pode ser tratado através de uma única porta. O que eu gostaria de saber é se isso é possível usando o nginx ou outros produtos (como lighttpd) atuando como proxies reversos. Provavelmente esse tipo de configuração pode ser gerenciado pelo Apache, mas esqueci de mencionar na minha pergunta original que prefiro não ter que usar o Apache (embora eu o faça se não houver outra opção no Linux) plataforma para conseguir isso).
28289 alemartini
Como eu disse na minha resposta, "acredito que o suporte mais recente à liberação do nginx [atualização do TLS], embora ainda não tenha tentado". Se você precisar que eu leia o manual, não terá sorte.
womble
Me desculpe se eu dei a impressão de que preciso de alguém para ler um manual para mim. Parece que o problema (e as perguntas sobre ele) não foram descritos com precisão suficiente (meu erro), levando a diferentes interpretações do que eu estava perguntando ou precisando. Por isso, decidi abrir uma nova pergunta sobre esse problema e tentar evitar qualquer possível confusão com relação ao problema ou às perguntas específicas sobre ele. De qualquer forma, obrigado pelo seu tempo e por compartilhar sua visão.
alemartini
0

Não tenho certeza de como isso ocorre, mas o CUPSD responde aos http e https na porta 631. Se o nginx não puder fazer isso agora, talvez eles possam aprender como a equipe do CUPS consegue, mas o CUPS está sob a GPL, portanto, o nginx pode precisar alterar sua licença se quiser implementar esse recurso e não conseguir encontrar o código para fazê-lo em outro lugar.

Ryan Grange
fonte
0

Em teoria, você poderia ter uma página da web acessível via HTTP, capaz de abrir um WebSocket em https: 443 para qualquer lugar que ele quiser. O handshake inicial do WebSocket é HTTP. Portanto, sim, é possível criar uma página com aparência insegura, realmente capaz de comunicação segura. Você poderia fazer isso com a Netty Library .

djangofan
fonte
0

Finalmente, é possível fazer isso corretamente desde 1.15.2. Veja as informações aqui .

No seu nginx.conf, adicione um bloco como este (fora do bloco 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;
    }
}

Em seguida, você pode criar seu bloco de servidor normal, mas ouvindo estas portas diferentes:

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

Dessa forma, o bloco de fluxo pode ler e detectar se é TLS ou não (na porta 8080 neste exemplo) e, em seguida, o proxy passa localmente para a porta correta do servidor.

Sam Bull
fonte