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支持呢?经过一些研究,所有可能的选择看起来都不令人满意:
当前架构无效,因为SSL/TLS在CLB中终止,但CLB不提供任何选项announce H2 support through ALPN。
另一种使用 CLB 的方法是停止使用 SSL/TLS 卸载功能并将其移至 EC2 实例(即 TCP:443 ➝ TCP:443)。这样 H2 支持可以在 SSL/TLS 握手期间宣布,但此选项需要升级 EC2 实例以支持额外的 SSL/TSL 工作负载。相似替代品:
- TCP:443 ➝ SSL:443:类似于 TCP:443 ➝ TCP:443 但允许使用可信任 [= =86=] 密钥证书。
- SSL:443➝SSL:443:end-to-end加密类似于TCP:443➝SSL:443。不是真正的选择:(1) PROXY 协议是 not supported for this combination(并且使用 XFF 也不是一个选择,因为这是 transport-level 平衡); (2) 客户端 SSL/TLS 握手在 CLB 中执行,因此 H2 不会被宣布。
其他选项是用 ELB 替换 CLB (HTTPS ➝ HTTP)。 ELB 支持 H2。但是(1)我们需要依靠 XFF 来提取客户端 IP 地址(已经解释了为什么这是一个问题); (2) ELB 和 EC2 实例之间的流量将是 H1(我们想让未加密的 H2 流量到达 EC2 实例)。换句话说,这不是一个选项。
综上所述,所有选项都是有问题的。恕我直言,理想的解决方案是保留原始 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。否则,原来的顺序和内容被精确地保留在这里。
目标是在一个简单的堆栈中包含 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支持呢?经过一些研究,所有可能的选择看起来都不令人满意:
当前架构无效,因为SSL/TLS在CLB中终止,但CLB不提供任何选项announce H2 support through ALPN。
另一种使用 CLB 的方法是停止使用 SSL/TLS 卸载功能并将其移至 EC2 实例(即 TCP:443 ➝ TCP:443)。这样 H2 支持可以在 SSL/TLS 握手期间宣布,但此选项需要升级 EC2 实例以支持额外的 SSL/TSL 工作负载。相似替代品:
- TCP:443 ➝ SSL:443:类似于 TCP:443 ➝ TCP:443 但允许使用可信任 [= =86=] 密钥证书。
- SSL:443➝SSL:443:end-to-end加密类似于TCP:443➝SSL:443。不是真正的选择:(1) PROXY 协议是 not supported for this combination(并且使用 XFF 也不是一个选择,因为这是 transport-level 平衡); (2) 客户端 SSL/TLS 握手在 CLB 中执行,因此 H2 不会被宣布。
其他选项是用 ELB 替换 CLB (HTTPS ➝ HTTP)。 ELB 支持 H2。但是(1)我们需要依靠 XFF 来提取客户端 IP 地址(已经解释了为什么这是一个问题); (2) ELB 和 EC2 实例之间的流量将是 H1(我们想让未加密的 H2 流量到达 EC2 实例)。换句话说,这不是一个选项。
综上所述,所有选项都是有问题的。恕我直言,理想的解决方案是保留原始 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。否则,原来的顺序和内容被精确地保留在这里。