因为我们的项目中用到了Skipper作为UI层的gateway,在支持一个新上线的UI应用的时候,发现一个奇怪的现象,假定:
Skipper Domain: https://ui.example.com
Webapp Internal Domain:https://webapp.internal.example.com
在Skipper收到一个请求 GET https://ui.example.com/webapp
的时候,根据Routing规则:
Path("/webapp") -> "https://webapp.internal.example.com"
请求应该是会被转到这个地址的。测试发现:
# 正常工作,返回200
curl -i https://webapp.internal.example.com
# 返回404
curl -i https://ui.example.com
神奇的就是后面的测试,返回404,而且来源于一个nginx
服务器。
通过 curl -vv
比较两个请求的差异,发现两个服务器的证书CN不同,一个是 *.example.com
,另外一个是 *.internal.example.com
,怀疑是不是SNI相关的问题,尝试了一下:
curl -i -H "Host: webapp.internal.example.com" https://ui.example.com
哈,成功返回200.
查阅了一下SNI相关资料,发现了疑惑:SNI中hello_name来源于 URL中的domain name,而不是被加密的Header。一个web server如果host多个domains,在request过来的时候,会根据URI中的domain name来查找对应domain的证书,然后用来SSL握手,如果找不到证书,才会返回一个默认的页面,比如404.
但是根据我们的试验,证书应该是正确的,所以应该不是SNI的问题。
问题是解决了,但是却不知道是什么原因,这让人很不舒服,继续找。无意中发现了这个Issue,其中提到:
This would break usage of net/http for domain fronting, an anti-censorship technique specifically relying on sending different hosts in SNI and HTTP headers. Such a use of net/http is not rare: censorship-resistant tunneling software such as Lantern and my own project Geph both use net/http this way.
domain fronting
?什么鬼,著名的lantern
居然在用,查了一下,明白了。原来这个技术就是用来翻墙的,哈。
具体来说:
比如你想访问被墙的网站forbidden.example
,你可以用36计之张冠李戴,谎称我访问的是allowed.example
, 然后在request header中指定Host: forbidden.example
,用https,这样请求就会被加密,邪恶的探测器们就无法知道你真正的地址,而且这样也符合SNI的握手协议。
现在很多网站都支持这种技术,比如google:
curl -vv -H "Host: maps.google.com" https://www.google.com
从URL上看,访问的是www.google.com
,但是你看一下返回内容会发现,这个内容却是maps.google.com
返回的。
在我们的例子中,因为skipper中设置了-proxy-preserve-host
,这样Host
header会被传递给下面的目标app,而这个目标app因为支持domain fronting,尝试着去找那个 ui.example.com
对应的证书,当然没有,所以返回404
.