最近网络持续抽风,套cloudflare和github的网页持续上不去,错误的特征就是超时,ping可以通,但443端口无反应,连端口关闭的信息都没有……

在排查故障时看到了一些关于SSLHTTPSSNInginx的东西,再加上内网有一个树莓派。前几天刚刚安排上smartdns做内网dns服务器,于是就安排了一下nginx的HTTPS反向(正向)代理。这nginx前置都快糊客户端脸上不应该叫正向代理? →_→

nginx可以利用ngx_http_proxy_module来进行反向代理。

通常的配置是这样的:

server{
        listen 443 ssl http2;
        server_name  cfwk.dogcraft.top;
        access_log /var/log/nginx/tuu3.log;
        ssl_certificate /home/pi/testdog.pem;
        ssl_certificate_key /home/pi/dogww.key;
        access_log /var/log/nginx/indexpage.log ;
location  /{
        proxy_pass https://162.159.211.103:8443/;
        proxy_set_header Host $http_host;
        #proxy_set_header Host "sdd.dogcraft.top";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding '';
        #proxy_buffering off;
        #proxy_ssl_verify off;
        #proxy_ssl_name yyxxxxxx.dogcraft.top;
        #proxy_ssl_server_name on;
        }
}

其中proxy_pass https://162.159.211.103:8443/;这个是后端服务器的地址。这个配置在不代理cloudflare的网站时是没有问题的。

在内网树莓派上建立这样一个代理用来代理www.dogcraft.top也就是直接部署在阿里云上的主页。

server{
        listen 443 ssl http2;
        server_name  cfwk.dogcraft.top;
        access_log /var/log/nginx/tuu3.log;
        ssl_certificate /home/pi/testdog.pem;
        ssl_certificate_key /home/pi/dogww.key;
        access_log /var/log/nginx/indexpage.log ;
location  /{
        proxy_pass https://101.37.173.241/;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding '';
        }
}

然后在本地的/etc/hostscfwk.dogcraft.top的地址指向树莓派的内网ip,然后这样访问cfwk.dogcraft.top就会出现www.dogcraft.top的页面,树莓派是自身带有Let's Encrypt发的证书,所以浏览器不会报证书错误的提醒。

现在改动一个地方


server{
        listen 443 ssl http2;
        server_name  cfwk.dogcraft.top;
        access_log /var/log/nginx/tuu3.log;
        ssl_certificate /home/pi/testdog.pem;
        ssl_certificate_key /home/pi/dogww.key;
        access_log /var/log/nginx/indexpage.log ;
location  /{
        proxy_pass https://101.37.173.241/;
        proxy_set_header Host "dogcraft.top";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding '';
        }
}

proxy_set_header Host "dogcraft.top";给安排上,这个时候再访问https://cfwk.dogcraft.top出现的页面就是dogcraft.top这个页面了,这就是一个比较有意思的事情了。

nginx这类可以安排多个网站的基础设施是通过Host头来区分不同的服务的,如果没有指定或者里面没有这个host,就会返回一个默认的host(在监听端口那里标有default的那一个)。现在有了两处配置,分别是proxy_passHost。其中proxy_pass决定把http请求安排到那个IP地址之上,'Host'头决定nginx把http请求发送给哪一个后端。

现在的情况比较简单,现在如果用一个套上了一层cloudflare的网站作为后端那就麻烦了,先是直接把配置搬过去


server{
        listen 443 ssl http2;
        server_name  cfwk.dogcraft.top;
        access_log /var/log/nginx/tuu3.log;
        ssl_certificate /home/pi/testdog.pem;
        ssl_certificate_key /home/pi/dogww.key;
        access_log /var/log/nginx/indexpage.log ;
location  /{
        proxy_pass https://sig.dogcraft.top/;#这个是套了一层cloudflare的地址,源站和www.dogcraft.top在一个地方。
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding '';
        }
}

这样会出现502错误,原因是cloudflare的ip对应的服务器上同时具有很多网站,同样也是具有ssl证书,不同的网站证书肯定是不同的,但是你访问这个ip时cloudflare是不知道你要看那个网站的,所以你需要告诉cloudflare你要哪个网站的证书。这个过程是用明文完成的,因为这个时候还没有完成SSL握手,也无从加密。这是整个HTTPS加密体系中最薄弱的环节,现在知道了这个事情之后感觉这个问题是对TSL与HTTPS整个完全加密体系的嘲讽,最关键与基础的部分居然用明文。毕竟https近几年才大规模普及,当时设计体系的人也难以预料到这个情况,尤其是套上cloudflare这种CDN的网站,像www.dogcraft.top这种不套上cdn的就没有这个问题,大不了给你个默认证书,反正都是*.dogcraft.top这种的通配符证书,只要不出三级域名就完全没有问题。现在说是TLS1.3版本要修复这个问题,安排成ESNI还需要EDNS的支持,但这个标准的普及不知道要到那年那月了……(更多信息可以查一下RSA、SNI等)

按照网上的解决方案,安排成这样就行了:


server{
        listen 443 ssl http2;
        server_name  cfwk.dogcraft.top;
        access_log /var/log/nginx/tuu3.log;
        ssl_certificate /home/pi/testdog.pem;
        ssl_certificate_key /home/pi/dogww.key;
        access_log /var/log/nginx/indexpage.log ;
location  /{
        proxy_pass https://sig.dogcraft.top/;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding '';
        proxy_ssl_name sig.dogcraft.top;
        proxy_ssl_server_name on;
        }
}

即添加上两行proxy_ssl_name sig.dogcraft.top; proxy_ssl_server_name on;然后nginx会向后端的服务器发送SNI信息,然后就可以获得到正确的证书了。

然后现在有了三个配置项目。分别是proxy_pass`Host`proxy_ssl_name。这三者并不一定是一致的。

配置项目备注
proxy_pass104.28.28.226cloudflare给安排的节点ip
Hostsig.dogcraft.top套了一层cloudflare
proxy_ssl_namesig.dogcraft.top

结果:正常访问到sig.dogcraft.top

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
Hostsig.dogcraft.top套了一层cloudflare
proxy_ssl_namesig.dogcraft.top

结果:正常访问到sig.dogcraft.top

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
Hostwww.dogcraft.top在cloudflare仅dns,不套cdn的地址
proxy_ssl_namesig.dogcraft.top套了一层cloudflare的二级域名

结果:返回www.dogcraft.top的内容

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
Hostwww.dogcraft.top在cloudflare仅dns,不套cdn的地址
proxy_ssl_namedogcraft.top在cloudflare仅dns,不套cdn的地址

结果:返回www.dogcraft.top的内容

配置项目备注
proxy_passXXX.XX.xx网上搜到的解析到同一个IP地址的别人的域名
Hostwww.dogcraft.top在cloudflare仅dns,不套cdn的地址
proxy_ssl_namedogcraft.top在cloudflare仅dns,不套cdn的地址

结果:返回www.dogcraft.top的内容

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
HostXXX.XX.xx网上搜到的解析到同一个IP地址的别人的域名
proxy_ssl_namedogcraft.top在cloudflare仅dns,不套cdn的地址

结果:返回cloudflare的403

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
Hostdogcraft.top在cloudflare仅dns,不套cdn的地址
proxy_ssl_nameXXX.XX.xx网上搜到的解析到同一个IP地址的别人的域名

结果:返回cloudflare的403

配置项目备注
proxy_pass104.16.175.255:8443网上搜到的cloudflare自定义ip
Hosts.dogcraft.top套了一层cloudflare,并在非标准的https端口上工作
proxy_ssl_namedogcraft.top在cloudflare仅dns,不套cdn的地址

结果:返回s.dogcraft.top:8443的内容

配置项目备注
proxy_pass104.16.175.255:8443网上搜到的cloudflare自定义ip
Hostr.dogcraft.top没套cloudflare,并在非标准的https端口上工作
proxy_ssl_namedogcraft.top在cloudflare仅dns,不套cdn的地址

结果:返回r.dogcraft.top:8443的内容

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
Hostsdjkiusk.dogcraft.top不解析到任何地址的域名
proxy_ssl_namesig.dogcraft.top套了一层cloudflare的二级域名

结果:返回cloudflare的Origin DNS error 错误

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
Hostsig.dogcraft.top套了一层cloudflare的二级域名
proxy_ssl_namesdjkiusk.dogcraft.top不解析到任何地址的域名

结果:正常访问到sig.dogcraft.top

配置项目备注
proxy_pass104.16.175.255网上搜到的cloudflare自定义ip
Hostsig.dogcraft.top套了一层cloudflare的二级域名
proxy_ssl_namesksui.sdjkiusk.dogcraft.top不解析到任何地址的域名

结果:返回nginx的502报错,查看日志是SSL握手出了问题。


结果总结。只要以dns方式接入cloudflare,无论是不是用仅dns还是代理,通过cloudflare的ip都可以访问到。proxy_pass控制着cloudflare的节点ip以及回源所用的端口号。proxy_ssl_name控制着发回哪个站的证书,Host则是控制访问哪个站,如果两者不在同一个域名下则会返回403。


配合内网的smartdns可以对网站进行偷梁换柱,可以把百度替换成bing,如果这样做就需要自签https证书与自建CA了。

参考资料: