一次报价该在哪一层检查租户

同一个 orders → inventory-v1 的 GET /quote?sku=paper,假设实验需要仅 x-tenant: lab 可访问。把租户判断塞进每个库存副本,会让规则变更与应用发布耦合;把检查放进代理又必须说明请求的身份来源、超时以及授权服务不可用时如何处理。本文选择 Envoy HTTP ext_authz,只解决这一个检查需求;x-tenant 在本例只是合成输入,不是可信身份,真实租户属性必须绑定到已认证来源且过滤不可由外部随意伪造的头。

Envoy 在转发库存之前向外部授权服务询问,拒绝时不触及库存

图中外部授权是 Envoy HTTP 过滤链里的一步。LDS 中 listener 使用 HTTP connection manager,过滤器按次序调用授权服务;允许才继续到 router 的 RDS/CDS/EDS 路径(先修 03 Envoy)。控制面能部署规则,但“ext_authz 已加载”不等于目标请求确实经过这个 listener;负例必须在库存应用日志中找不到相同请求 ID。

扩展机制并非一种执行环境

Envoy 自带的 HTTP/network filter、Lua、Wasm 模块与 ext_authz 调外部进程是不同机制。Wasm 提供可在代理环境里运行扩展逻辑的沙箱/ABI,受宿主暴露能力、模块兼容性、内存/CPU 限额影响;ext_authz 则把决策交给独立 gRPC/HTTP 服务,代理对服务的往返时延、并发和可用性有依赖。这里不叠加 Wasm 检查同一个头:双重决策会让放行/拒绝归因困难,也增加两个独立升级面。升级 Envoy 时应重新核验 filter API 和扩展兼容;升级远端授权服务时应核验协议/超时语义及兼容窗口。

一次合成用例设允许的 x-tenant: lab 返回允许,x-tenant: other 返回拒绝。设定明确的授权请求超时(例如示意 200ms,不是本仓库配置或实测),用受限资源的授权进程做慢响应和直接断连两种负例。failure_mode_allow=false 时服务故障/超时应按文档中的错误路径拒绝;开启时故障才允许继续,而策略明确拒绝不应被“故障放行”改成允许。观察授权结果、超时/错误统计、代理与授权服务的资源水位、客户端响应以及库存处理日志;负载饱和时系统总排队时间可能超过 filter 自身超时。短超时、连接并发、授权服务容量、熔断和可观测性要共同配置,不能以打开故障放行掩盖持续故障。

合成输入及处理位置(不代表当前部署中信任 x-tenant):

1
2
GET /quote?sku=paper HTTP/1.1
x-tenant: lab
授权服务结果 故障放行关闭 故障放行开启
明确允许 转发库存 转发库存
明确拒绝 拦截 仍应拦截
超时/不可用 按错误策略拒绝 可能绕过检查并转发,须报警

先修 21 授权 的身份前提仍适用。官方 Envoy 1.37 ext_authz 配置 和 API 给出过滤器参数。工程已提供授权服务和真实代理驱动,运行状态以输出目录 status.txt 为准;未执行的代理路径仍为 NOT_RUN。复跑时保存版本、二进制哈希及相同请求 ID 的代理/授权/库存记录,静态配置不是运行证据。

从授权服务到真实过滤链

examples/service-mesh/electives/E05/authz.py 是独立 HTTP 授权服务,只使用 Python 标准库。lab.py 生成 Envoy 原生 JSON 配置并驱动真实 Envoy;它没有实现替代代理。JSON 与 YAML 都是 Envoy 支持的配置形式,用 JSON 可以避免增加生成依赖。

服务按 HTTP ext_authz 协议返回状态:200 允许,403 明确拒绝,503 表示授权服务故障。请求正文不参与决策,响应长度为零。x-lab-mode: slow 注入 500ms 延迟,error 返回 503。这些仅用于环回地址上的实验端口,不能公开给客户端作为可控故障开关。

工程将检查超时固定为 100ms,上文的 200ms 只是说明。关键配置如下;完整 listener、route、cluster 和 access log 由 config() 生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
failure_mode_allow: false
failure_mode_allow_header_add: true
allowed_headers:
patterns:
- exact: x-tenant
- exact: x-lab-mode
- exact: x-request-id
http_service:
server_uri:
uri: http://authz
cluster: authz
timeout: 0.1s

HTTP 模式不会默认转发任意自定义头,因此三个实验头必须显式允许。若遗漏 x-tenant,授权服务只看见缺失值,会按本例规则拒绝;这不是身份系统失效。实际 Envoy 1.37.0 的 HTTP 客户端在错误对象中显式填入 403,过滤器优先使用它,因此不能假设 status_on_error: 503 会覆盖该路径。策略拒绝与调用错误都可返回 403,须区分 ext_authz_denied 与 ext_authz_error,并核对后端是否收到请求。

过滤链顺序为 ext_authz → router。只有授权允许或故障放行时,router 才调用库存。测试库存返回固定只读报价,保存实际收到的请求 ID;它用于验证拦截时点,不替代累计工程的订单行为。授权 deny 后库存出现相同 ID,就是验证失败。

复跑两种故障策略

在包含 Python 3 和真实 Envoy 1.37.0 的隔离环境,从 examples/service-mesh 执行:

1
ENVOY_BIN=/path/to/envoy SM_EVIDENCE_DIR=/tmp/service-mesh-E05 bash scripts/run-E05.sh

驱动记录真实二进制版本与 SHA-256,先执行 --mode validate,再启动两个独立代理。每个代理均测试允许、明确拒绝、超时和授权端 503;之后停止授权进程,验证连接失败。端口动态分配且只绑定环回地址,退出时回收自己启动的进程。容器执行需要把代理和 Python 服务放在同一网络命名空间;Mac 宿主的 127.0.0.1 与 Podman VM 的环回地址不能混用。

输入 故障关闭 故障放行 库存应收到 ID
lab、正常 200 200 是
other、正常 403 403 否
lab、500ms 慢响应 403 200 仅故障放行
lab、授权端 503 403 200 仅故障放行
授权进程停止 403 200 仅故障放行

表中给出驱动断言;本轮实际结果及边界见下节。requests.json 保存预期、实际响应和库存到达标记;inventory-ids.json 保存库存接收记录;Envoy 日志包含响应详情,stats-*.txt 保存 filter 计数器。只有全部响应、代理 response details 和后端到达断言满足时才写 PASS。找不到真实 Envoy 则退出 77、写 NOT_RUN,不会把授权服务自身测试冒充代理验收。

等待时间与并发是两种上限

等待外部授权时,请求仍占用代理流状态和相关缓冲。100ms 超时限制一次检查等待时间,却不是系统总容量:持续过载仍会产生大量并发检查。示例用容量 8 的 semaphore 限制同时执行的授权决策,超出返回 503;它不限制底层 HTTP 服务器创建的线程总数,因此只是决策并发边界,不是生产级连接隔离。

容量对照应向授权端并发发送 20 个慢请求,检查至少一个得到 503,等待慢请求结束后再验证普通 lab 请求恢复 200。代理路径还需观察授权 cluster 的 pending/active 请求、超时计数和进程 CPU/内存。不能把 semaphore 的单测写成代理资源上限已经实测。

授权服务升级要保持响应协议和策略兼容;Envoy 升级还要复跑头转发、超时分类与过滤器排序。Wasm 模块另有宿主 ABI 和运行时兼容要求,适用于确实需要代理内执行的逻辑。本例没有这个需求,因此没有增加空的 Wasm 模块。

需求关键词 可迁移原则 本例执行点
拒绝后仍执行 副作用之前核对拦截 router 前 ext_authz 与请求 ID
决策服务故障 区分拒绝与无法决策 403、503、failure_mode_allow
检查变慢 同时控制时间和资源 100ms 超时与决策容量 8

实跑先发现超时返回 403、详情为 ext_authz_error,与原先预期 503 不同。HTTP 客户端固定源码 的 errorResponse() 和 过滤器固定源码 的错误分支解释了这个优先级。工程改为明确使用 403,并额外断言错误分类,避免把正常拒绝误当超时检查通过。

本轮真实代理结果

Linux/arm64、Envoy 1.37.0 的完整驱动已通过,原始目录 examples/service-mesh/evidence/E05/20261003T091341Z-2/ 包含版本、配置验证、请求、后端 ID、代理日志、计数器和 status.txt。使用的 Envoy 镜像 digest 为 sha256:f9e63fffcc6e831547cbd1690d50c85f864d40348a5223ceee0af96037c4d506;Go 服务编译环境为 Go 1.25.1。E05 同时核对 allow/deny、超时、授权端 5xx、服务不可用与重启恢复,且分别验证故障放行和拒绝。403 的拒绝/错误分类已用真实 response details 区分。

这些结果限定于本例单机环回网络、固定过滤链和合成输入。它们不证明多机高可用、持久额度、生产身份认证或吞吐上限。

练习与参考解答

  1. 画出授权服务返回 deny 时 external-stub 能否收到调用。参考解答:若过滤器确实覆盖 orders → inventory,Envoy 在转发库存前终止该库存请求;orders 是否继续调用 external-stub 由其应用逻辑决定,现有工程只在库存成功后调用,因此不应出现本次后续外部调用。须以两个应用的请求 ID 核对。
  2. 开启 failure_mode_allow 后,所有 x-tenant: other 都放行了吗?参考解答:没有。授权服务正常返回拒绝仍是拒绝;只有调用失败/超时按配置可能被放过,此时安全边界下降,应报警并限制暴露范围。

资料与系列导航

Envoy 1.37 ext_authz、Wasm 扩展;源码入口 Envoy v1.37.0 ext_authz。研究记录 writing-plans/service-mesh/research/E05.md。

系列导航:E04 VM 接入 → E05 外部授权(本文) → E06 全局配额。