Use HTTPS NGINX as a forward proxy server

NGINX primarily designed as a reverse proxy server, but with the development of NGINX, it also can be used as one of the options forward proxy. Forward proxy itself is not complicated, and how the agent encrypted HTTPS traffic is the main problem to be solved forward proxy. This article describes the use of NGINX to forward proxy HTTPS traffic two programs, and the use of scenarios and the main problem.

HTTP / HTTPS proxy forward Classification

Brief background as a forward proxy classified under the following understanding:

According to the client's perception of whether the classification

  • General Agent : the need in the browser or system environment variables manually set the proxy address and port on the client. Such as squid, squid specify the server IP and port 3128 on the client side.
  • Transparent Proxy : The client does not need any proxy settings, "agent" this role is transparent to the client. Such as enterprise network link Web Gateway appliance.

Classified by whether the agent to decrypt the HTTPS

  • Tunnel Broker : that is, pass-through proxy. Proxy server only on the TCP protocol transparent transmission HTTPS traffic, traffic for its agents are not aware of the specific content without decrypting. The purpose server and its clients direct access to do TLS / SSL interaction. Agent NGINX embodiment discussed herein belong to this model.
  • Middleman (MITM, Man-in-the -Middle) proxy : a proxy server to decrypt HTTPS traffic for client use self-signed certificates to complete TLS / SSL handshake, for the purpose of completion of the normal TLS server interaction. This creates two TLS / SSL session link server - client - agent. As Charles , simple principles can be described with reference to the article .
    Note: The client in this case is actually a TLS handshake phase of a proxy server to get its own self-signed certificate, the certificate chain verification of default is not successful, we need to trust the proxy Root CA certificate signed certificate from the client. So the process is the client's felt. If you want to make no sense of transparent proxy, we need to push self-built Root CA certificate to the client, in the internal environment is achievable.

Why deal with forward proxy HTTPS traffic need special treatment?

As a reverse proxy, the proxy server usually end (terminate) HTTPS encrypted traffic and forwards to backend instance. HTTPS encryption and authentication process flow takes place between the client and the reverse proxy server.

And as a forward proxy in dealing with the client sent me traffic, HTTP encryption encapsulated in a TLS / SSL, the proxy server can not see the client requests the URL you want to access the domain name, as shown below. So proxy HTTPS traffic, compared to HTTP, need to do some special treatment.
image

NGINX solutions

From the foregoing the classification, NGINX HTTPS proxy solution to belong to the pass-through (tunnel) mode, ie without decrypting not perceive the upper flow. Specific solutions there are two categories layer 7 and the layer 4.

HTTP CONNECT tunnel (Layer 7 solutions)

History background

As early as 1998, that is, TLS has not officially born SSL era, dominated by Netscape SSL protocol put forward on the use of web proxy tunneling SSL traffic INTERNET-DRAFT . The core idea is to use HTTP CONNECT request to establish a HTTP CONNECT Tunnel between the client and the agent, you need to specify the destination host and port clients need access to the CONNECT request. Draft in the following picture:
image

The whole process can refer to the HTTP authoritative guide map:

  1. The client to the proxy server sends HTTP CONNECT request.
  2. The proxy server establishes a TCP connection using the HTTP CONNECT request and the destination host and port of the server.
  3. Proxy server to the client returns a 200 response HTTP.
  4. The client and proxy servers to establish HTTP CONNECT tunnel, after HTTPS traffic to the proxy server, directly to the remote destination server via TCP through. The role of the proxy server is transparent transmission HTTPS traffic and does not need to decrypt HTTPS.
    image

NGINX ngx_http_proxy_connect_module模块

NGINX as a reverse proxy server, there has been no official support for the HTTP CONNECT method. But NGINX based on modular, scalable good features, Ali @chobits provides ngx_http_proxy_connect_module modules to support the HTTP CONNECT method, so that NGINX can be extended to forward proxy.

Environment to build

Environmental CentOS 7 as an example.

1) 安装
对于新安装的环境,参考正常的安装步骤和安装这个模块的步骤(https://github.com/chobits/ngx_http_proxy_connect_module),把对应版本的patch打上之后,在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

对于已经安装编译安装完的环境,需要加入以上模块,步骤如下:

# 停止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文件配置

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

使用场景

7层需要通过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 Connection Established后就开始交互TLS/SSL握手和流量了。

NGINX stream (4层解决方案)

既然是使用透传上层流量的方法,那可不可做成“4层代理”,对TCP/UDP以上的协议实现彻底的透传呢?答案是可以的。NGINX官方从1.9.0版本开始支持ngx_stream_core_module模块,模块默认不build,需要configure时加上--with-stream选项来开启。

问题

用NGINX stream在TCP层面上代理HTTPS流量肯定会遇到本文一开始提到的那个问题:代理服务器无法获取客户端想要访问的目的域名。因为在TCP的层面获取的信息仅限于IP和端口层面,没有任何机会拿到域名信息。要拿到目的域名,必须要有拆上层报文获取域名信息的能力,所以NGINX stream的方式不是完全严格意义上的4层代理,还是要略微借助些上层能力。

ngx_stream_ssl_preread_module模块

要在不解密的情况下拿到HTTPS流量访问的域名,只有利用TLS/SSL握手的第一个Client Hello报文中的扩展地址SNI (Server Name Indication)来获取。NGINX官方从1.11.5版本开始支持利用ngx_stream_ssl_preread_module模块来获得这个能力,模块主要用于获取Client Hello报文中的SNI和ALPN信息。对于4层正向代理来说,从Client Hello报文中提取SNI的能力是至关重要的,否则NGINX stream的解决方案无法成立。同时这也带来了一个限制,要求所有客户端都需要在TLS/SSL握手中带上SNI字段,否则NGINX stream代理完全没办法知道客户端需要访问的目的域名。

环境搭建

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

对于已经安装编译安装完的环境,需要加入以上3个与stream相关的模块,步骤如下:

# 停止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文件配置
NGINX stream与HTTP不同,需要在stream块中进行配置,但是指令参数与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;
    }
}

使用场景

对于4层正向代理,NGINX对上层流量基本上是透传,也不需要HTTP CONNECT来建立隧道。适合于透明代理的模式,比如将访问的域名利用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

常见问题

1) 客户端手动设置代理导致访问不成功
4层正向代理是透传上层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 tunnel,但是由于NGINX是透传,所以把CONNECT请求直接转发给了目的服务器。目的服务器不接受CONNECT方法,所以最终出现"Proxy CONNECT aborted",导致访问不成功。

2) 客户端没有带SNI导致访问不成功
上文提到用NGINX stream做正向代理的关键因素之一是利用ngx_stream_ssl_preread_module提取出Client Hello中的SNI字段。如果客户端客户端不携带SNI字段,会造成代理服务器无法获知目的域名的情况,导致访问不成功。

在透明代理模式下(用手动绑定hosts的方式模拟),我们可以在客户端用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 s_client默认不带SNI,可以看到上面的请求在TLS/SSL握手阶段,发出Client Hello后就结束了。因为代理服务器不知道要把Client Hello往哪个目的域名转发。

If you specify SNI with openssl with servername parameters, you can normally visit a success, the command is as follows:

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

to sum up

This paper summarizes the NGINX using the HTTP CONNECT tunnel and NGINX stream are two ways to do the principles HTTPS forward proxy, environmental construction, usage scenarios and the main issues, we hope to do a variety of scenarios to provide a reference forward proxy.

Guess you like

Origin yq.aliyun.com/articles/706196