首頁 > 軟體

阿里雲國際版使用Nginx作為HTTPS轉發代理伺服器的處理方法

2022-05-13 21:51:26

NGINX最初被設計為反向代理伺服器。但是,隨著不斷髮展,NGINX也可以作為實現轉發代理的選項之一。轉發代理本身並不複雜,它解決的關鍵問題是如何加密HTTPS流量。本文介紹了使用NGINX作為HTTPS流量轉發代理的兩種方法,以及它們的應用場景和主要問題。

今天87cloud詳談下阿里雲國際如何使用NGINX作為HTTPS轉發代理伺服器

HTTP/HTTPS 轉發代理的分類

首先,讓我們仔細看看轉發代理的分類。

分類依據:代理對客戶是否透明

  • 通用代理:在這裡,代理地址和埠是在使用者端上的瀏覽器或系統環境變數中手動設定的。例如,當您在使用者端上指定 Squid 伺服器的 IP 地址和埠 3128 時。
  • 透明代理:使用者端上不需要代理設定。“代理”角色對使用者端是透明的。例如,企業網路上的 Web 閘道器裝置是透明代理。

分類依據:代理是否加密HTTPS

  • 隧道代理:這是一個透明傳輸流量的代理。代理伺服器專門通過 TCP 透明地傳輸 HTTPS 流量。它不會解密或感知其代理流量的特定內容。使用者端與目標伺服器執行直接 TLS/SSL 互動。本文介紹了與此型別相關的NGINX代理模式。
  • 中間人 (MITM) 代理:代理伺服器解密 HTTPS 流量,使用自簽名證書完成與使用者端的 TLS/SSL 握手,並完成與目標伺服器的正常 TLS 互動。在使用者端-代理-伺服器連結上設定了兩個 TLS/SSL 對談。

注意:在這種情況下,使用者端在TLS握手過程中實際獲取了代理伺服器的自簽名證書,預設情況下證書鏈的驗證不成功。代理自簽名證書中的根 CA 證書必須在使用者端上受信任。因此,使用者端知道此過程中的代理。如果將自簽名根 CA 證書推播到使用者端(在企業的內部環境中實現),則可實現透明代理。

轉發代理處理 HTTPS 流量時需要特殊處理

在充當反向代理時,代理伺服器通常會終止 HTTPS 加密流量並將其轉發到後端範例。HTTPS 流量的加密、解密和身份驗證發生在使用者端和反向代理伺服器之間。

另一方面,當充當轉發代理並處理使用者端傳送的流量時,代理伺服器不會在使用者端請求的URL中看到目標域名,因為HTTP流量已加密並封裝在TLS / SSL中,如下圖所示。因此,與 HTTP 流量不同,HTTPS 流量在代理實現期間需要一些特殊處理。

NGINX解決方案

根據前面幾節中的分類,當NGINX用作HTTPS代理時,代理是透明的傳輸(隧道)代理,既不解密也不感知上層流量。具體來說,有兩種NGINX解決方案可用:第7層(L7)和第4層(L4)。以下各節詳細介紹了這些解決方案。

HTTP 連線隧道 (L7 解決方案)

歷史背景

早在1998年TLS還沒有正式上市的時候,推廣SSL協定的網景就提出使用Web代理進行SSL流量的隧道。核心思想是使用HTTP CONNECT請求在使用者端和代理之間建立HTTP CONNECT隧道。CONNECT 請求必須指定使用者端需要存取的目標主機和埠。網際網路草案中的原始圖表如下:

有關整個過程的詳細資訊,請參閱 HTTP:權威指南中的圖表。以下步驟簡要概述了該過程。

1) 使用者端向代理伺服器傳送 HTTP CONNECT 請求。
2) 代理伺服器使用 HTTP CONNECT 請求中的主機和埠資訊與目標伺服器建立 TCP 連線。
3) 代理伺服器向用戶端返回 HTTP 200 響應。
4) 使用者端與代理伺服器建立 HTTP CONNECT 隧道。在 HTTPS 流量到達代理伺服器後,代理伺服器通過 TCP 連線透明地將 HTTPS 流量傳輸到遠端目標伺服器。代理伺服器僅透明地傳輸 HTTPS 流量,不解密 HTTPS 流量。

 

ngx_http_proxy_connect_module

作為反向代理伺服器,NGINX並不正式支援HTTP CONNECT方法。但是,由於NGINX的模組化和可延伸功能,阿里巴巴@chobits提供了連線模組(中文內容)來支援HTTP CONNECT方法,以擴充套件NGINX作為轉發代理。ngx_http_proxy_connect_module

環境建設

以 CentOS 7 環境為例,讓我們詳細看看這過程。

1) 環境安裝

對於新環境安裝,請參閱安裝連線模組的常見安裝步驟(中文內容)。ngx_http_proxy_connect_module

安裝相應版本的修補程式,並在“configure”命令下新增引數,如以下範例所示。--add-module=/path/to/ngx_http_proxy_connect_module

./configure 
--user=www 
--group=www 
--prefix=/usr/local/nginx 
--with-http_ssl_module 
--with-http_stub_status_module 
--with-http_realip_module 
--with-threads 
--add-module=/root/src/ngx_http_proxy_connect_module

此外,為現有環境新增,如下所示。ngx_http_proxy_connect_module

# 停止NGINX服務
# systemctl stop nginx
# 備份原執行檔案
# cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak
# 在原始碼路徑重新編譯
# cd /usr/local/src/nginx-1.16.0
./configure 
--user=www 
--group=www 
--prefix=/usr/local/nginx 
--with-http_ssl_module 
--with-http_stub_status_module 
--with-http_realip_module 
--with-threads 
--add-module=/root/src/ngx_http_proxy_connect_module
# make
# 不要make install
# 將新生成的可執行檔案拷貝覆蓋原來的nginx執行檔案
# cp objs/nginx /usr/local/nginx/sbin/nginx
# /usr/bin/nginx -V
nginx version: nginx/1.16.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-threads --add-module=/root/src/ngx_http_proxy_connect_module

2) 設定 nginx.conf 檔案

執行以下命令以組態檔。nginx.conf

server {
     listen  443;
    
     # dns resolver used by forward proxying
     resolver  114.114.114.114;
     # forward proxy for CONNECT request
     proxy_connect;
     proxy_connect_allow            443;
     proxy_connect_connect_timeout  10s;
     proxy_connect_read_timeout     10s;
     proxy_connect_send_timeout     10s;
     # forward proxy for non-CONNECT request
     location / {
         proxy_pass http://$host;
         proxy_set_header Host $host;
     }
 }

應用場景

在 L7 解決方案中,HTTP CONNECT 請求必須建立隧道,因此,代理伺服器是使用者端必須感知的公共代理。在使用者端上手動設定 HTTP(S) 代理伺服器的 IP 地址和埠。使用 cURL 的“-x”引數存取使用者端,如下所示。

# curl https://www.baidu.com -svo /dev/null -x 39.105.196.164:443
* About to connect() to proxy 39.105.196.164 port 443 (#0)
*   Trying 39.105.196.164...
* Connected to 39.105.196.164 (39.105.196.164) port 443 (#0)
* Establish HTTP proxy tunnel to www.baidu.com:443
> CONNECT www.baidu.com:443 HTTP/1.1
> Host: www.baidu.com:443
> User-Agent: curl/7.29.0
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 Connection Established
< Proxy-agent: nginx
<
* Proxy replied OK to CONNECT request
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
*     subject: CN=baidu.com,O="Beijing Baidu Netcom Science Technology Co., Ltd",OU=service operation department,L=beijing,ST=beijing,C=CN
...
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.baidu.com
> Accept: */*
>
< HTTP/1.1 200 OK
...
{ [data not shown]

由“-v”引數列印的上述詳細資訊指示使用者端首先與代理伺服器 39.105.196.164 建立 HTTP CONNECT 隧道。一旦代理回覆為“HTTP/1.1 200 連線已建立”,使用者端就會啟動 TLS/SSL 握手並將流量傳送到伺服器。

NGINX流(L4解決方案)

由於上層流量是透明傳輸的,因此這裡出現的關鍵問題是NGINX是否應該充當“L4代理”來實現TCP / UDP以上協定的完全透明傳輸。答案是肯定的。NGINX 1.9.0或更高版本支援ngx_stream_core_module。預設情況下不生成此模組。在命令下新增選項以啟用此模組。-- with-streamconfigure

常見問題

使用NGINX流作為TCP層HTTPS流量的代理,會導致本文開頭提到的相同問題:代理伺服器未獲得使用者端要存取的目標域名。發生這種情況是因為在 TCP 層獲得的資訊僅限於 IP 地址和埠,而不獲取域名。要獲取目標域名,代理必須能夠從上層封包中提取域名。因此,NGINX流不是嚴格意義上的L4代理,它必須尋求上層的幫助才能提取域名。

ngx_stream_ssl_preread_module

為了在不解密HTTPS流量的情況下獲取HTTPS流量的目標域名,唯一的方法是在TLS/SSL握手期間使用第一個ClientHello封包中包含的SNI欄位。從版本1.11.5開始,NGINX支援ngx_stream_ssl_preread_module。此模組有助於從 ClientHello 封包獲取 SNI 和 ALPN。對於L4轉發代理,從ClientHello封包中提取SNI的能力至關重要,否則NGINX流解決方案將無法實現。但是,這也帶來了一個限制,即在 TLS/SSL 握手期間,所有使用者端都必須在 ClientHello 封包中包含 SNI 欄位。否則,NGINX流代理將不知道使用者端需要存取的目標域名。

環境建設

1) 環境安裝

對於新安裝的環境,請參考常用安裝步驟(中文內容),直接在“configure”命令下新增、、和選項。請考慮以下範例以更好地理解。--with-stream--with-stream_ssl_preread_module--with-stream_ssl_module

./configure 
--user=www 
--group=www 
--prefix=/usr/local/nginx 
--with-http_ssl_module 
--with-http_stub_status_module 
--with-http_realip_module 
--with-threads 
--with-stream 
--with-stream_ssl_preread_module 
--with-stream_ssl_module

為已安裝和已編譯的環境新增上述三個與流相關的模組,如下所示。

# 停止NGINX服務
# systemctl stop nginx
# 備份原執行檔案
# cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak
# 在原始碼路徑重新編譯
# cd /usr/local/src/nginx-1.16.0
# ./configure 
--user=www 
--group=www 
--prefix=/usr/local/nginx 
--with-http_ssl_module 
--with-http_stub_status_module 
--with-http_realip_module 
--with-threads 
--with-stream 
--with-stream_ssl_preread_module 
--with-stream_ssl_module
# make
# 不要make install
# 將新生成的可執行檔案拷貝覆蓋原來的nginx執行檔案
# cp objs/nginx /usr/local/nginx/sbin/nginx
# nginx -V
nginx version: nginx/1.16.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-threads --with-stream --with-stream_ssl_preread_module --with-stream_ssl_module

2) 設定 nginx.conf 檔案

與HTTP不同,在流塊中設定NGINX流。但是,命令引數與HTTP塊的命令引數類似。以下程式碼段顯示了主設定。

stream {
    resolver 114.114.114.114;
    server {
        listen 443;
        ssl_preread on;
        proxy_connect_timeout 5s;
        proxy_pass $ssl_preread_server_name:$server_port;
    }
}

應用場景

作為L4轉發代理,NGINX基本上透明地將流量傳輸到上層,並且不需要HTTP CONNECT來建立隧道。因此,L4 解決方案適用於透明代理模式。例如,當目標域名通過 DNS 解析定向到代理伺服器時,需要通過繫結到使用者端來模擬透明代理模式。/etc/hosts

以下程式碼段顯示了使用者端上的命令:

cat /etc/hosts
...
# 把域名www.baidu.com繫結到正向代理伺服器39.105.196.164
39.105.196.164 www.baidu.com
 
# 正常利用curl來存取www.baidu.com即可。
# curl https://www.baidu.com -svo /dev/null
* About to connect() to www.baidu.com port 443 (#0)
*   Trying 39.105.196.164...
* Connected to www.baidu.com (39.105.196.164) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
*     subject: CN=baidu.com,O="Beijing Baidu Netcom Science Technology Co., Ltd",OU=service operation department,L=beijing,ST=beijing,C=CN
*     start date: 5月 09 01:22:02 2019 GMT
*     expire date: 6月 25 05:31:02 2020 GMT
*     common name: baidu.com
*     issuer: CN=GlobalSign Organization Validation CA - SHA256 - G2,O=GlobalSign nv-sa,C=BE
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.baidu.com
> Accept: */*
>
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: Keep-Alive
< Content-Length: 2443
< Content-Type: text/html
< Date: Fri, 21 Jun 2019 05:46:07 GMT
< Etag: "5886041d-98b"
< Last-Modified: Mon, 23 Jan 2017 13:24:45 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<
{ [data not shown]
* Connection #0 to host www.baidu.com left intact

常見問題

現在,讓我們快速看一下有關L4解決方案的關鍵問題。

1) 由於使用者端上的手動代理設定,存取嘗試失敗。

L4 轉發代理透明地傳輸上層 HTTPS 流量,不需要 HTTP CONNECT 即可建立隧道。因此,沒有必要在使用者端上設定 HTTP(S) 代理。關鍵問題是,在使用者端上手動設定 HTTP(S) 代理是否可確保存取嘗試成功。使用 cURL 的“-x”引數設定轉發代理伺服器並測試對此伺服器的存取。以下程式碼段顯示了結果。

# curl https://www.baidu.com -svo /dev/null -x 39.105.196.164:443
* About to connect() to proxy 39.105.196.164 port 443 (#0)
*   Trying 39.105.196.164...
* Connected to 39.105.196.164 (39.105.196.164) port 443 (#0)
* Establish HTTP proxy tunnel to www.baidu.com:443
> CONNECT www.baidu.com:443 HTTP/1.1
> Host: www.baidu.com:443
> User-Agent: curl/7.29.0
> Proxy-Connection: Keep-Alive
>
* Proxy CONNECT aborted
* Connection #0 to host 39.105.196.164 left intact

結果指示使用者端嘗試在 NGINX 之前建立 HTTP CONNECT 隧道。但是,由於NGINX透明地傳輸流量,因此CONNECT請求直接轉發到目標伺服器。目標伺服器不接受 CONNECT 方法。因此,“代理連線中止”反映在上面的程式碼段中,導致存取失敗。

2) 存取嘗試失敗,因為使用者端在 ClientHello 封包中不包含 SNI。

如前所述,當NGINX流用作轉發代理時,使用從ClientHello中提取SNI欄位至關重要。如果使用者端在 ClientHello 封包中未包含 SNI,則代理伺服器將不知道目標域名,從而導致存取失敗。ngx_stream_ssl_preread_module

在透明代理模式(由手動繫結主機模擬)下,使用 OpenSSL 在使用者端上進行模擬。

# openssl s_client -connect www.baidu.com:443 -msg
CONNECTED(00000003)
>>> TLS 1.2  [length 0005]
    16 03 01 01 1c
>>> TLS 1.2 Handshake [length 011c], ClientHello
    01 00 01 18 03 03 6b 2e 75 86 52 6c d5 a5 80 d7
    a4 61 65 6d 72 53 33 fb 33 f0 43 a3 aa c2 4a e3
    47 84 9f 69 8b d6 00 00 ac c0 30 c0 2c c0 28 c0
    24 c0 14 c0 0a 00 a5 00 a3 00 a1 00 9f 00 6b 00
    6a 00 69 00 68 00 39 00 38 00 37 00 36 00 88 00
    87 00 86 00 85 c0 32 c0 2e c0 2a c0 26 c0 0f c0
    05 00 9d 00 3d 00 35 00 84 c0 2f c0 2b c0 27 c0
    23 c0 13 c0 09 00 a4 00 a2 00 a0 00 9e 00 67 00
    40 00 3f 00 3e 00 33 00 32 00 31 00 30 00 9a 00
    99 00 98 00 97 00 45 00 44 00 43 00 42 c0 31 c0
    2d c0 29 c0 25 c0 0e c0 04 00 9c 00 3c 00 2f 00
    96 00 41 c0 12 c0 08 00 16 00 13 00 10 00 0d c0
    0d c0 03 00 0a 00 07 c0 11 c0 07 c0 0c c0 02 00
    05 00 04 00 ff 01 00 00 43 00 0b 00 04 03 00 01
    02 00 0a 00 0a 00 08 00 17 00 19 00 18 00 16 00
    23 00 00 00 0d 00 20 00 1e 06 01 06 02 06 03 05
    01 05 02 05 03 04 01 04 02 04 03 03 01 03 02 03
    03 02 01 02 02 02 03 00 0f 00 01 01
140285606590352:error:140790E5:SSL routines:ssl23_write:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 289 bytes
⋯

預設情況下,OpenSSL 不包括 SNI。如程式碼段所示,在傳送 ClientHello 後,前面的請求將在 TLS/SSL 握手階段終止。發生這種情況是因為代理伺服器不知道應將 ClientHello 轉發到的目標域名。s_client

使用帶有“伺服器名稱”引數的 OpenSSL 來指定 SNI,將產生成功的存取。

# openssl s_client -connect www.baidu.com:443 -servername www.baidu.com

結論

本文介紹了使用NGINX作為HTTPS流量轉發代理的兩種方法。它總結了NGINX使用HTTP CONNECT隧道和NGINX流充當HTTPS轉發代理的解決方案的原則,環境構建要求,應用場景和關鍵問題。本文在各種情況下使用NGINX作為轉發代理時用作參考,具體情況參考www.87cloud.com

到此這篇關於阿里雲國際版使用Nginx作為HTTPS轉發代理伺服器的處理方法的文章就介紹到這了,更多相關Nginx作為HTTPS轉發代理伺服器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com