Obsługa żądań http i https za pomocą jednego portu z nginx

16

zastanawiałem się, czy nginx jest w stanie obsłużyć żądania HTTP i https na tym samym porcie . [*]

To właśnie próbuję zrobić. Korzystam z serwera WWW (lighttpd) obsługującego żądania HTTP oraz programu C obsługującego określoną sekcję drzewa dokumentów za pośrednictwem protokołu https. Te dwa procesy działają na tym samym serwerze.

Na poziomie zapory mogę mieć tylko jeden port przekierowujący ruch na ten serwer . Chciałbym więc skonfigurować nginx na tym serwerze, aby nasłuchiwał żądań na jednym porcie, a następnie:

A) przekierowuje wszystkie żądania http://myhost.com/ *, aby trafiły do ​​localhost: 8080 (gdzie nasłuchuje lighttpd)

B) jeśli użytkownik zażąda adresu URL zaczynającego się na przykład od https: // myhost.com/app, wysyła to żądanie do localhost: 8008 (program w języku C). Pamiętaj, że w takim przypadku ruch między zdalną przeglądarką a nginx musi być szyfrowany.

Czy uważasz, że to może być możliwe? Jeśli tak, to jak to zrobić?

Wiem, jak to zrobić, używając dwóch różnych portów. Wyzwaniem, przed którym stoję, jest zrobienie tego za pomocą tylko jednego portu (niestety nie mam kontroli nad konfiguracją zapory ogniowej w tym konkretnym środowisku, więc jest to ograniczenie, którego nie mogę uniknąć). Korzystanie z technik takich jak odwrotny port do przodu przez ssh w celu ominięcia zapory również nie będzie działać, ponieważ powinno to działać dla zdalnych użytkowników, którzy nie mają nic więcej niż przeglądarkę internetową i łącze internetowe.

Jeśli wykracza to poza możliwości Nginx, czy znasz jakiś inny produkt, który spełniałby te wymagania? (jak dotąd nie udało mi się skonfigurować tego z lighttpd i funtem). Wolałbym także unikać Apache (chociaż jestem gotów go użyć, jeśli jest to jedyny możliwy wybór).

Z góry dzięki, Alex

[*] Żeby było jasne, mówię o obsłudze zaszyfrowanych i nieszyfrowanych połączeń HTTP przez ten sam port. Nie ma znaczenia, czy szyfrowanie odbywa się za pośrednictwem protokołu SSL lub TLS.

alemartini
źródło
Żądania HTTPS są domyślnie przesyłane do portu 443, więc nawet jeśli możesz to uruchomić (i myślę, że jest to możliwe przy odrobinie hakowania), musisz użyć yourhost.com i yourhost.com:80 jako linków (lub twójhost.com:443 i twójhost.com ).
Zanchey
Ok, jestem nowy w usłudze Server Fault i nie wiem, czy mogę usunąć własne pytanie. Ponieważ wydaje się, że problem nie został sformułowany wystarczająco jasno, zamiast tego otworzę nowe pytanie. W każdym razie bardzo dziękuję wszystkim, którzy przyczynili się z przydatnymi sugestiami dotyczącymi tego problemu.
alemartini

Odpowiedzi:

18

Zgodnie z artykułem Wikipedii na temat kodów stanu, Nginx ma niestandardowy kod błędu, gdy ruch HTTP jest wysyłany do portu https (kod błędu 497)

I zgodnie z dokumentacją nginx na stronie error_page , możesz zdefiniować identyfikator URI, który będzie wyświetlany dla określonego błędu.
W ten sposób możemy utworzyć identyfikator użytkownika, do którego klienci będą wysyłani, gdy pojawi się kod błędu 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;
    }
}

Jednak jeśli klient zgłosi żądanie inną metodą niż GET, żądanie to zostanie przekształcone w GET. W ten sposób, aby zachować metodę żądania, za pośrednictwem której klient wszedł; używamy przekierowań przetwarzania błędów, jak pokazano w dokumentach nginx na stronie error_page

I dlatego używamy 301 =307przekierowania.

Korzystając z pokazanego tutaj pliku nginx.conf, możemy nasłuchiwać http i https na tym samym porcie

Komu
źródło
17

Dla tych, którzy mogą szukać:

Dodaj ssl on;i error_page 497 $request_uri;do swojej definicji serwera.

HoverHell
źródło
8
Niewielkie udoskonalenie odpowiedzi HoverHells: Dodaj ssl on;i error_page 497 =200 $request_uri;do definicji serwera. Spowoduje to zmianę kodu stanu na 200.
Mawi12345,
Ta odpowiedź na błąd służy do wyjaśnienia, dlaczego to rozwiązanie działa.
SiliconMind
4

Jeśli chcesz być naprawdę sprytny, możesz użyć funkcji proxy połączenia, aby wąchać pierwszą parę bajtów przychodzącego strumienia danych i przekazać połączenie na podstawie zawartości bajtu 0: jeśli jest to 0x16 (SSL / TLS ' handshake 'byte), przekaż połączenie po stronie SSL, jeśli jest to znak alfabetyczny, wykonaj normalny HTTP. Obowiązuje mój komentarz na temat numeracji portów .

Zanchey
źródło
2

Tak, jest to możliwe, ale wymaga łatania kodu źródłowego nginx (HoverHell ma rozwiązanie bez łatania). Nginx traktuje to jako błędną konfigurację, a nie prawidłową konfigurację.

Zmienna $ ssl_session_id może być używana do różnic pomiędzy połączeniem zwykłym a ssl.

Łata przeciwko 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)

Konfiguracja serwera:

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
źródło
1

Nie sądzę, żeby było coś, co mogłoby obsłużyć dwa różne protokoły na jednym porcie ...

Zastanawiam się, dlaczego możesz przesłać tylko jeden port, ale poza tym ... nie jest to idealne, ale gdybym był w twoich butach, podałbym wszystko przez https.

William Hilsum
źródło
Cześć Wil i dziękuję za odpowiedź! Myślę, że serwowanie wszystkiego przez https może być opcją, chociaż chciałbym móc to skonfigurować w sposób, który opisałem. Być może można to zrobić, jeśli przedni serwer WWW (działający jako odwrotne proxy) mógłby ustanowić normalną sesję HTTP, a następnie uaktualnić ją do https bez zmiany portów. Myślę, że to zachowanie jest opisane w RFC2817 (aktualizacja do TLS z HTTP / 1.1), ale nie jestem pewien, czy nginx lub inne serwery internetowe wiedzą, jak sobie poradzić z tym standardem.
alemartini
Nie mam czasu na przeczytanie całego dokumentu RFC (i nie jestem pewien, czy jestem wystarczająco inteligentny, aby go zrozumieć!), Ale czy mówisz o standardowych negocjacjach przed ustanowieniem bezpiecznej sesji lub o pełnej sesji różnych sesji? Wydaje mi się, że rozumiem trochę więcej - serwer obsługuje dwa porty i to proxy odpowiada na żądanie - brzmi fajnie, ale nigdy tego nie widziałem. Być może rozwiązaniem może być utworzenie jednej bezpiecznej strony na jednym porcie i posiadanie całego katalogu wirtualnego, który po prostu dziedziczy / importuje drugą stronę? Nie rozwiązuje wszystkich problemów, ale może po prostu działać: S
William Hilsum
1

Nie można obsługiwać zarówno HTTP, jak i HTTPS przez ten sam port, ponieważ oba końce połączenia oczekują mówienia w określonym języku i nie są wystarczająco sprytne, aby się dowiedzieć, czy drugi koniec mówi coś innego.

Jako komentarz do odpowiedzi Wil sugerował, ty mógł używać TLS uaktualnić (wierzę nowsze wersje nginx wspierać ją, chociaż nie próbowałem), ale to nie działa HTTP i HTTPS, że po prostu działa HTTP z TLS uaktualnienia. Problemem jest nadal obsługa przeglądarek - większość przeglądarek (wciąż) nie obsługuje tego. Jeśli masz ograniczoną pulę klientów, jest to jednak możliwe.

womble
źródło
1
Zaszyfrowany i nieszyfrowany ruch HTTP może być obsługiwany przez pojedynczy port. Chciałbym wiedzieć, czy jest to możliwe przy użyciu nginx lub innych produktów (takich jak lighttpd) działających jako odwrotne proxy. Prawdopodobnie tego rodzaju konfiguracja może być obsługiwana przez Apache, ale w moim pierwotnym pytaniu zapomniałem wspomnieć, że wolałbym nie używać Apache (chociaż zrobiłbym to, gdyby nie było innego wyboru w Linuksie platforma do osiągnięcia tego celu).
alemartini
Jak powiedziałem w mojej odpowiedzi: „Wierzę, że nowsze wersje Nginx obsługują [uaktualnienie TLS], chociaż nie próbowałem”. Jeśli potrzebujesz, abym przeczytał instrukcję dla ciebie, to nie masz szczęścia.
womble
Przepraszam, jeśli sprawiłem wrażenie, że potrzebuję kogoś do przeczytania instrukcji. Wydaje się, że problem (i pytania na jego temat) nie zostały wystarczająco dokładnie opisane (mój błąd), co prowadzi do różnych interpretacji tego, o co prosiłem lub czego potrzebowałem. Postanowiłem więc otworzyć nowe pytanie dotyczące tego problemu i starać się unikać wszelkich możliwych nieporozumień dotyczących problemu lub szczegółowych pytań na jego temat. W każdym razie dziękuję za poświęcony czas i za podzielenie się spostrzeżeniami.
alemartini,
0

Nie jestem pewien, jak to się robi, ale CUPSD odpowiada zarówno na HTTP, jak i https na porcie 631. Jeśli nginx nie może teraz tego zrobić, być może mogą nauczyć się, jak zespół CUPS to robi, ale CUPS jest pod GPL, więc nginx może potrzebować zmiany licencji, jeśli chce wdrożyć taką możliwość i nie może znaleźć kodu, aby to zrobić gdzie indziej.

Ryan Grange
źródło
0

Teoretycznie możesz mieć stronę internetową dostępną przez HTTP, która może otworzyć WebSocket na https: 443 w dowolnym miejscu. Początkowym uzgadnianiem WebSocket jest HTTP. Tak, możliwe jest stworzenie niebezpiecznie wyglądającej strony zdolnej do bezpiecznej komunikacji. Możesz to zrobić za pomocą Netty Library .

djangofan
źródło
0

Jest to w końcu możliwe do prawidłowego wykonania od 1.15.2. Zobacz informacje tutaj .

W swoim pliku nginx.conf dodaj blok taki jak ten (poza blokiem 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;
    }
}

Następnie możesz utworzyć normalny blok serwera, ale nasłuchując na tych różnych portach:

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

W ten sposób blok strumienia może wstępnie odczytać i wykryć, czy jest to TLS, czy nie (na tym przykładzie port 8080), a następnie serwer proxy przekazuje go lokalnie do właściwego portu serwera.

Sam Bull
źródło