注册一个 proxy 传输
和其他传输一样,proxy 传输有一个id 和一个值为 proxy 的 type,此外还有一个嵌套的 proxy 块,用于指定上游目标:
网络类型
tcp—— 上游监听在某个主机和端口上,address就是对应的host:port。适用于本身已经绑定了 TCP 端口的上游。unix—— 上游监听在一个 Unix 域套接字上,address是该套接字的文件系统路径。这样可以避免为只需要到达内核的流量暴露一个 TCP 端口。inherited—— 仅适用于grpc服务。在内核启动该服务的进程之前,它会先准备好一个监听套接字,并以已打开的状态交给该服务,这样服务自己的服务器就可以立即开始接受连接,而不必和内核竞争绑定某个端口,也不需要协调端口号。一个继承的监听器可以承载该服务的任意数量的 proxy 路由。
unix 和 tcp 目标指向的是已经在别处监听的地址,因此它们也可以在 static 服务的 manifest.yaml 中声明——参阅 Static 服务。inherited 目标只对内核正在主动启动的 grpc 服务才有意义。
为什么对 grpc 服务而言 inherited 是更安全的默认选择
对于 grpc 服务,相比让服务自己绑定一个 unix 或 tcp 监听器、再让某个传输指向它,更推荐使用 inherited。因为内核会在服务进程存在之前就自己创建好监听器,然后才把它以已打开的状态交给该进程,服务自行绑定监听器所带来的几种风险都不复存在:
- 没有任何东西能抢先于服务绑定。 使用
unix或tcp目标时,服务必须在自己启动之后再自行绑定该地址,这就留下了一个窗口期,另一个本地进程有可能抢先绑定同一个路径或端口,从而拦截本应发给该服务的流量。使用inherited时,内核在服务进程甚至还没启动之前就已经持有该监听器,因此不存在其他进程可以抢占的窗口期。 - 从一开始就是收紧的权限。 内核准备的监听器所在的目录权限为
0700,监听器本身权限为0600。在把它交出去之前,只有内核自己的进程用户能够访问它,因此它不会像服务自行创建的套接字那样,因为初始化代码使用了较宽松的默认权限,而短暂地对同组用户或所有人可见。 - 服务永远不需要绑定权限。 服务接收到的是一个已经打开的监听器,而不是自己创建一个,因此它不需要绑定端口或创建套接字文件的权限。这在使用
RunAsUser让服务以受限的操作系统用户身份运行时尤其重要——该用户不需要任何特殊的网络权限,即使服务被攻破,也无法借此打开其他非预期的监听器。 - 保证到达正确的进程。 因为内核创建了该监听器,并直接把它交给了自己刚启动的那个特定子进程,所以到达该监听器的流量必然会到达那个确切的服务实例。这里不存在需要查找的地址,也不可能连接到某个碰巧抢先绑定了同一路径的冒充进程。
- 自动清理。 服务启动后,内核会关闭自己持有的引用;服务卸载时,内核也会移除该监听器,因此一个已停止的服务不会留下存活或残留的端点供其他进程发现或复用。
unix 和 tcp,同等程度的信任需要靠您自己的部署方式来保证:将 unix 套接字路径放在只有目标用户才能写入的目录中,并将 tcp 目标绑定在 localhost 或私有网络上,而不是可公开访问的接口上。这些做法仍然是合理的选择——对于 static 服务固定的上游而言,它们也是唯一的选择——但对于内核自己启动的 grpc 服务来说,inherited 省去了把这个部署细节做对的负担。
绑定一条路由
绑定到 proxy 传输的路由,携带的是与其他任何 HTTP 路由相同的声明——pattern 和 access,具体说明见路由与传输。proxy 路由通常使用以斜杠结尾的模式覆盖整个子树,让该前缀下的所有路径都能到达上游。对于 proxy 路由而言,根模式 / 始终被当作子树处理,可以匹配所有绝对路径,因此如果服务拥有根路径,就可以用一条路由代理整个应用。
内核保留和注入的内容
该代理会转发端到端的请求头,包括Cookie 和 Authorization,并保留原始的请求路径、查询字符串和 Host 请求头。逐跳(hop-by-hop)请求头按反向代理的常规方式处理。
在转发之前,内核总是会移除调用方提供的任何身份标识请求头,并注入自己经过验证的版本:
X-Arupa-AuthenticatedX-Arupa-User- 用户所属的每个组各对应一个
X-Arupa-Group请求头
访问控制
访问权限的评估方式与任何 HTTP 路由相同:先检查服务级策略,再检查路由自己的access 策略。关于策略模型,请参阅访问控制。内核会在代理请求之前检查访问权限,因此被拒绝的请求永远不会到达上游。
冲突
proxy 路由遵循路由与传输中描述的常规 HTTP 路由冲突规则:同一个路径不能被两个服务拥有,一个接受任意方法的路由会与同一路径下所有指定了具体方法的路由发生冲突。proxy 路由不是static 的,因此不会触发 static 与非 static 的冲突规则——它可以和一个 HTTP 传输的路由共享同一片路径空间,就像两条被动态处理的路由那样,仍然遵循常规的方法规则。