前几篇讲的都是「怎么发现、怎么路由、怎么挂负载均衡策略」,全部落在
CDS/EDS/RDS 三类资源上。PeerAuthentication 和
AuthorizationPolicy 不一样:它们基本不碰
CDS/RDS(第 6
篇 提过 RdsGenerator 的
skippedRdsConfigs 里就有
PeerAuthentication/AuthorizationPolicy/RequestAuthentication),真正的落点是入站
Listener 的 filter chain——mTLS 要求变成 filter
chain 的传输层配置,授权规则变成插进 HTTP filter 链的
envoy.filters.http.rbac。证书本身怎么发出来、怎么喂给
Envoy,是另一条独立通道:SDS。本文只钉这条翻译与分发链路,策略语义(什么时候该用
STRICT、零信任模型怎么落地)不重复讲,见 零信任系列。
本文是「Istio / 网格控制面内核」系列第 8 篇。→ 系列目录
上接 第 7 篇:DestinationRule;下接 第 9 篇:Sidecar/Telemetry/EnvoyFilter。
版本锚定:Istio 1.30.3,源码路径对应
pilot/pkg/security/authz/、security/pkg/pki/ca/、security/pkg/nodeagent/(函数签名以 master 分支验证,1.30.x 语义一致)。
一、两个策略资源,两种落点
| 资源 | 落点 | 生成路径 |
|---|---|---|
PeerAuthentication |
入站 Listener 的 downstream TLS 要求(是否强制 mTLS) | 影响 LDS 侧 filter chain 的 transport socket,不是 CDS |
AuthorizationPolicy |
HTTP/网络 filter 链里的
envoy.filters.http.rbac(或 TCP 等价物) |
pilot/pkg/security/authz/builder |
(对照)DestinationRule.tls |
出站 Cluster 的 client TLS(第 7 篇) | CDS |
这张表的重点是方向:PeerAuthentication
管「我这个 workload
接受什么样的入站连接」,DestinationRule
管「我作为客户端怎么发出去」——第 7
篇第五节
已经指出过这一对配置分居两端、容易被当成一个东西调。AuthorizationPolicy
则是在
mTLS/身份已经建立之后再做一层「这个身份能不能访问这个资源」的判断。
二、PeerAuthentication:mTLS 模式与作用域优先级
MutualTLS.Mode
有四档:UNSET(继承父级,若无父级则视为
PERMISSIVE)、DISABLE、PERMISSIVE(同时接受
mTLS 与明文)、STRICT(只接受
mTLS)。作用域优先级:workload 级(带 selector)
> namespace 级 > mesh 级,UNSET
时逐级向上找第一个显式设置的值。
portLevelMtls
有两条容易踩的边界,官方文档明确写出:
- 只有带
selector的 workload 级策略才能用portLevelMtls——namespace 级策略只能设一个统一默认,不能做端口例外。 portLevelMtls里的端口号是workload(容器)端口,不是 Kubernetes Service 端口,并且只在该端口确实被某个 Service 绑定时才生效——直接暴露、没有对应 Service 的容器端口配了portLevelMtls也不会被采纳。
Ambient 模式下有一个语义收紧:DISABLE
模式不支持——因为 ztunnel 通过 HBONE
协议对代理间流量强制
mTLS,这条边界只在这里提一句,不展开,细节留给 Ambient
篇。这意味着同一份 PeerAuthentication YAML 在
sidecar 与 ambient
两种数据面下的可用取值范围本身不完全相同——这是策略语义随执行体不同而收紧的一个具体例子,而不是纯粹的文档差异。
三、AuthorizationPolicy → RBAC filter:ALLOW/DENY/AUDIT 与 CUSTOM 是两条不同的编译路径
pilot/pkg/security/authz/builder.Builder
把策略按 action
分桶:allowPolicies、denyPolicies、auditPolicies、customPolicies。核心区别:
3.1 ALLOW / DENY / AUDIT:直接编译成 RBAC enforce 规则
对这三种 action,Builder.build 直接产出一个
rbacpb.RBAC{Action: ..., Policies: {...}},塞进
envoy.filters.http.rbac 的 rules
字段——Envoy 收到请求后自己按 RBAC
语义放行或拒绝,不需要额外一跳。
3.2 CUSTOM:先 shadow 评估,再触发 ext_authz
CUSTOM action(对接外部授权
provider)不能直接让 RBAC filter 决定放行/拒绝——Envoy 的
RBAC filter 本身不支持远程调用。Istio
的做法(builder.go 注释直接写明设计):
- 把 CUSTOM 策略编译成
shadow_rules(DENYaction,仅评估不拦截),命中时把结果写进 dynamic metadata,key 形如istio-ext-authz-ns[...]-policy[...]-rule[...]。 - 紧跟着插入的
envoy.filters.http.ext_authz用filter_enabled_metadata读取上一步写的 metadata,只有 key 前缀匹配istio-ext-authz时才真正调用外部授权服务。
flowchart LR
req["Request"] --> rbacShadow["RBAC filter<br/>shadow_rules (DENY, no block)"]
rbacShadow -->|"write dynamic metadata"| meta["istio_ext_authz_shadow_effective_policy_id"]
meta --> extauthz{"ext_authz filter<br/>filter_enabled_metadata match?"}
extauthz -->|"yes"| callout["Call external authz service"]
extauthz -->|"no"| skip["Skip ext_authz, continue"]
这条链路的动机是用 Envoy 原生机制(metadata +
条件启用)模拟「RBAC
评估结果驱动是否调用外部服务」,而不是修改 RBAC filter
本身去支持远程调用——本质上是把「进程内策略引擎」(envoy.filters.http.rbac)与「外部策略服务」(ext_authz
后的 provider)拼接成一条链,而不是二选一。这和 零信任系列
07 篇 讨论的「进程内执行(Cedar/OPA in-process)vs
远程调用」权衡是同一类工程分歧的另一种解法:Istio
选择两者都留,用 shadow
结果做门控,而不是强制二选一。ext_authz
本身的失败模式(fail-open/fail-closed、超时)见 Envoy
扩展边界篇,本文不重复。
四、规则怎么变成 permission / principal:身份来自哪个字段
pilot/pkg/security/authz/model.Model.Generate
把一条策略规则拆成
permissions(描述「什么请求」)与
principals(描述「谁发的」),分别生成 Envoy
rbacpb.Permission /
rbacpb.Principal。几个具体的生成器例子(来自
model/generator_test.go
的测试用例,反映真实映射规则):
| Istio 字段 | 生成的 Envoy 匹配 | 含义 |
|---|---|---|
to.operation.ports |
destinationPort |
目的端口精确匹配 |
from.source.ipBlocks |
directRemoteIp /
remoteIp(addressPrefix + prefixLen) |
源 IP CIDR |
from.source.namespaces |
filter_state key
io.istio.peer_principal,正则
.*/ns/<namespace>/.* |
从对端证书身份里提取 namespace 段 |
from.source.principals(含隐式 trust domain
场景) |
filter_state key
io.istio.peer_principal,前缀匹配
spiffe://<trust-domain>/ |
直接按 SPIFFE URI 前缀匹配 |
关键点:from.source.namespaces /
principals 这类身份匹配,最终读取的是 mTLS
对端证书里解析出的 SPIFFE 身份,写入 Envoy 的
io.istio.peer_principal filter
state——这条链路成立的前提是对端连接确实是
mTLS。如果 PeerAuthentication 是
PERMISSIVE
且这次连接是明文,peer_principal
就是空的,任何按身份写的 AuthorizationPolicy
规则都不会匹配(具体是拒绝还是放行取决于 action 与默认
deny/allow
逻辑)——这是「策略配对了却好像没生效」的一类高频根因:先查这次连接是不是真的用了
mTLS,而不是先怀疑 RBAC 规则写错。
SPIFFE 身份格式是
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>,trust
domain 默认 cluster.local,可通过 mesh
配置改写;跨信任域的联邦、身份细节属于 SPIFFE/SPIRE
与 mTLS
大规模部署 的范围,本文不重复。
五、证书从哪来:istiod CA
上一节的前提——「对端证书带 SPIFFE
身份」——要有人签发。istiod 内置的 CA(历史上叫
Citadel,现在是 istiod 的一个组件)核心逻辑在
security/pkg/pki/ca/ca.go:IstioCA.Sign(csrPEM []byte, certOpts CertOpts) ([]byte, error),CertOpts
里的 SubjectIDs 就是要写进证书 SAN 的 SPIFFE
URI。签发发生在一个独立的 gRPC
服务(security/pkg/server/ca/server.go 的
CreateCertificate)上,流程:
- istio-agent 生成私钥与 CSR。
- istio-agent 带着凭证(通常是 K8s ServiceAccount JWT)把 CSR 发给 istiod 的 CA gRPC 端点。
Authenticate校验凭证身份(security/pkg/server/ca/authenticate/kubeauth)。- 校验通过后
IstioCA.Sign用当前持有的签发证书/私钥(KeyCertBundle)签发一张短期证书返回。
istiod 支持自签/中间 CA 模式,也支持把签发请求转发给外部 CA(RA 模式)——这一层的运维细节(根 CA 轮换、外部 CA 集成)属于 mTLS 大规模部署 的范围,本文只钉「谁签发、身份写在哪」这一步。
六、SDS:证书怎么从 istiod 到 Envoy 手里
签发证书和把证书喂给 Envoy
是两条独立的通道——这是本文要钉的最后一环,也是 Envoy
xDS 资源树篇 里把 SDS 画成「可选边」的原因:LDS/CDS 上的
transport_socket 只声明「引用哪个 secret
名」,真正的证书字节走单独的 SDS 会话。
istio-agent 本身就是这条通道的 SDS
server(security/pkg/nodeagent/sds/server.go 的
Server,通过 Unix Domain Socket 提供
gRPC)。它背后的
SecretManagerClient(security/pkg/nodeagent/cache/secretcache.go)处理两个特殊命名的资源:
default:workload 的 SPIFFE 证书。首次请求时向配置的caClient(通常直连 istiod)发 CSR,之后按证书生命周期缓存并在临近过期时自动轮转。ROOTCA:只含根证书,用于验证对端。
也支持纯文件模式:证书挂载在
/etc/certs/{key,cert,root-cert.pem}
时直接读文件,不经过 CA 客户端——这条路径下 istiod
完全不参与证书签发,只是运维预置文件,常见于外部 CA
集成或非标准证书来源的场景。
sequenceDiagram
participant Envoy
participant Agent as istio-agent (local SDS server)
participant CA as istiod CA
Envoy->>Agent: SDS request (UDS): "default" / "ROOTCA"
alt no cached cert
Agent->>CA: CreateCertificate(CSR, JWT)
CA-->>Agent: signed cert chain (SPIFFE SAN)
end
Agent-->>Envoy: SDS response: cert + key / root cert
Envoy、Listener、Cluster
上引用的 SDS ConfigSource 通常也是
ads: {}(走同一条聚合流),但
type_url 与 LDS/CDS
不同——同一条物理连接上跑着多个逻辑独立的 xDS
类型,这一点和 第
5–6 篇 讲的 CDS/EDS/RDS
命名契约同构:谁引用谁,全靠资源名字符串对齐,SDS
只是多了「resource name
是固定的两个特殊字符串」这一条额外约束。
七、边界声明
本文不重写以下内容,只给出上面提到的外链:
- 零信任策略含义、微分段边界、SPIFFE/SPIRE 联邦细节 → 零信任系列、IAM 工作负载身份。
- ext_authz 的失败语义与 filter 挂点权衡 → Envoy 扩展边界。
- Ambient 模式下 ztunnel 如何用 HBONE 承载 mTLS、PeerAuthentication 语义收紧的完整细节 → Ambient 篇。
八、谱系、争论与开放问题
谱系:把身份验证结果(mTLS 对端证书)写进请求上下文、再用策略引擎在数据面就地判定授权,是 Google BeyondCorp「不信任网络位置、验证每次连接」思路在服务间通信上的落地(零信任系列 03 篇 有完整脉络,这里不重复)。SPIFFE 作为跨平台工作负载身份标准,把「身份长什么样」和「Istio 具体怎么签发」解耦——istiod CA 只是 SPIFFE 生态里的一个 issuer 实现。
争论:CUSTOM action 用「shadow RBAC + metadata 门控 + ext_authz」这条迂回链路,而不是让 RBAC filter 直接支持远程判定——这是 Envoy filter 职责单一化(每个 filter 只做一件事,组合而非扩权)的具体代价:换来的是可组合性和 filter 语义清晰,代价是同一条策略決策要跨两个 filter 才能看全,config_dump 排障时容易只看到 RBAC 或只看到 ext_authz 而漏掉另一半。
开放问题:auto mTLS 的 best-effort
推断(第 7
篇)与 workload 级 PeerAuthentication
精确策略之间的信息差,叠加上
AuthorizationPolicy
按身份匹配的前提(连接必须真的是
mTLS),三者组合起来的「配置正确但连接方式不对」的排障成本,目前没有一个单一工具(istioctl analyze
或等价物)能一次性交叉检查——这类跨资源一致性检查目前更依赖运维经验,而不是自动化规则。
九、参考资料
规范 / 官方文档(A)
- Istio 1.30 文档,Security 概览(身份签发与 SDS 分发流程图)。
- Istio 1.30 文档,PeerAuthentication 参考(mTLS
模式、
portLevelMtls约束、Ambient 下DISABLE不支持的说明)。 istio/api:security/v1beta1/peer_authentication.proto。
源码(A)
istio/istio:pilot/pkg/security/authz/builder/builder.go(Builder.build,CUSTOM 的 shadow rules 与 ext_authz 门控设计注释)。istio/istio:pilot/pkg/security/authz/model/model.go、generator_test.go(permission/principal 生成器示例)。istio/istio:security/pkg/pki/ca/ca.go(IstioCA.Sign)、security/pkg/server/ca/server.go(CreateCertificate)。istio/istio:security/pkg/nodeagent/sds/server.go(本地 SDS server)、security/pkg/nodeagent/cache/secretcache.go(SecretManagerClient、default/ROOTCA资源)。istio/istio:architecture/security/istio-agent.md(istio-agent 作为 SDS/ADS 中间层的架构说明)。
站内
- 零信任系列:BeyondCorp 论文脉络 · 零信任策略引擎 · mTLS 大规模部署
- IAM / 工作负载身份:mTLS、SPIFFE/SPIRE
- Envoy / 扩展边界 · Envoy / xDS 资源树
- 第 7 篇:DestinationRule · 第 9 篇:Sidecar/Telemetry/EnvoyFilter
- 系列目录
实验台账
- 本篇不粘贴
config_dump中的 RBAC/SDS 实跑片段;filter 生成规则与 SDS 流程引用源码函数与官方架构文档,未在本机集群逐字段核对。
十、小结
PeerAuthentication和AuthorizationPolicy基本不碰 CDS/RDS,主要落在入站 Listener 的 filter chain:前者管 TLS 要求,后者变成envoy.filters.http.rbac。portLevelMtls只对 workload 级策略生效,且端口号是容器端口不是 Service 端口。AuthorizationPolicy的 CUSTOM action 走「shadow RBAC 评估 → 写 metadata → 门控 ext_authz」,不是让 RBAC filter 直接远程判定。- 按身份匹配的规则最终读取的是 mTLS 握手带出的 SPIFFE
身份(
io.istio.peer_principal)——连接不是 mTLS 时这类规则天然不会命中。 - 证书签发(istiod CA)与证书分发(istio-agent 本地 SDS server)是两条独立通道,和 LDS/CDS 共享 ADS 传输但资源名字空间完全独立。
→ 系列目录 · 上一篇:DestinationRule · 下一篇:Sidecar/Telemetry/EnvoyFilter
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Istio 控制面】istiod 进程模型:discovery、config、CA 与注入的边界
拆解 pilot-discovery 二进制内部的组件化启动顺序:CA 为何先于 Discovery 初始化、注入 Webhook 与配置校验为何是独立 HTTPS 服务、以及哪些组件真正位于 xDS 推送热路径上。
【Istio 控制面】DestinationRule:Cluster 之上的 subset、outlier 与 TLS 挂载
拆解 DestinationRule 如何在 CDS 已生成的 Cluster 之上派生 subset、挂载 connectionPool/outlierDetection/loadBalancer/tls,以及 mesh 默认、DestinationRule 级、port 级、subset 级四层设置的真实合并规则——其中 port 级与 subset 级的继承语义并不对称。
【Istio 控制面】控制面全景:从 CRD 到 xDS 的翻译与推送内核
定位 Istio 控制面内核相对 Envoy 消费侧、Service Mesh 税文与 Gateway API 资源模型的缺口;给出五条坐标系、16 篇地图与本系列明确不写的范围,钉住 istiod 1.30.3 为主线。
【Istio 控制面】代理身份与订阅:sidecar、ztunnel、waypoint、Gateway 谁在订阅什么
拆解 istiod 如何从节点 ID 与 mTLS/JWT 得到一个 model.Proxy 身份,以及 NodeType 如何决定该连接会订阅哪一套 xDS 资源类型:sidecar/Router 走标准 LDS/RDS/CDS/EDS,ztunnel 走自定义 Address/Authorization,waypoint 是 Envoy 但推送触发条件不同。