Nginx正向代理

使用 Nginx 搭建 HTTPS 正向代理服务
NGINX 搭建 HTTP 正向代理

最近帮同事搭建一个代理服务器,要求当请求的请求头中包含dest_ip时,就将请求转发到这个目的地址,否则就正常请求。当自己用下面这种方式很快就实现 HTTP 正向代理,信心满满的交给同事使用时,却发现这种配置无法正常代理 HTTPS 请求。

location / {
    if ($http_dest_ip != "") {
        proxy_pass http://$http_dest_ip/$request_uri;
    }
    proxy_pass https://$http_host$request_uri;
}

NGINX 代理 HTTPS 请求时 access 日志:

192.168.73.26 - - [06/Dec/2018:19:42:27 +0800] "CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-"
192.168.73.26 - - [06/Dec/2018:19:42:27 +0800] "CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-"
192.168.73.26 - - [06/Dec/2018:19:42:27 +0800] "CONNECT acs.m.taobao.com:443 HTTP/1.1" 400 179 "-" "-" "-"

NGINX 的error 日志:

2018/12/06 19:42:27 [info] 79953#1783043: *16 client sent invalid request while reading client request line, client: 192.168.73.26, server: localhost, request: "CONNECT gw.alicdn.com:443 HTTP/1.1"
2018/12/06 19:42:27 [info] 79953#1783043: *17 client sent invalid request while reading client request line, client: 192.168.73.26, server: localhost, request: "CONNECT gw.alicdn.com:443 HTTP/1.1"
2018/12/06 19:42:27 [info] 79953#1783043: *18 client sent invalid request while reading client request line, client: 192.168.73.26, server: localhost, request: "CONNECT acs.m.taobao.com:443 HTTP/1.1"

为什么 NGINX 不能做 HTTPS 正向代理服务器

HTTPS 现在已经被大范围的使用在网络数据安全传输领域,基于 HTTPS 的浏览器和服务器之间通信都是被加密的。所以,当浏览器通过代理发送一个 HTTPS 请求时,请求的地址和端口也是被加密的,代理服务器也无法知道这些信息。那么代理是如何知道请求是发到哪里呢?为了解决这个问题,浏览器会先发送一个明文的 HTTP 协议的 CONNECT 请求给代理服务器,告诉代理请求的目的地址和端口。CONNECT 请求的内容格式如下:

CONNECT ***:443 HTTP/1.1
Host: bayden.com:443
Connection: keep-alive
User-Agent: Chrome/47.0.2526.58

收到这个请求后,代理会和目标服务器建立一个 TCP 连接,并返回一个 HTTP 200 的响应给浏览器,告诉浏览器自己和目标服务器的 TCP 连接已建立。响应格式如下:

HTTP/1.1 200 Connection Established
Connection: close

之后,代理只会透明的来回传输浏览器和服务器之间经过 SSL 加密的数据包,并不知道也不需要知道传输的实际内容,直接通道关闭。

出现以上异常的具体原因是 NGINX 本身的设计就是作为一个反向代理服务器,而非正向代理服务器,并且在短期也没有打算支持正向代理,所以现在 NGINX 并不支持 CONNECT 请求方式,因此收到“CONNECT ***:443 HTTP/1.1”请求时会报“client sent invalid request while reading client request line”异常。这种情况并不是说 NGINX 无法处理 SSL,只是作为一个 forward proxy 不行。
安装扩展模块

那如何让 NGINX 可以正向代理 HTTPS 请求呢?我们需要借助一个第三方扩展模块 ngx_http_proxy_connect_module 来让 NGINX 支持 CONNECT 请求,建立一个 SSL 请求的通道。

ngx_http_proxy_connect_module 安装方式:

$ wget http://Nginx.org/download/Nginx-1.9.2.tar.gz
$ tar -xzvf Nginx-1.9.2.tar.gz
$ cd Nginx-1.9.2/
$ patch -p1 < /path/to/ngx_http_proxy_connect_module/patch/proxy_connect.patch
$ ./configure --add-module=/path/to/ngx_http_proxy_connect_module
$ make && make install

其中 “/path/to” 为 proxy_connect.patch 文件在服务器的存放地址。需要注意的是,对于使用 Mac 的同学,我目前还没有找到使用 brew install nginx 的方式安装 ngx_http_proxy_connect_module 扩展的方法。

编译安装完 ngx_http_proxy_connect_module 扩展模块后,使用如下配置即可以使 NGINX 正常代理 HTTPS 请求。

NGINX HTTPS 代理完整配置:

http {
    ...    
    resolver 8.8.8.8; # DNS 服务器可根据实际情况单独配置
    ...

    server {
        listen       80;
        server_name  proxy_server;
        ...

        proxy_connect;
        proxy_connect_allow all;
        proxy_connect_connect_timeout 10s;
        proxy_connect_read_timeout 10s;
        proxy_connect_send_timeout 10s;

        location / {
            proxy_pass http://$host;
            proxy_set_header Host $host;
        }

NGINX proxy for docker

当然,如果你会使用 docker,那么可以直接使用已经编译了 ngx_http_proxy_connect_module 模块的 NGINX 镜像 Nginx forward proxy 快速搭建一个 HTTPS正向代理服务器。

wget http://software.yangyijing.cn/software/ngx_http_proxy_connect_module.tar.gz

Dockerfile

FROM alpine:3.9
ENV NGINX_VERSION 1.15.12

# https://github.com/chobits/ngx_http_proxy_connect_module下载的主分支包
#  wget http://software.yangyijing.cn/software/ngx_http_proxy_connect_module.tar.gz
ADD ngx_http_proxy_connect_module.tar.gz /opt/

RUN GPG_KEYS=B0F4253373F8F6F510D42178520A9993A1C052F8 
	&& CONFIG="
		--prefix=/etc/nginx 
		--sbin-path=/usr/sbin/nginx 
		--modules-path=/usr/lib/nginx/modules 
		--conf-path=/etc/nginx/nginx.conf 
		--error-log-path=/var/log/nginx/error.log 
		--http-log-path=/var/log/nginx/access.log 
		--pid-path=/var/run/nginx.pid 
		--lock-path=/var/run/nginx.lock 
		--http-client-body-temp-path=/var/cache/nginx/client_temp 
		--http-proxy-temp-path=/var/cache/nginx/proxy_temp 
		--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp 
		--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp 
		--http-scgi-temp-path=/var/cache/nginx/scgi_temp 
		--user=nginx 
		--group=nginx 
		--with-http_ssl_module 
		--with-http_realip_module 
		--with-http_addition_module 
		--with-http_sub_module 
		--with-http_dav_module 
		--with-http_flv_module 
		--with-http_mp4_module 
		--with-http_gunzip_module 
		--with-http_gzip_static_module 
		--with-http_random_index_module 
		--with-http_secure_link_module 
		--with-http_stub_status_module 
		--with-http_auth_request_module 
		--with-http_xslt_module=dynamic 
		--with-http_image_filter_module=dynamic 
		--with-http_geoip_module=dynamic 
		--with-threads 
		--with-stream 
		--with-stream_ssl_module 
		--with-stream_ssl_preread_module 
		--with-stream_realip_module 
		--with-stream_geoip_module=dynamic 
		--with-http_slice_module 
		--with-mail 
		--with-mail_ssl_module 
		--with-compat 
		--with-file-aio 
		--with-http_v2_module 
		# 对应上面添加的模块目录
                --add-module=/opt/ngx_http_proxy_connect_module 
	" 
	&& addgroup -S nginx 
	&& adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx 
	&& apk add --no-cache --virtual .build-deps 
		gcc 
		libc-dev 
		make 
		openssl-dev 
		pcre-dev 
		zlib-dev 
		linux-headers 
		curl 
		gnupg1 
		libxslt-dev 
		gd-dev 
		geoip-dev 
		# 编译ngx_http_proxy_connect_module依赖的
                patch 
                pcre 
                zlib 
	&& curl -fSL https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz -o nginx.tar.gz 
	&& curl -fSL https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz.asc  -o nginx.tar.gz.asc 
	&& export GNUPGHOME="$(mktemp -d)" 
	&& found=''; 
        for server in 
		ha.pool.sks-keyservers.net 
		hkp://keyserver.ubuntu.com:80 
		hkp://p80.pool.sks-keyservers.net:80 
		pgp.mit.edu 
	; do 
		echo "Fetching GPG key $GPG_KEYS from $server"; 
		gpg --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$GPG_KEYS" && found=yes && break; 
	done; 
	test -z "$found" && echo >&2 "error: failed to fetch GPG key $GPG_KEYS" && exit 1; 
	gpg --batch --verify nginx.tar.gz.asc nginx.tar.gz 
	&& rm -rf "$GNUPGHOME" nginx.tar.gz.asc 
	&& mkdir -p /usr/src 
	&& tar -zxC /usr/src -f nginx.tar.gz 
	&& rm nginx.tar.gz 
	&& cd /usr/src/nginx-$NGINX_VERSION 
        # 对应版本的patch文件
        && patch -p1 < /opt/ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_101504.patch 
	&& ./configure $CONFIG --with-debug 
	&& make -j$(getconf _NPROCESSORS_ONLN) 
	&& mv objs/nginx objs/nginx-debug 
	&& mv objs/ngx_http_xslt_filter_module.so objs/ngx_http_xslt_filter_module-debug.so 
	&& mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module-debug.so 
	&& mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module-debug.so 
	&& mv objs/ngx_stream_geoip_module.so objs/ngx_stream_geoip_module-debug.so 
	&& ./configure $CONFIG 
	&& make -j$(getconf _NPROCESSORS_ONLN) 
	&& make install 
	&& rm -rf /etc/nginx/html/ 
	&& mkdir /etc/nginx/conf.d/ 
	&& mkdir -p /usr/share/nginx/html/ 
	&& install -m644 html/index.html /usr/share/nginx/html/ 
	&& install -m644 html/50x.html /usr/share/nginx/html/ 
	&& install -m755 objs/nginx-debug /usr/sbin/nginx-debug 
	&& install -m755 objs/ngx_http_xslt_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_xslt_filter_module-debug.so 
	&& install -m755 objs/ngx_http_image_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_image_filter_module-debug.so 
	&& install -m755 objs/ngx_http_geoip_module-debug.so /usr/lib/nginx/modules/ngx_http_geoip_module-debug.so 
	&& install -m755 objs/ngx_stream_geoip_module-debug.so /usr/lib/nginx/modules/ngx_stream_geoip_module-debug.so 
	&& ln -s ../../usr/lib/nginx/modules /etc/nginx/modules 
	&& strip /usr/sbin/nginx* 
	&& strip /usr/lib/nginx/modules/*.so 
	&& rm -rf /usr/src/nginx-$NGINX_VERSION 
	
	# Bring in gettext so we can get `envsubst`, then throw
	# the rest away. To do this, we need to install `gettext`
	# then move `envsubst` out of the way so `gettext` can
	# be deleted completely, then move `envsubst` back.
	&& apk add --no-cache --virtual .gettext gettext 
	&& mv /usr/bin/envsubst /tmp/ 
	
	&& runDeps="$( 
		scanelf --needed --nobanner --format '%n#p' /usr/sbin/nginx /usr/lib/nginx/modules/*.so /tmp/envsubst 
			| tr ',' '
' 
			| sort -u 
			| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' 
	)" 
	&& apk add --no-cache --virtual .nginx-rundeps $runDeps 
	&& apk del .build-deps 
	&& apk del .gettext 
	&& mv /tmp/envsubst /usr/local/bin/ 
	
	# Bring in tzdata so users could set the timezones through the environment
	# variables
	&& apk add --no-cache tzdata 
	
	# forward request and error logs to docker log collector
	&& ln -sf /dev/stdout /var/log/nginx/access.log 
	&& ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]

nginx配置文件

resolver 8.8.8.8;
    server {
        listen       80;
        server_name  185.184.223.120;
        proxy_connect;
        proxy_connect_allow all;
        proxy_connect_connect_timeout 600s;
        proxy_connect_read_timeout 600s;
        proxy_connect_send_timeout 600s;

        location / {
            proxy_pass http://$host;
            proxy_set_header Host $host;
        }
}

原创文章,如需转载,请注明来自:https://bigzuo.github.io/

原文地址:https://www.cnblogs.com/yangtao416/p/14694605.html