授权服务器宕机时,拦截还是放行?
一句话总结
ext_authz 是一个 HTTP 过滤器,它会针对每个请求向代理之外的授权服务器询问“能不能放行这个请求”。询问的方式有两种(HTTP、gRPC),而当授权服务器无法作答时,是阻断还是放行,由一行配置(failure_mode_allow)决定。
为什么需要它
本地速率限制和故障注入,Envoy 只看配置就能做出判断。授权则不然。“这个令牌的主人能不能查看这个订单”,只有了解用户数据库、权限表、组织策略的一方才能回答,而如果把这类判断以代码的形式放进每个服务,有多少个服务就会产生多少种不同的授权。只要有一个服务晚一点修改规则,这个服务就会成为漏洞。
于是出现了这样的结构:把判断集中在一处(授权服务器),代理扣住请求,向那台服务器询问。把 OPA 放在 Envoy 旁边的方式、设置内部授权服务的方式,全都使用这个过滤器。在 Istio 的 AuthorizationPolicy 中使用 action: CUSTOM 和 provider 时,istiod 会根据网格配置(extensionProviders 中的 envoyExtAuthzHttp、envoyExtAuthzGrpc),直接把这个过滤器放进 sidecar。
工作原理
HTTP 方式——Envoy 会以原请求的方法和路径,向授权服务器再发送一次请求。正文为空(Content-Length: 0),头则挑选后携带。默认携带的只有 Host、Method、Path、Content-Length、Authorization,其余的必须写在 allowed_headers 中才会传递。授权服务器返回 2xx 即为放行,否则其状态码和正文会原样发给客户端。
gRPC 方式——不重新发送请求,而是把请求的属性(来源、目的地、头、路径、TLS 信息)作为名为 CheckRequest 的 protobuf 传递。服务名称是 envoy.service.auth.v3.Authorization,方法是 Check。这一方式下,如果把 allowed_headers 留空,所有的头都会被传递。同一个过滤器,两种方式的默认值却相反,文档说明这是为了保持旧有行为的兼容性。更换授权服务器的同时更换方式,策略所依赖的头可能会悄悄消失。
头会向两个方向流动。
| 配置 | 何时 | 去向 |
|---|---|---|
allowed_upstream_headers |
放行 | 附加到上游请求(同名的会被覆盖) |
allowed_client_headers |
拒绝 | 附加到客户端响应 |
gRPC OkHttpResponse.headers |
放行 | 附加到上游请求 |
“覆盖”这一性质很重要。如果授权服务器指定了 x-user: alice,即使客户端在同一个头中写入 admin 发送过来,到达上游的仍然是 alice。如果上游信任这个头并据此判断权限,这一性质就是防止伪造的最后一道墙。
授权服务器无法作答时——连接失败、超时、5xx 都属于这种情况。
failure_mode_allow: false status_on_error(기본 403)를 돌려준다 ← 막는다(fail closed)
failure_mode_allow: true 요청을 그대로 보낸다 ← 흘린다(fail open)
+ failure_mode_allow_header_add: true 업스트림에 x-envoy-auth-failure-mode-allowed: true
统计数据累积在 http.<HCM stat_prefix>.ext_authz.<필터 stat_prefix>.(占位符依次为 HTTP 连接管理器的 stat_prefix 与过滤器的 stat_prefix)之下的 ok、denied、error、failure_mode_allowed 中。如果选择了放行,那么 failure_mode_allowed 上升的瞬间,就是请求在没有授权的情况下通过的瞬间。
可以按路径关闭。在路由的 typed_per_filter_config 中放入 ExtAuthzPerRoute 并设置 disabled: true,该路径就完全不会再询问授权服务器。用于健康检查、静态文件这类没有令牌的请求。
在现场相遇的样子
“部署授权服务器期间,所有服务都返回 403。”这是阻断一方的代价。授权服务器位于所有请求的路径上,所以它的可用性就等于服务的可用性。这就是为什么要把授权服务器作为 sidecar 放在代理旁边(OPA 的常见部署方式),或者部署多台并设置较短的 timeout。
“安全检查中发现了授权被关闭的时间段。”这是放行一方的代价。授权服务器挂掉的那几分钟里,所有请求都通过了,如果既没有标记头,也没有统计告警,这件事要等到以后翻日志才会暴露。
“改成 gRPC 之后,所有请求都通过了。”这是因为授权集群没有启用 HTTP/2,每次连接都失败,而又是放行一方的配置,于是悄悄通过了。统计中的 error 会按请求数上升。
官方文档:External Authorization filter · ext_authz API · Authorization service API · Istio External Authorization
下一项实验要做什么
亲手编写 HTTP 版和 gRPC 版授权服务器各一个,并启动分别向它们询问的两台 Envoy。通过服务器日志确认伪造的身份头在上游会变成什么,以及两种方式传给授权服务器的头有何不同。只为健康检查路径关闭授权,最后把两个授权服务器都停掉,测量阻断一方和放行一方实际会给出什么响应。