AWS CLB/ELB + HTTP/2 支持

AWS CLB/ELB + HTTP/2 support

目标是在一个简单的堆栈中包含 HTTP/2 支持:部署在多个 EC2 实例中的 Web 应用程序 + 使用 PROXY 协议的 transport-level CLB启用策略 (SSL:443 ➝ TCP:80) 以卸载 SSL/TLS 并平衡传入的 HTTPS 流量。

PROXY协议的几个原因:(1)地理定位逻辑的执行; (2) 执行简单的访问控制规则; (3) 日志记录。所有这些功能都需要访问可靠的(即不易伪造的)客户端 IP 地址。 AWS 中 PROXY 协议的唯一替代方案是切换到 application-level 平衡并使用 XFF header 提取客户端的远程 IP 地址。然而,这是不可接受的:任何人都可以简单地更改其 IP 地址,只需在传入的 HTTPS 请求中注入伪造的 XFF header。 AFAIK,AWS CLBs/ELBs 不要注入包含客户端远程 IP 的 header(例如 the True-Client-IP header in Akamai)。

那么,如何给栈添加H2支持呢?经过一些研究,所有可能的选择看起来都不令人满意:

综上所述,所有选项都是有问题的。恕我直言,理想的解决方案是保留原始 CLB(SSL:443 ➝ TCP:80;平衡 + SSL/TLS 卸载 + PROXY 协议)并允许在 CLB 中启用策略以通过 ALPN 宣布 H2 支持.但是恐怕这在 AWS 中是不可能的。 CLB TCP:443 ➝ TCP:443 方法的任何替代方案?

这个答案没有提供绝妙的解决方案,因为可能没有,但我相信您对 AWS Application Load Balancer(ALB,也称为 ELB/2.0 的理解存在差距).

anybody could trivially change its IP address simply injecting a fake XFF header in the incoming HTTPS request. AFAIK, AWS CLBs/ELBs do not inject a header containing the remote IP of the client

这两个方面都不正确。

客户端的远程 IP 是 X-Forwarded-For 中的 right-most IP 地址,由平衡器发送给实例。这不能被欺骗。如果客户端在 XFF 中包含一个或多个地址,根据处理 HTTP header 的规则(从左到右,从第一到最后),它们被平衡器规范化为单个 header,它们出现在实际客户端 IP 的左侧。

示例请求:我在请求中注入了两个欺骗性的 X-Forwarded-For headers,一个有 2 个值,一个有 1 个...这是 curl 发送的内容:

$ curl -v http://cx-xxxxxxxx-xxxx-xxxxxxxxxx.us-east-1.elb.amazonaws.com/test/dump/headers \
       -H 'X-Forwarded-For: 192.168.254.252, 10.10.10.10' \
       -H 'X-Forwarded-For: 172.16.16.16'
* Hostname was NOT found in DNS cache
*   Trying 52.x.x.x...
* Connected to cx-xxxxxxxx-xxxx-xxxxxxxxxx.us-east-1.elb.amazonaws.com (52.x.x.x) port 80 (#0)
> GET /test/dump/headers HTTP/1.1
> User-Agent: curl/7.35.0
> Host: cx-xxxxxxxx-xxxx-xxxxxxxxxx.us-east-1.elb.amazonaws.com
> Accept: */*
> X-Forwarded-For: 192.168.254.252, 10.10.10.10
> X-Forwarded-For: 172.16.16.16
>
< HTTP/1.1 200 OK

但这是我的实例看到的:

GET /test/dump/headers HTTP/1.1
X-Forwarded-For: 192.168.254.252, 10.10.10.10, 172.16.16.16, 203.0.113.1
X-Forwarded-Proto: http
X-Forwarded-Port: 80
Host: cx-xxxxxxxx-xxxx-xxxxxxxxxx.us-east-1.elb.amazonaws.com
X-Amzn-Trace-Id: Root=1-58d0704b-557517de784f5exxxxxxxxxx
User-Agent: curl/7.35.0
Accept: */*

我发送的 headers 已经被平衡器归一化并且 X-Forwarded-For 中的 right-most 值已经被平衡器添加 - 它是我所在的机器 运行 curl,它建立了传入连接。¹

这就是它始终与 ALB 一起工作的方式。如果客户端提供了任何 XFF,则这些 XFF 是不可信的,当然(尽管您仍应记录或保存它们,因为如果客户端使用的是正确识别客户端的代理,它们可能会有用),但最后一个右边始终是从外部建立到您的 ALB 连接的外部机器的地址。

这是您总是解释 X-Forwarded-For: 的方式——从 header 的底部开始,向上,然后从右到左,因为这是任何正常行为的代理(哪个 ALB是,在这种情况下)将处理它们——通过将其客户端的 IP 地址(从其网络堆栈报告)附加到右侧(或者通过在任何其他之后添加额外的 X-Forwarded-For header——要么方式在语义上是有效的,但 ALB 不使用后一种方法)。解析时,当您遇到第一个不是您自己的地址时,您会停下来——那是您唯一可以信任的地址。

此外,如果客户端在使用 http 连接时注入 X-Forwarded-Proto: https —— 试图欺骗已经与平衡器建立了安全连接,而实际上他们并没有建立平衡器 —— ALB 会丢弃它。实例只看到真相:X-Forwarded-Proto: http.


此外,您可能忽略了一个事实,即在使用 HTTP/1.1 连接到实例时,平衡器会做一些非常有用的事情:

You can use HTTP/2 with HTTPS listeners. You can send up to 128 requests in parallel using one HTTP/2 connection. The load balancer converts these to individual HTTP/1.1 requests and distributes them across the healthy targets in the target group using the round robin routing algorithm.

http://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html

据我估计,该功能将负载平衡的概念提升到一个全新的水平。


¹ 我所在机器的 IP 地址 运行 curl。 敏锐的观察者会注意到,这实际上是来自 RFC-5737,但它被报告给实例作为我在家里的 IP 地址。除了清理目的外,我没有以其他方式更改实例所见的请求 headers。否则,原来的顺序和内容被精确地保留在这里。