内部环境中 H2C(未加密的 HTTP/2)迁移指南

292 阅读4分钟

内部环境中 H2C(未加密的 HTTP/2)迁移指南

这篇博客记录了在将 HTTP1.1 服务迁移到 HTTP/2 过程中进行的一些调查。背景是使用 HTTP/2 进行内部通信将显著提高效率。为了实现这一点,h2c(未加密的 HTTP/2)方案更好,因为它防止了对等方之间的加密和解密。

为什么选择 H2C

对于在主机级代理实例和容器内部服务之间的内部通信,由于多路复用功能,HTTP/2 比传统的 HTTP1.1 有很大改进。

此外,在内部集群中,通常认为内部系统是安全的,TLS 提供的安全性有些多余。因此,要求 TLS 的 h2 解决方案不是内部使用的理想方式。因此,我们选择了 h2c 作为内部服务的解决方案。

HTTP/2 协议协商

HTTP/2 有两种协议协商方式,分别是 h2h2c

  • h2: 基于 TLS 的加密 HTTP/2 协商,称为 ALPN(应用层协议协商)
  • h2c: 先验知识(rfc7540#3.4)或升级机制(rfc7230#6.7

ALPN 在 TLS 连接握手期间完成,这不是本文的主题。

先验知识

如果客户端(代理)已经知道服务器支持 HTTP/2,则可以直接发送 HTTP/2 请求。这就是“先验知识”的含义。那么客户端如何知道这一点呢

在内部环境中,服务路由通过服务注册-发现机制完成,这为支持 h2c 先验知识提供了可能性。对于代理,当它将请求转发到目的地时,可以从服务注册表中查询对等方是否支持 HTTP/2,如果支持,则通过 h2c 发送 HTTP/2 请求。

http2_prior_knowledge.png

升级机制

客户端通过 HTTP1.1 发送请求,并附带诸如 Upgrade: h2c 之类的升级头部到服务器,服务器可能支持 HTTP/2。

如果服务器支持 HTTP/2,它可能支持从 HTTP1.1 到 HTTP/2 的升级功能。要升级,服务器将发送一个带有 101(切换协议) 的响应,然后跟随 HTTP/2 格式的数据包。

http2_prior_knowledge.png 注意,HTTP/2 服务器不一定支持此功能,正如 rfc7230 第 6.7 节 所述:

如果服务器希望继续使用当前协议,则可以忽略接收到的升级头字段。

升级听起来很棒,但实际上在实现和集成过程中引入了很大的麻烦。稍后我会详细说明。

在 Go 语言中集成 H2C

Go 提供了 golang.org/x/net/http2… 包来支持 h2c。 它支持在协议协商期间的先验知识和升级机制功能。如果这些条件都不满足,则使用 HTTP1.1 作为回退。使用非常简单,只需将您的 HTTP 处理程序放入 HTTP/2 服务器中,然后该库将帮助您进行协议协商。

注意,HTTP/2 和 HTTP1.1 服务器监听同一个端口,并根据客户端发送的请求选择协议。

目前,h2c 库中支持 先验知识升级机制。对于我们的内部使用,升级功能无用且存在潜在问题,如 http2 smuggle。我向 h2c 包提交了一个 PR

升级机制已被弃用

RFC 从 rfc9113 开始弃用了升级机制,内容如下:

"h2c" 字符串以前用于 HTTP 升级机制的 Upgrade 头字段([HTTP 的第 7.8 节])。这种用法从未被广泛部署,因此本文件已弃用该用法。HTTP2-Settings 头字段的情况也是如此,该头字段用于升级到 "h2c"。

由于“从未被广泛部署”,RFC 将其弃用。基于此,Go 团队决定在标准库中仅支持 先验知识h2c

H2C 升级机制中的问题

H2C 的主要问题集中在升级功能上,无论是集成还是实现方面。这里,我将逐一介绍这些问题。实现困难通过 GitHub 问题进行了总结。

HTTP/2 Smuggle

HTTP/2 Smuggle 发生在代理情况下,允许攻击者通过 HTTP/2 升级访问未经授权的资源。

http2_smuggle.png 设想 api/normal 可以被任何用户访问,但 api/flag 仅限于授权用户访问。访问控制通过例如 Nginx 的代理完成。注意,代理必须也支持 HTTP/2。HTTP/2 Smuggle 的过程描述如下:

  1. 客户端向代理发送带有升级头的请求
  2. 代理检查访问权限,并盲目地将其转发给服务器
  3. 服务器发现这是一个带有升级头的 HTTP1.1 请求,因此发送带有 HTTP/2 负载的 101 响应码
  4. 代理转发响应并转发负载
  5. 建立了一个长连接
  6. 客户端直接请求未经授权的资源如 api/flag,绕过了代理的限制

HTTP/2 Smuggle 问题发生在几个著名的云提供商如 Azure 和 Amazon 中。最终的解决方案是停止在代理期间转发 升级 头。

实现问题

TODO: 应该在检查 go h2c 的实现后修复。