深色模式
mTLS 与零信任
摘要:本文面向负责集群内安全的 SRE / 安全工程师。解释 Istio 如何基于 SPIFFE 身份实现服务间双向 TLS,如何用
PeerAuthentication逐步收紧到 STRICT,以及如何用AuthorizationPolicy做零信任授权。覆盖版本 Istio 1.31(apiVersionsecurity.istio.io/v1beta1,旧版 1.2x 同 group)。
背景:明文集群内的横向移动风险
默认情况下,K8s 内部服务间通信是明文且无认证的。任何能进集群的 Pod 都能伪装成任意服务,向任意端点发请求(横向移动)。零信任(Zero Trust)的核心是:不默认信任网络位置,每个连接都验证身份并授权。
Istio 用 mTLS(双向 TLS) 解决加密与身份认证,用 AuthorizationPolicy 解决“谁能调谁”。两者配合构成网格内零信任。
核心概念:SPIFFE 身份
Istio 为每个工作负载签发基于 SPIFFE 的短期 X.509 证书,身份格式:
spiffe://cluster.local/ns/<namespace>/sa/<serviceaccount>1
证书由 istiod(作为 CA)签发,默认 24 小时生命周期并自动轮换,通过 SDS(Secret Discovery Service)分发给 Sidecar。应用代码完全无感知,mTLS 在代理层透明完成。
身份粒度来自 ServiceAccount
Istio 身份基于 K8s ServiceAccount。若命名空间内所有 Pod 都用默认 SA,则它们共享同一身份,授权策略无法区分。生产请为每个服务创建独立 SA 并在 Deployment 中 serviceAccountName 引用。
架构:mTLS 握手与策略层级
生产实践一:PeerAuthentication 三种模式
PeerAuthentication 控制 mTLS 模式,层级为 mesh-wide(istio-system 命名空间、无 selector) < namespace < workload(带 selector),子级覆盖父级。
| 模式 | 行为 |
|---|---|
STRICT | 只接受 mTLS,拒绝明文 |
PERMISSIVE | 同时接受 mTLS 与明文(迁移期) |
DISABLE | 不启用 mTLS |
步骤 1:先开 PERMISSIVE(网格级,安全过渡)
yaml
apiVersion: security.istio.io/v1beta1 # [版本相关] 1.31 稳定;v1 亦可用,按需选择
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system # 根命名空间 = 网格级默认
spec:
mtls:
mode: PERMISSIVE1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
步骤 2:命名空间级过渡到 STRICT
yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: payments
spec:
mtls:
mode: STRICT1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
步骤 3:端口级例外(健康探针 / 监控抓取)
部分端口(如云 LB 健康检查、Prometheus 抓取)来自无 Sidecar 的客户端,无法做 mTLS。用 portLevelMtls 让该端口保持 PERMISSIVE,而非把整个工作负载降级:
yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: payments-api
namespace: payments
spec:
selector:
matchLabels:
app: payments-api
mtls:
mode: STRICT
portLevelMtls:
9090: # 工作负载端口(非 Service 端口)
mode: PERMISSIVE1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
端口语义
portLevelMtls 中的端口是工作负载端口,必须被某个 Service 绑定,Istio 才会生效。不要用 Service 端口。
步骤 4:全网格 STRICT
所有命名空间接入网格并验证后,在 istio-system 设 STRICT 作为基线:
yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
生产实践二:AuthorizationPolicy(零信任授权)
PeerAuthentication 只解决“身份与加密”,不限制谁能调谁。需 AuthorizationPolicy 做 L7 授权。空 spec 表示 deny-all。
生产危险
应用 deny-all 前,必须先写好所有合法 ALLOW 规则。顺序错误会瞬间切断整个命名空间流量。请在变更窗口、先小范围(单服务)验证。
yaml
# 先允许合法调用,再 deny-all
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: payment-allow
namespace: payments
spec:
selector:
matchLabels:
app: payment-service
action: ALLOW
rules:
- from:
- source:
principals:
- "cluster.local/ns/payments/sa/order-service"
- "cluster.local/ns/payments/sa/billing-service"
to:
- operation:
methods: ["POST", "GET"]
paths: ["/api/v1/payments/*"]
---
# 命名空间默认拒绝(放在 ALLOW 之后)
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: payments
spec: {} # 空 spec = deny all1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
避免过宽
不要用 source.namespaces 单独授权——它允许该命名空间任意 SA 调用。对敏感服务必须用 source.principals(具体 SPIFFE 身份)。季度审计策略覆盖范围。
mTLS 与 K8s NetworkPolicy 是互补,不是替代
这是常见误解。二者工作层级不同:
| 维度 | Istio mTLS / AuthorizationPolicy | K8s NetworkPolicy |
|---|---|---|
| 层级 | L7(身份 + 应用属性) | L3/L4(IP/CIDR/端口/命名空间) |
| 身份基础 | SPIFFE 服务身份 | 源/目的 Pod IP(易变) |
| 加密 | 是(传输加密) | 否(仅网络隔离) |
| 授权粒度 | 到方法/路径/Header | 到端口/协议 |
| 依赖 | Istio 数据面 | CNI 插件(非所有 CNI 都支持) |
互补结论
NetworkPolicy 提供网络层隔离(即便没有 Sidecar 的 Pod 也生效,且能挡住非预期的 IP 段流量);Istio mTLS 提供身份级加密与零信任授权。生产应两者叠加:NetworkPolicy 兜底网络边界,mTLS/AuthorizationPolicy 做精细身份授权。
验证、回滚与排障
bash
# 检查服务间 mTLS 状态
istioctl authn tls-check deploy/order-service -n payments
# 查看代理加载的证书
istioctl proxy-config secret deploy/payment-service -n payments
# 静态分析策略冲突
istioctl analyze --all-namespaces
# 回滚到 PERMISSIVE(解除 STRICT 造成的连接拒绝)
kubectl apply -f pa-permissive.yaml -n payments1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
mTLS 握手失败排障顺序
- 双方模式一致? 服务端 STRICT 而调用方无 Sidecar(发明文)→ 连接被拒。用
proxy-config secret确认客户端有有效未过期证书。 - DestinationRule 是否误设
tls.mode: DISABLE/ISTIO_MUTUAL与服务端模式冲突? 这类错误看起来像网络故障,实为配置问题。 - 服务端是否真有 Sidecar? 无注入则自然无法 mTLS,用
proxy-config listeners --port确认。 - 是否被 NetworkPolicy 在 TCP 层挡掉?
tcpdump可判断握手是否在 TCP 层就被拒。
常见症状:
upstream connect error or disconnect/reset before headers、503 UF(access log)。
迁移最佳实践与副作用
- 灰度路径:PERMISSIVE(全量)→ 命名空间 STRICT → 端口例外 → 全网格 STRICT。不要在未全量注入 Sidecar 前直接全网格 STRICT,否则未接入服务全部断连。
- VM / 遗留系统:需 Sidecar 或基于 SPIFFE 的 VM 身份接入;Istio 支持对 VM 的工作负载证明(部分版本/分发
[版本相关],请按目标版本核实)。 - 副作用:mTLS 增加少量 CPU(加解密)与握手延迟;证书轮换若失败(CA 不可达)会在 24h 后影响新连接。监控
istiodCA 健康与证书剩余有效期。
参考资料
- Istio 官方文档 - 任务:启用严格 mTLS,访问日期:2026-10-08。
- Istio 官方文档 - AuthorizationPolicy 参考,访问日期:2026-10-08。
- Istio mTLS / SPIFFE 安全指南(2026),访问日期:2026-10-08。
- OneUptime - 服务间认证实践,访问日期:2026-10-08。