排除 CloudFront 502 ERROR The request could not be satisfied 问题

项目需求,在 Cloud Front 上部署了一个全球加速,加速后端的一个api。结构如下:
在这里插入图片描述
比如,客户端发起 https://www.natsusao.com,经过 CNAME 记录,关联到最近的 Cloud Front 节点后,CF转发请求给源 https://www-cdn.natsusao.com ,API 后端服务器做出处理后,再将结果原路返回。

这个时候访问 https://www.natsusao.com 出现了这个场面:
在这里插入图片描述
502 错误,证明客户端到 CF 是通的,但在回溯源站的过程中出了错。
诡异的是,同样一台服务器,更早之前采用了相同的配置方法,只是域名不同,为 www-cdn.natsusao2.com (与 natsusao只差 natsusao2),却是走得通的。

这世上没有玄学,这种情况肯定有解释。于是我一一展开可能的问题的排查。

可能是源站出了基本的访问性问题

要排除这个很简单,直接用浏览器或者HTTP客户端工具访问源站地址即可:
https://www-cdn.natsusao.com
在这里插入图片描述
结果并无问题,此项排除。

可能是 CNAME 没有正确配置

要使 Cloud Front 正确工作,需要在 CF 中配置好备用名称(CNAME)。
在这里插入图片描述
如果只是单方面把 DNS 的 CNAME 记录解析给 CloudFront 分配的域名而这一步漏填或者填错,则会发生 502。

这个时候可以直接访问 AWS 分配好的 CloudFront 域名来进行测试:
在这里插入图片描述
此域名是跳过 CNAME 解析而直接走 CF 的。直接访问它会有两种结果:

  • 如果访问后正常返回,则说明CF到源站之间无问题,需要检查 CNAME 相关的配置。
  • 如果访问后同样出现 502,则说明问题还是出在CF到源站中的别的地方。

不幸的是我们的问题是后者,所以还得继续找。

可能是证书链缺失

在浏览器或者在一些 http 客户端软件里看到证书链正常,不代表证书链是完整的。
在这里插入图片描述
这是因为关于证书链的策略,有两派做法:
1、脑补派。这一派以浏览器为代表,亦即只要服务器发送最后一层证书即可。剩余的中间证书,浏览器会自动去找对应的组织进行下载,然后脑补完毕;
2、完整派。这一派的代表比如安卓的原生请求,需要服务器发送完整的证书链,然后跟系统里的根证书匹配后形成证书链验证,而不会再去做额外的下载核对。如果中间证书是缺失的,只发了最后一层的证书,那么就会发生错误(而且错误往往非常诡异,用户不会直接看到是证书报错)。

要排查这个很简单,浏览器既然不可信了,那就直接抓包看看。我用的是 wireshark,结果抓到的证书链是完整的,并没有问题:
在这里插入图片描述
可以看到后端带了完整的证书链下来。因此这个可能性排除。

可能是TLS算法、版本不匹配

这种可能性非常小,因为CF对TLS的版本支持是很友好的。能支持 TLSv1.0 - 1.2。
在这里插入图片描述
而普遍的后端服务器(包括Nginx),都已经支持 1.2。
这同样可以通过抓到的包来排除:
在这里插入图片描述
可以看到源站是支持 TLS 1.2 的握手的。所以排除TLS版本的问题。

会不会是算法不支持呢?这种可能性更小了,不过还是要排除掉。我找到了CF的支持源站算法的列表。
支持的CloudFront和源站之间的协议和加密

在 Server Hello 握手的包里能看到服务器用的加密:
在这里插入图片描述
当然这是在浏览器发送的 Client Hello 里挑选出来的,并不是后端支持的全部。而这个算法恰好也可以被CF支持:
在这里插入图片描述于是这个可能性也被排除了。

原来证书类问题不用搞抓包这么复杂

根据AWS的建议,我们可以直接借助证书机构的证书工具,来检验我们源站的证书。
SSL服务器证书检验工具
检测结果类似:
在这里插入图片描述
上图可以看到较为完整的后端支持的算法。
在这里插入图片描述
上图可以看到不同的 http 客户端的模拟握手信息,借此工具是可以直接判断出后端可能存在的证书问题。
从上面也可以看到我们的源站并没有问题,所以证书问题被彻底排除了。

柳暗花明捉到真凶

原因排查的也差不多了,一筹莫展。

灵感

正当跟 AWS 的技术支持纠结之际,我无意中扫到了AWS文档里的一个小要点:
在这里插入图片描述
文档此处只是介绍怎么用 openssl 工具来正确判断证书是否对应,并没有直接提及502问题。但这里的 host header 引起了我的注意——此标头会影响到证书的判断!

行为配置的标头歧义

于是我返回检查了我的源站 Policy:
在这里插入图片描述AllViewer 策略的本意,是完整转发 http 客户端所有的标头、cookie等信息到源站。因为服务的权限信息需要依赖于 http header 来做,所以我们选择全部放行,让CF只充当加速的角色。

我细想了一下,这里的 HOST 有歧义:这里到底是 http 客户端访问时的域名 host,还是 CF 通过域名溯源访问到源站的 HOST 呢?(在这个问题中,就是到底是 www.natsusao.com 还是 www-cdn.natsusao.com)

确认也很简单,动手实验一下就知道了。另起一个测试CF,源站域名 glowcut2.debug.natsusao.com,干掉nginx,使用 netcat 指令来简单抓 TCP,看看带上来的 HOST 就知道(在生产环境的小朋友千万别这么干):
在这里插入图片描述
可以看到,如果是 AllViewer 的策略下,这里就会是 http 客户端初始的 HOST(SNI),CF 并不会重写。源站会原原本本收到 Viewer 一开始携带上来的 HOST。

Nginx自身也有一个容易让人误会的问题

假设一种情况,你的服务器开启了 nginx 并进行了反向代理,反向代理的 ServerName 有 SNA、SNB。这个时候一个 http 请求过来了,携带的 HOST 是 SNC,SNC并不在 nginx 反向代理的列表中,此时 nginx 会怎么处理呢?

会 404 Not Found?不,Nginx 会随机挑选一个幸运 ServerName,可能是 SNA 也可能是 SNB 来匹配。
这种随机性会造成挺多无语的问题。(测试版本:nginx/1.21.6)

本次受害者的受害原因之一也是这个。后面会提到。

问题分析

事已至此,已经可以推论出问题的全貌了。

1、首先是 http 客户端(AWS 里叫 Viewer)发起了正常的域名请求 www.natsusao.com。

2、CF 收到请求之后,对源站进行回源。在这个步骤,由于 AllViewer 策略的存在,CF 不会覆写 HOST,而是对源站 www-cdn.natsusao.com 进行了 HOST为 www.natsusao.com 的 http 请求。注意这两个域名的区别。

3、源站 nginx 收到 HOST 为 www.natsusao.com 的请求后,匹配不到 ServerName(源站本机配置的是 www-cdn.natsusao.com),随机挑选了另一个ServerName 来回应。我的源站此刻挑选到了 www-cdn.natsusao2.com。

4、回应回来的内容倒不是要紧的,要紧的是证书也跟着一起回来了。这个时候 CF 一看,证书是另一个随机的域名的 *.natsusao2.com ,跟回源的域名 www-cdn.natsusao.com 匹配不上,旋即丢出了 502 ERROR The request could not be satisfied。

整个过程的直接原因,的确是证书的问题,但归根结底又不是证书的问题,而是行为策略导致的 HOST 变化的问题,着实令人头大。

还有一个玄学疑问!

那为什么一开头提到的 www.natusao2.com 的代理是正常的呢?

很简单,因为 nginx 随机挑选到 www-cdn.natsusao2.com 来匹配,用的证书是签发的泛域名证书 *.natsusao2.com,刚好跟 http 客户端上发给 CF 的HOST www.natsusao2.com 对应上了。

也正是这个巧合,让后面的问题排查走了很多弯路。

解决方法

不同于找问题的麻烦,解决方法很简单。针对 HOST 标头设置策略即可。
CF 应该遇到过这个问题的反馈,所以特地有一个策略来解决这个问题:
在这里插入图片描述
看一眼这个策略:
在这里插入图片描述
意思就是,透传所有的 Viewer 的标头,除了 HOST。这样,CF 的回源 HOST 就能覆盖为回源的域名。源站也能正确的匹配处理。

鸣谢

至此问题就完美解决啦。
感谢 AWS 的相关工作人员的协助,@杨 @Jack。

猜你喜欢

转载自blog.csdn.net/weixin_42445065/article/details/130113760