Menangani permintaan http dan https menggunakan satu port dengan nginx

16

saya bertanya-tanya apakah nginx dapat menangani permintaan http dan https di port yang sama . [*]

Inilah yang saya coba lakukan. Saya menjalankan server web (lighttpd) yang menangani permintaan http, dan program C yang melayani bagian tertentu dari pohon dokumen melalui https. Kedua proses ini berjalan di server yang sama.

Pada level firewall, saya hanya dapat memiliki satu port forwarding traffic ke server ini . Jadi yang ingin saya lakukan adalah mengatur nginx di server ini sehingga ia mendengarkan permintaan pada satu port dan kemudian:

A) mengarahkan ulang semua permintaan http://myhost.com/ * sehingga mereka pergi ke localhost: 8080 (di mana lighttpd mendengarkan)

B) jika pengguna meminta URL yang dimulai dengan, misalnya, https: // myhost.com/app, itu mengirimkan permintaan itu ke localhost: 8008 (program C). Perhatikan bahwa dalam hal ini, lalu lintas antara browser jarak jauh dan nginx harus dienkripsi.

Apakah Anda pikir ini mungkin? Jika ya, bagaimana itu bisa dilakukan?

Saya tahu cara melakukan ini menggunakan dua port yang berbeda. Tantangan yang saya hadapi adalah melakukan ini hanya dengan satu port (sayangnya, saya tidak memiliki kendali atas konfigurasi firewall pada lingkungan khusus ini, jadi itu adalah batasan yang tidak bisa saya hindari). Menggunakan teknik seperti membalikkan port fowarding melalui ssh untuk mem-bypass firewall tidak akan berhasil, karena ini harus bekerja untuk pengguna jarak jauh yang memiliki tidak lebih dari browser web dan tautan internet.

Jika ini di luar kemampuan nginx, apakah Anda tahu ada produk lain yang dapat memenuhi persyaratan ini? (Sejauh ini saya tidak berhasil dalam pengaturan ini dengan lighttpd dan pound). Saya juga lebih suka menghindari Apache (walaupun saya bersedia menggunakannya jika itu satu-satunya pilihan).

Terima kasih sebelumnya, Alex

[*] Untuk lebih jelasnya, saya berbicara tentang menangani koneksi HTTP terenkripsi dan tidak terenkripsi melalui port yang sama. Tidak masalah jika enkripsi dilakukan melalui SSL atau TLS.

alemartini
sumber
Permintaan HTTPS pergi ke port 443 secara default, jadi meskipun Anda dapat menjalankannya (dan saya pikir itu mungkin dengan sedikit peretasan), Anda harus menggunakan yourhost.com dan yourhost.com:80 sebagai tautan (atau yourhost.com:443 dan yourhost.com ).
Zanchey
Oke, saya baru di Server Fault dan saya tidak tahu apakah saya bisa menghapus pertanyaan saya sendiri. Karena tampaknya masalahnya belum dirumuskan dengan cukup jelas, saya akan membuka pertanyaan baru. Bagaimanapun, terima kasih banyak kepada semua orang yang telah berkontribusi dengan saran yang bermanfaat untuk masalah ini.
alemartini

Jawaban:

18

Menurut artikel wikipedia tentang kode status, Nginx memiliki kode kesalahan khusus ketika lalu lintas http dikirim ke port https (kode kesalahan 497)

Dan menurut nginx docs pada error_page , Anda dapat mendefinisikan URI yang akan ditampilkan untuk kesalahan tertentu.
Dengan demikian kita dapat membuat uri bahwa klien akan dikirim ketika kode kesalahan 497 dinaikkan.

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

Namun jika klien membuat permintaan melalui metode lain selain GET, permintaan itu akan berubah menjadi GET. Dengan demikian untuk mempertahankan metode permintaan yang datang melalui klien; kami menggunakan pengalihan kesalahan pemrosesan seperti yang ditunjukkan dalam dokumen nginx pada error_page

Dan itulah sebabnya kami menggunakan 301 =307pengalihan.

Dengan menggunakan file nginx.conf yang ditampilkan di sini, kami dapat meminta http dan https mendengarkan pada port yang sama

Komu
sumber
17

Bagi mereka yang mungkin mencari:

Tambahkan ssl on;dan error_page 497 $request_uri;ke definisi server Anda.

HoverHell
sumber
8
Sedikit peningkatan pada jawaban HoverHells: Tambahkan ssl on;dan error_page 497 =200 $request_uri;ke definisi server Anda. Ini akan mengubah kode status menjadi 200.
Mawi12345
Jawaban kesalahan penyajian ini menjelaskan mengapa solusi ini berfungsi.
SiliconMind
4

Jika Anda ingin benar-benar pintar, Anda dapat menggunakan proxy koneksi untuk mengendus beberapa byte pertama dari aliran data yang masuk, dan menyerahkan koneksi berdasarkan pada konten byte 0: jika 0x16 (SSL / TLS ' handshake 'byte), meneruskan koneksi ke sisi SSL, jika ini merupakan karakter alfabet, lakukan HTTP normal. Komentar saya tentang penomoran port berlaku .

Zanchey
sumber
2

Ya, itu mungkin, tetapi perlu menambal kode sumber nginx (HoverHell memiliki solusi tanpa menambal). Nginx memperlakukan ini sebagai kesalahan konfigurasi daripada konfigurasi yang valid.

Variabel $ ssl_session_id dapat digunakan untuk membedakan antara koneksi biasa dan ssl.

Patch terhadap 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)

Konfigurasi server:

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
sumber
1

Saya tidak berpikir ada sesuatu yang dapat menangani dua protokol berbeda pada satu port ...

Saya ingin tahu mengapa Anda hanya dapat meneruskan satu port, tetapi selain itu ... itu tidak ideal tetapi jika saya berada di posisi Anda, saya akan melayani semuanya melalui https.

William Hilsum
sumber
Hai Wil, dan terima kasih atas jawaban Anda! Saya pikir bahwa melayani semuanya melalui https bisa menjadi pilihan, meskipun saya ingin dapat mengatur ini dengan cara yang telah saya jelaskan. Mungkin ini bisa dilakukan jika server web depan (bertindak sebagai proxy terbalik) dapat membuat sesi http normal dan kemudian memutakhirkannya ke https tanpa mengubah port. Saya pikir perilaku ini dijelaskan dalam RFC2817 (meningkatkan ke TLS dengan HTTP / 1.1) tetapi saya tidak yakin apakah nginx atau server web lain tahu cara menangani standar itu.
alemartini
Saya tidak punya waktu untuk membaca seluruh RFC (dan tidak yakin saya cukup pintar untuk memahaminya!) Tetapi apakah Anda berbicara tentang negosiasi standar sebelum sesi aman dibuat atau sesi yang berbeda penuh meledak? Saya rasa saya mengerti sedikit lebih - server melayani pada dua port dan itu adalah proxy yang merujuk permintaan - terdengar keren tapi saya belum pernah melihatnya selesai. Mungkin solusinya bisa melalui pembuatan situs aman tunggal pada satu port dan memiliki seluruh direktori virtual yang hanya mewarisi / mengimpor situs web lain? Itu tidak mengatasi semua masalah, tetapi mungkin hanya bekerja: S
William Hilsum
1

Anda tidak dapat mendukung HTTP dan HTTPS melalui port yang sama, karena kedua ujung koneksi mengharapkan untuk berbicara bahasa tertentu, dan mereka tidak cukup pintar untuk bekerja jika ujung yang lain berbicara sesuatu yang lain.

Seperti komentar Anda untuk jawaban Wil menyarankan, Anda dapat menggunakan upgrade TLS (saya percaya rilis nginx yang lebih baru mendukungnya, meskipun saya belum mencoba), tetapi itu tidak menjalankan HTTP dan HTTPS, itu hanya menjalankan HTTP dengan upgrade TLS. Masalahnya masih dukungan browser - sebagian besar browser (masih) tidak mendukungnya. Jika Anda memiliki sekelompok klien yang terbatas, maka ini adalah kemungkinan.

womble
sumber
1
Lalu lintas HTTP terenkripsi dan tidak terenkripsi dapat ditangani melalui satu port. Yang ingin saya ketahui adalah apakah ini mungkin dengan menggunakan nginx atau produk lain (seperti lighttpd) yang bertindak sebagai proksi terbalik. Sangat mungkin pengaturan semacam ini dapat ditangani oleh Apache, tetapi saya lupa menyebutkan dalam pertanyaan awal saya bahwa saya lebih suka tidak harus menggunakan Apache (walaupun saya akan melakukannya jika tidak ada pilihan lain di Linux platform untuk mencapai ini).
alemartini
Seperti yang saya katakan dalam jawaban saya, "Saya percaya dukungan rilis nginx yang lebih baru [peningkatan TLS], walaupun saya belum mencoba". Jika Anda membutuhkan saya untuk membaca manual untuk Anda, maka Anda kurang beruntung.
womble
Maaf jika saya memberi kesan bahwa saya membutuhkan seseorang untuk membaca manual untuk saya. Sepertinya masalah (dan pertanyaan tentang itu) tidak dijelaskan dengan cukup tepat (kesalahan saya), yang mengarah ke interpretasi yang berbeda dari apa yang saya tanyakan atau butuhkan. Jadi saya memutuskan untuk membuka pertanyaan baru tentang masalah ini dan mencoba untuk menghindari kemungkinan kebingungan sehubungan dengan masalah atau pertanyaan spesifik tentang itu. Bagaimanapun, terima kasih atas waktu Anda dan untuk berbagi wawasan Anda.
alemartini
0

Saya tidak yakin bagaimana itu berhasil, tetapi CUPSD merespons baik http maupun https pada port 631. Jika nginx tidak dapat melakukannya sekarang, mungkin mereka dapat belajar dari bagaimana tim CUPS menariknya, tetapi CUPS berada di bawah GPL, jadi nginx mungkin harus melihat mengubah lisensinya jika mereka ingin menerapkan kemampuan seperti itu dan tidak dapat menemukan kode untuk melakukannya di tempat lain.

Ryan Grange
sumber
0

Secara teori, Anda dapat memiliki halaman web yang dapat diakses melalui HTTP, yang dapat membuka WebSocket di https: 443 ke mana saja yang diinginkan. Jabat tangan awal WebSocket adalah HTTP. Jadi, ya, dimungkinkan untuk membuat halaman yang terlihat tidak aman benar-benar mampu komunikasi yang aman. Anda bisa melakukan ini dengan Perpustakaan Netty .

Djangofan
sumber
0

Ini akhirnya mungkin dilakukan dengan benar sejak 1.15.2. Lihat informasinya di sini .

Di nginx.conf Anda, tambahkan blok seperti ini (di luar blok 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;
    }
}

Kemudian Anda dapat membuat blok server normal, tetapi mendengarkan pada port yang berbeda ini:

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

Dengan cara ini, blok stream dapat membaca dan mendeteksi apakah itu TLS atau tidak (pada port 8080 dalam contoh ini), dan kemudian proksi meneruskannya ke port server yang benar secara lokal.

Sam Bull
sumber