Istio Ambient Mesh 中 PeerAuthentication 的实现:从 ztunnel 转换机制到 Waypoint 信任边界
本文解析 Istio Ambient Mesh 中 PeerAuthentication 资源的工作原理:与 sidecar 模式不同,Ambient 工作负载的 mTLS 策略并不直接下发给 ztunnel,而是由 istiod 按“网格级 → 命名空间级 → 工作负载级”的优先级规则计算出生效策略,再转换为 ztunnel 自定义的 Authorization xDS 资源进行 L4 层强制执行。读完本文,你将掌握 PeerAuthentication 在 ztunnel 与 Waypoint 代理两条链路上的完整转换逻辑、DENY 策略的组/规则/匹配语义,以及可直接复现验证的测试用例入口。
背景:Ambient 下 PeerAuthentication 语义为何不同
PeerAuthentication 资源定义了流量到达某个网格工作负载时是否必须通过 Istio mTLS 加密传输。Sidecar 模式下其语义相对明确,但 Ambient 工作负载的架构差异——节点级 L4 代理 ztunnel 取代了容器内 sidecar——决定了策略的执行方式必须相应调整:
- ztunnel 的目标是成为一个最小化 L4 代理,其 xDS 配置被刻意精简;
- 策略不能在 ztunnel 内部做复杂的继承计算,因此计算工作前移到控制面(istiod);
- 结果是 ztunnel 收到的永远是一份“已经算好”的、按工作负载粒度裁剪的最终策略。
PeerAuthentication 与 ztunnel
ztunnel 只消费两种 xDS 资源
ztunnel 仅支持两种(自定义)xDS 资源:
因此 ztunnel 不会直接收到 PeerAuthentication。当 istiod 检测到一条指向 Ambient 捕获工作负载的 PeerAuthentication 时,它会按 mesh-wide → namespace → workload 的优先级规则计算该工作负载的生效策略,并把该策略发送(以 Authorization 形式)给 ztunnel。
以文档给出的经典例子:这条 PeerAuthentication 要求选中 app: a 的工作负载整体 STRICT,仅 9090 端口例外为 PERMISSIVE:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: strict-and-permissive-mtls
spec:
selector:
matchLabels:
app: a
mtls:
mode: STRICT
portLevelMtls:
9090:
mode: PERMISSIVE
它会被转换成如下的 Authorization:
action: DENY
groups:
- rules:
- matches:
- notPrincipals:
- presence: {}
- rules:
- matches:
- notDestinationPorts:
- 9090
name: converted_peer_authentication_strict-and-permissive-mtls
scope: WORKLOAD_SELECTOR
上述策略的语义是:在 ztunnel 处拒绝未认证流量,除非其目的地端口是 9090。
结合源码理解转换:convertPeerAuthentication
上面这个转换并非“理论描述”,仓库中有一个对应的完整黄金测试文件:输入 peer-authn-strict-and-permissive-port-mtls-in.yaml 恰好就是上文那条 STRICT + 9090 PERMISSIVE 的策略,而期望输出 peer-authn-strict-and-permissive-port-mtls.yaml 与文档给出的 Authorization 逐字段一致,这直接印证了转换行为的确定性。
转换的核心实现位于 authorization.go 的 convertPeerAuthentication 函数,从源码结构看,其关键设计如下:
-
只有“工作负载级 + 带端口级 mTLS”的策略才会产出转换结果。函数开头就检查:如果策略位于根命名空间、没有
selector、或者没有任何portLevelMtls,直接返回 nil(这类策略等价于纯 STRICT 或纯 PERMISSIVE,不需要逐端口表达):// violates case #1, #2, or #3 if cfg.Namespace == rootNamespace || pa.Selector == nil || len(pa.PortLevelMtls) == 0 { return nil } -
STRICT 顶层模式 → “无 principal 即拒绝”规则。
mode == STRICT时,转换结果首先加入一条基于notPrincipals: [presence]的匹配——即:连接的 TLS 证书中若不存在 principal(未认证),命中 DENY。 -
非 STRICT 的端口 → 端口豁免规则。对于
portLevelMtls中 PERMISSIVE/DISABLE 的端口,在顶层为 STRICT 的前提下,追加notDestinationPorts: [port]规则——连接目的地不是该端口时拒绝,从而把 9090 这类端口豁免出强制 mTLS。多个 STRICT 端口则各自生成独立的 Group(Group 之间是 OR 关系),实现“这些端口同样要求认证”。 -
全 STRICT 策略提前短路。如果顶层是 STRICT 且所有端口级也都是 STRICT,函数返回 nil:这等价于一条纯 STRICT 策略,由 xDS 服务器直接推送静态 STRICT 策略(常量
istio_converted_static_strict,见 authorization.go#L37-L39),无需逐端口展开。 -
命名规范:转换后的策略命名为
converted_peer_authentication_<原策略名>(通过model.GetAmbientPolicyConfigName生成),scope固定为WORKLOAD_SELECTOR,action固定为DENY。
Authorization 的匹配语义
转换产物的字段语义由 authorization.proto 定义,理解它对读懂转换结果至关重要:
| 层级 | 逻辑关系 | 说明 |
|---|---|---|
groups 之间 |
OR | 至少一个 group 命中,策略动作生效 |
group.rules 之间 |
AND | 组内所有规则必须同时命中 |
rule.matches 之间 |
AND | 规则内所有条件必须同时成立 |
同一 Match 中多字段 |
AND | 多类型字段同时设置时须全部满足 |
| 同字段多值 | OR | 如多个 principal 匹配任一即可 |
Match 支持 namespaces / principals / source_ips / destination_ips / destination_ports 及其 not_ 反义形式,字符串匹配支持精确、前缀、后缀与 presence(存在性)四种类型。scope 分为 GLOBAL、NAMESPACE、WORKLOAD_SELECTOR 三级;PeerAuthentication 转换结果固定落在 WORKLOAD_SELECTOR,因为它的 selector 绑定了具体工作负载标签。
优先级解析与静态 STRICT 策略
“mesh → namespace → workload”的继承计算集中在 authorization.go 的 convertedSelectorPeerAuthentications 函数中,从源码结构看有几个值得注意的规则:
- 同级冲突时最旧者获胜:
getOldestPeerAuthn按CreationTimestamp比较,同一层级存在多条无 selector 策略时,创建时间最早的生效(并有Switch selected mesh policy to ...的 Debug 日志)。 - 工作负载策略只在非根命名空间生效:位于根(系统)命名空间且带 selector 的 PeerAuthentication 被忽略。
- 生效策略是 STRICT 时引用静态策略:
effectivePeerAuthenticationKeys会把istio-system/istio_converted_static_strict加入该工作负载的策略键集合;而一旦工作负载策略中出现了需要端口例外的混合模式(如 STRICT + 9090 PERMISSIVE),静态 STRICT 策略会被剔除,改发转换后的完整策略——避免“先整体拒绝、再端口豁免”的双重下发。 - 合并(merge)逻辑:当根/命名空间策略为 STRICT、工作负载策略为非 STRICT 顶层但带有 STRICT 端口时,转换会把父级的 STRICT 语义合并进工作负载策略(
shouldMergeStrict分支),使下发的单条Authorization自包含全部约束。
用测试用例验证转换行为
文档指引读者直接阅读 TestRBACConvert 的测试用例(ambientindex_test.go#L1751)。该测试遍历 testdata 目录下所有 *-in.yaml 文件,调用 convertPeerAuthentication 后与同名黄金文件比对。目录中有 20 余组 PeerAuthentication 用例,覆盖了完整的模式组合矩阵,例如:
| 输入文件(输入策略) | 覆盖场景 |
|---|---|
| peer-authn-strict-in.yaml | 纯 STRICT 工作负载策略(期望输出为空,走静态策略) |
| peer-authn-strict-and-permissive-port-mtls-in.yaml | STRICT + 9090 PERMISSIVE 端口豁免(即文档主例) |
| peer-authn-strict-and-disable-port-mtls-in.yaml | STRICT + DISABLE 端口,期望输出与 PERMISSIVE 端口相同 |
| peer-authn-strict-and-strict-port-mtls-in.yaml | 顶层与端口全 STRICT,等价短路 |
| peer-authn-permissive-port-mtls-strict-in.yaml | 顶层 PERMISSIVE 下的单个 STRICT 端口 |
| peer-authn-permissive-root-strict-namespace-permissive-workload-in.yaml | 根 STRICT、命名空间 PERMISSIVE、工作负载端口的多级合并 |
这些黄金文件可直接作为“策略 → L4 授权”映射的规范参考。
PeerAuthentication 与 Waypoint 代理
适用前提(原文档的明确标注):本节描述的 Waypoint 联动行为在文档写作时尚未完全实现,且依赖于 ztunnel hairpinning 的设计讨论。以下内容反映的是该机制的目标设计。
关键在于 ztunnel 转发时机:ztunnel 先在其 L4 层应用 TRANSPORT 类策略(即 Authorization),之后才把流量转发给 Waypoint 代理。由此产生两类行为:
- 目标工作负载的生效策略达到 STRICT 时:未认证流量会在到达 Waypoint 之前就被 ztunnel 拒绝——这正是上文 DENY 策略的落点;
- 生效策略为 PERMISSIVE(默认值)时:ztunnel 对 Waypoint 打开一条普通 TLS 的 HBONE 隧道(注意:这不是 mTLS),并在不呈递客户端证书的情况下转发流量。
这引出 Ambient 安全模型中最重要的一条约束:Waypoint 代理绝不能从入站连接中推导任何身份,即使该连接来自 ztunnel 的 hairpin 路径。换言之,所有走 TLS HBONE 隧道的流量都必须视为不可信;此后流量仍经由 ztunnel 返回(依然在同一条 TLS HBONE 隧道上)并被转发到目的工作负载。
PERMISSIVE 策略下未认证流量的路径
graph TD;
src[src pod]-->|plaintext port|ztunnel{"ztunnel (L4 policy applied here)"}
ztunnel{ztunnel}-->|TLS|wp{waypoint}
wp-->|mTLS|ztunnel
ztunnel-->|plaintext|dst[dst pod]
即:源 pod 以明文到 ztunnel 的本地端口;ztunnel 在此处应用 L4 策略;ztunnel 以 TLS(非 mTLS)到 waypoint;waypoint 返回;ztunnel 以明文送达到目的 pod。
认证请求到达被捕获目的端的路径
graph TD;
src[src pod]-->|15008|ztunnel{ztunnel}
ztunnel-->|HBONE|dwp{"destination waypoint (all policy applied here)"}
dwp{destination waypoint}-->|15008|dztunnel{destination ztunnel}
dztunnel-->|host network|dst[dst pod]
认证流量则经由 15008 端口的 HBONE mTLS 隧道直达目标 Waypoint,由 Waypoint 应用全部策略(包括 L7 策略),再经目的端 ztunnel 走宿主网络到达工作负载。
与 L4 AuthorizationPolicy 的边界
PeerAuthentication 转换只是 Authorization 资源的一个来源。同一个 convertAuthorizationPolicy 所在的 convertAuthorizationPolicy 函数还负责把 AuthorizationPolicy 的 L4 字段(IP、端口、principal、命名空间、ServiceAccount 及 when 条件中的 L4 属性,见 l4WhenAttributes)转换为 Authorization;若策略包含 HTTP 层属性,istiod 会剥离这些字段并写入“ztunnel 不支持 HTTP 属性,需部署 Waypoint 代理执行”的 Status 条件(httpRuleFmt 等模板)。这与本文主题一脉相承:Ambient 中 L4 强制在 ztunnel、L7 强制在 Waypoint,PeerAuthentication 作为纯传输层策略,天然完整落在 ztunnel 一侧。
小结
- ztunnel 不消费 PeerAuthentication;istiod 计算生效策略后下发转换好的
Authorization(DENY+WORKLOAD_SELECTOR),规则是 groups OR、rules AND、matches AND; - STRICT 顶层模式编译为“无 principal 即拒绝”,非 STRICT 端口编译为
notDestinationPorts豁免,全 STRICT 策略短路为静态策略istio_converted_static_strict; - 同级策略冲突以
CreationTimestamp最旧者获胜;多级继承的合并逻辑与 20 余组黄金文件测试一一对应,可通过TestRBACConvert复现验证; - 在 Waypoint 路径上,ztunnel 先执行 L4 策略再转发;PERMISSIVE 下走的是无客户端证书的普通 TLS HBONE 隧道,因此 Waypoint 必须将一切隧道流量视为不可信、绝不推导身份。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00