纪录:Nginx proxy 的 403 与 504 错误
简述
Nginx 代理由于流量变大,被上游的 Cloudflare 标记为异常流量,从而返回 403 错误。 WPEngine 似乎有同步 Cloudflare 规则的机制,我们的服务器 IP 被 WPEngine 加入黑名单了。所以在我们绕过 Cloudflare 直连 WPEngine 服务器的时候,又发生 504 错误。
问题
我们的网站的一个子路径托管给第三方来经营。托管的方式就是在我们的 Nginx 上,将这个路径的请求代理转发到合作方的域名下,他们使用 WPEngine。
设我方域名为 a.com
,对方域名为 a.wpengine.com
,nginx 的 proxy 规则如下(节选)
location /resources/ {
proxy_pass https://a.wpengine.com/;
proxy_set_header Host a.wpengine.com;
}
从今天某个时间开始,页面出现了 403。但是如果直接访问他们的域名,则是没有问题的。
近期双方开发人员都没有进行发布改动。
解决过程
确认是谁 403 了
一开始找对方要日志,对方提供的 access log 与 error log 都显示,他们的服务没有 403。(后话:其实这时候的日志不准确,因为请求根本没到 WPEngine 服务器)。
我已经怀疑请求压根就没到达对方服务器。那么就有可能被我们的 Nginx 给 403 了,但是感觉不合理。
这种服务代理链路问题,我们能做的就是确认是自己节点,还是上游节点出了问题。查询了一番 nginx 的文档,找到了 upstream_http_server
、upstream_status
这两个变量(参见,nginx upstream module ariable)。加到 log_format 里:
log_format www-acclog '$proxy_add_x_forwarded_for - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent $upstream_response_time ' '"$http_referer" "$http_user_agent" $http_host ' 'up_s: "$upstream_status" up_rl: "$upstream_response_length" up_sn: "$upstream_http_server"';
重启 Nginx 服务,重新查看日志,赫然发现:
- 上游服务器是 cloudflare
- 上游服务器返回 403
解决 Cloudflare 的屏蔽
显然,这种集成方式不太合理。server to server 中间隔着一个 Cloudflare 防火墙,被屏蔽是早晚的事情。
我们设想 Cloudflare 可以设置白名单、或者设置一个白名单的 Header,这样可以让我们的流量不被屏蔽。对于一个防火墙来说应该是很简单的。
然而实际操作却很坎坷。
首先,我们的合作方压根不知道这东西的存在。他们也不了解。直接甩了 WPEngine 的账号给我们,让我们自己去联系 WPEngine 的 Online Support(他妈的)。
经过了解,我们才知道 Cloudflare 是 WPEngine 内部集成的,没有开放太多配置给用户。我们跟他们的技术人员配置了一些规则,设置了特殊的 Header 放行规则,似乎不起作用。
从 25 年 3 月开始,所有托管都只能使用 Advanced Network,也就是带有 Cloudflare 的方案。最后,他们的技术人员手动帮我们把这个域名的网络,改回 Legacy Network,也就是没有 Cloudflare 的方案。这才算是绕过了。
新的问题 504
绕过 Cloudflare 后,我们的 Nginx 算是直连了 WPEngine 的服务器。但是新的报错来了,经过我们域名的访问的请求直接 504 了,而同样直接访问他们域名的请求则是正常的。
解决这个问题的过程比较不自信。
我们很快就发现,只有在我们的部分节点上无法访问他们的服务。这个现象很符合流量在某处被拦下的情况。直觉告诉我,他们的 WPEngine 依然有某种防火墙规则,把我们这些节点发过去的流量直接丢弃了。但是证据呢?
运维菜鸟这时候就临时抱佛脚,狂补网络知识。
首先,在有问题的节点上尝试 ping 他们的服务器,没问题可以 ping 通。
再用,curl
加上 -vvvv
参数。发现请求就一直卡着:
* Trying 35.247.18.169... * TCP_NODELAY set * connect to 35.247.18.169 port 443 failed: Connection timed out * Failed to connect to glowcontent.wpengine.com port 443: Connection timed out * Closing connection 0 curl: (7) Failed to connect to glowcontent.wpengine.com port 443: Connection timed out
但是卡着能证明什么呢?依然不能证明流量是被我们的 AWS 拦下,还是到了 WPEngine 被拦下的。
用 tcpdump
命令,查看流量:
xxuser@prod-yyyy:~$ sudo tcpdump host 35.247.18.169
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens5, link-type EN10MB (Ethernet), capture size 262144 bytes
10:26:37.748098 IP prod-cron0 > 169.18.247.35.bc.googleusercontent.com: ICMP echo request, id 24600, seq 1, length 64
10:26:37.811410 IP 169.18.247.35.bc.googleusercontent.com > prod-cron0: ICMP echo reply, id 24600, seq 1, length 64
10:26:38.749799 IP prod-cron0 > 169.18.247.35.bc.googleusercontent.com: ICMP echo request, id 24600, seq 2, length 64
10:26:38.811502 IP 169.18.247.35.bc.googleusercontent.com > prod-cron0: ICMP echo reply, id 24600, seq 2, length 64
-- 上面是 ping 的抓包,下面是 curl 的抓包
10:26:42.688311 IP prod-cron0.55218 > 169.18.247.35.bc.googleusercontent.com.https: Flags [S], seq 3267936914, win 26883, options [mss 8961,sackOK,TS val 2764475158 ecr 0,nop,wscale 7], length 0
10:26:43.708853 IP prod-cron0.55218 > 169.18.247.35.bc.googleusercontent.com.https: Flags [S], seq 3267936914, win 26883, options [mss 8961,sackOK,TS val 2764476179 ecr 0,nop,wscale 7], length 0
10:26:45.724844 IP prod-cron0.55218 > 169.18.247.35.bc.googleusercontent.com.https: Flags [S], seq 3267936914, win 26883, options [mss 8961,sackOK,TS val 2764478195 ecr 0,nop,wscale 7], length 0
10:26:49.788860 IP prod-cron0.55218 > 169.18.247.35.bc.googleusercontent.com.https: Flags [S], seq 3267936914, win 26883, options [mss 8961,sackOK,TS val 2764482259 ecr 0,nop,wscale 7], length 0
可以看到,我们向对方服务器一直在发送 SYN
TCP 握手请求,但是没有收到对方服务器的任何回应。但是,还是那句话:
卡着能证明什么呢?
流量到底是在哪里被拦截下来的?
我们自己检查了一遍 AWS EC2 上的网络完全策略,这些节点的 Outbound rules 都是全量放行的。
看了下节点上的 iptables-save
,规则是空的。
但是,我们还是不能证明流量出我们的云服务器流出去了。计算机网络知识的生疏、对云服务集群的网络拓扑的陌生,让我们很不自信。我想在 AWS Console 里找到一个地方,可以看到我们向 WPEngine 的请求正常流出了。但是没有找到。
最后怀着不自信的心情,跟 WPEngine 的技术人员说,“能不能检查下你们的服务器有没有什么 IP 黑名单,把我们的服务器 IP 屏蔽了”。(我们甚至不知道怎么查看自己服务器集群的公网出口 IP)。
最后发现,他们的黑名单里确实有一个 IP 的地理位置跟我们的云服务集群的地理位置吻合,让他们试着移除后,服务就正常了。
后记
问题虽然解决了,但是整体的体验是比较狼狈。
需要去补充一些知识:
- 了解 AWS 的网络拓扑。写文章时,再去看。似乎发现 AWS 的 VPC 有 Flow log,可以记录出口流量日志。
- 一些网络 Debug 的工具的使用以及方法论
- 计算机网络基础