土法炼钢兴趣小组的算法知识备份

【Istio 控制面】PeerAuthentication / AuthorizationPolicy → filter chain 与 SDS:策略怎么变成 RBAC 与证书

文章导航

分类入口
networkkubernetes
标签入口
#istio#istiod#peerauthentication#authorizationpolicy#rbac#sds#spiffe#mtls#ca

目录

前几篇讲的都是「怎么发现、怎么路由、怎么挂负载均衡策略」,全部落在 CDS/EDS/RDS 三类资源上。PeerAuthenticationAuthorizationPolicy 不一样:它们基本不碰 CDS/RDS(第 6 篇 提过 RdsGeneratorskippedRdsConfigs 里就有 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)、DISABLEPERMISSIVE(同时接受 mTLS 与明文)、STRICT(只接受 mTLS)。作用域优先级:workload 级(带 selector) > namespace 级 > mesh 级UNSET 时逐级向上找第一个显式设置的值。

portLevelMtls 有两条容易踩的边界,官方文档明确写出:

  1. 只有带 selector 的 workload 级策略才能用 portLevelMtls——namespace 级策略只能设一个统一默认,不能做端口例外。
  2. 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 分桶:allowPoliciesdenyPoliciesauditPoliciescustomPolicies。核心区别:

3.1 ALLOW / DENY / AUDIT:直接编译成 RBAC enforce 规则

对这三种 action,Builder.build 直接产出一个 rbacpb.RBAC{Action: ..., Policies: {...}},塞进 envoy.filters.http.rbacrules 字段——Envoy 收到请求后自己按 RBAC 语义放行或拒绝,不需要额外一跳。

3.2 CUSTOM:先 shadow 评估,再触发 ext_authz

CUSTOM action(对接外部授权 provider)不能直接让 RBAC filter 决定放行/拒绝——Envoy 的 RBAC filter 本身不支持远程调用。Istio 的做法(builder.go 注释直接写明设计):

  1. 把 CUSTOM 策略编译成 shadow_rulesDENY action,仅评估不拦截),命中时把结果写进 dynamic metadata,key 形如 istio-ext-authz-ns[...]-policy[...]-rule[...]
  2. 紧跟着插入的 envoy.filters.http.ext_authzfilter_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。如果 PeerAuthenticationPERMISSIVE 且这次连接是明文,peer_principal 就是空的,任何按身份写的 AuthorizationPolicy 规则都不会匹配(具体是拒绝还是放行取决于 action 与默认 deny/allow 逻辑)——这是「策略配对了却好像没生效」的一类高频根因:先查这次连接是不是真的用了 mTLS,而不是先怀疑 RBAC 规则写错

SPIFFE 身份格式是 spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>,trust domain 默认 cluster.local,可通过 mesh 配置改写;跨信任域的联邦、身份细节属于 SPIFFE/SPIREmTLS 大规模部署 的范围,本文不重复。


五、证书从哪来:istiod CA

上一节的前提——「对端证书带 SPIFFE 身份」——要有人签发。istiod 内置的 CA(历史上叫 Citadel,现在是 istiod 的一个组件)核心逻辑在 security/pkg/pki/ca/ca.goIstioCA.Sign(csrPEM []byte, certOpts CertOpts) ([]byte, error)CertOpts 里的 SubjectIDs 就是要写进证书 SAN 的 SPIFFE URI。签发发生在一个独立的 gRPC 服务(security/pkg/server/ca/server.goCreateCertificate)上,流程:

  1. istio-agent 生成私钥与 CSR。
  2. istio-agent 带着凭证(通常是 K8s ServiceAccount JWT)把 CSR 发给 istiod 的 CA gRPC 端点。
  3. Authenticate 校验凭证身份(security/pkg/server/ca/authenticate/kubeauth)。
  4. 校验通过后 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.goServer,通过 Unix Domain Socket 提供 gRPC)。它背后的 SecretManagerClientsecurity/pkg/nodeagent/cache/secretcache.go)处理两个特殊命名的资源:

也支持纯文件模式:证书挂载在 /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

EnvoyListenerCluster 上引用的 SDS ConfigSource 通常也是 ads: {}(走同一条聚合流),但 type_url 与 LDS/CDS 不同——同一条物理连接上跑着多个逻辑独立的 xDS 类型,这一点和 第 5–6 篇 讲的 CDS/EDS/RDS 命名契约同构:谁引用谁,全靠资源名字符串对齐,SDS 只是多了「resource name 是固定的两个特殊字符串」这一条额外约束。


七、边界声明

本文不重写以下内容,只给出上面提到的外链:


八、谱系、争论与开放问题

谱系:把身份验证结果(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)

源码(A)

站内

实验台账


十、小结

  1. PeerAuthenticationAuthorizationPolicy 基本不碰 CDS/RDS,主要落在入站 Listener 的 filter chain:前者管 TLS 要求,后者变成 envoy.filters.http.rbac
  2. portLevelMtls 只对 workload 级策略生效,且端口号是容器端口不是 Service 端口。
  3. AuthorizationPolicy 的 CUSTOM action 走「shadow RBAC 评估 → 写 metadata → 门控 ext_authz」,不是让 RBAC filter 直接远程判定。
  4. 按身份匹配的规则最终读取的是 mTLS 握手带出的 SPIFFE 身份(io.istio.peer_principal)——连接不是 mTLS 时这类规则天然不会命中。
  5. 证书签发(istiod CA)与证书分发(istio-agent 本地 SDS server)是两条独立通道,和 LDS/CDS 共享 ADS 传输但资源名字空间完全独立。

系列目录 · 上一篇:DestinationRule · 下一篇:Sidecar/Telemetry/EnvoyFilter

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .