在传统的 TLS 握手中,只有客户端验证服务器的身份——服务器发送证书,客户端验证证书的有效性。但在微服务架构中,服务 A 调用服务 B 时,服务 B 也需要知道”调用方是谁”。这就是 mTLS(Mutual TLS,双向 TLS)的核心价值:双方互相验证身份。
mTLS 不是新技术。它在 TLS 协议中从一开始就被定义(RFC 8446
的 certificate_request / 客户端
Certificate 消息;TLS 1.2 见 RFC
5246)。但它在微服务和零信任架构中的大规模应用,是最近几年才开始的工程实践。
系列分工
篇目 本文不重复的内容 26 证书工程 X.509 字段、PKI 信任链、ACME、cert-manager 25 TLS 1.3 工程实践 1-RTT/0-RTT、middlebox、Nginx/OpenSSL 部署 76 网络隔离与零信任 微分段、PDP/PEP、零信任分段策略 TLS 1.3 最小实现 握手报文与 HKDF 源码级拆解 本文聚焦 双向认证握手差异、证书分发三模式、SPIFFE/SPIRE、Mesh 透明 mTLS;单向 TLS 密码套件与性能调优见 25、28 篇。
环境说明:下文 OpenSSL /
istioctl/linkerd命令为可复现示例;§6.1 握手延迟对比脚本需在本地 TLS 端点实测,本站未粘贴未执行环境的 ms 级输出。Sidecar 开销表为社区 benchmark 引用区间,非普适 SLA。
一、mTLS 与单向 TLS 的区别
1.1 握手流程对比
单向 TLS(标准 HTTPS):
客户端 服务器
├── ClientHello ──────────────────→│
│←── ServerHello ─────────────────┤
│←── Certificate ─────────────────┤ ← 服务器发送证书
│←── ServerKeyExchange ───────────┤
│←── ServerHelloDone ─────────────┤
│ │
│ [客户端验证服务器证书] │
│ │
├── ClientKeyExchange ────────────→│
├── ChangeCipherSpec ─────────────→│
├── Finished ─────────────────────→│
│←── ChangeCipherSpec ────────────┤
│←── Finished ────────────────────┤
客户端知道服务器的身份 ✓
服务器不知道客户端的身份 ✗
双向 TLS(mTLS):
客户端 服务器
├── ClientHello ──────────────────→│
│←── ServerHello ─────────────────┤
│←── Certificate ─────────────────┤ ← 服务器证书
│←── CertificateRequest ──────────┤ ← 新增: 要求客户端证书
│←── ServerKeyExchange ───────────┤
│←── ServerHelloDone ─────────────┤
│ │
│ [客户端验证服务器证书] │
│ │
├── Certificate ──────────────────→│ ← 新增: 客户端发送证书
├── ClientKeyExchange ────────────→│
├── CertificateVerify ────────────→│ ← 新增: 客户端证明持有私钥
├── ChangeCipherSpec ─────────────→│
├── Finished ─────────────────────→│
│ │
│ [服务器验证客户端证书] │
│ │
│←── ChangeCipherSpec ────────────┤
│←── Finished ────────────────────┤
客户端知道服务器的身份 ✓
服务器知道客户端的身份 ✓
1.2 CertificateRequest 消息
CertificateRequest 消息内容:
服务器在此消息中告诉客户端:
1. 接受的证书类型:
- rsa_sign (RSA 签名)
- ecdsa_sign (ECDSA 签名)
2. 接受的签名算法:
- sha256WithRSAEncryption
- ecdsa-with-SHA256
- ...
3. 接受的 CA 列表(certificate_authorities):
- 客户端证书必须由这些 CA 签发
- 列表为空 = 接受任何 CA
TLS 1.3 中的变化:
CertificateRequest 被加密传输
增加了 extensions 字段(如 oid_filters)
1.3 CertificateVerify 消息
CertificateVerify 的作用:
证明客户端确实持有证书对应的私钥
而不是从别处复制来的证书
工作原理:
1. 客户端对之前所有握手消息计算哈希
2. 用自己的私钥对哈希值签名
3. 发送签名给服务器
4. 服务器用客户端证书中的公钥验证签名
如果签名验证成功:
→ 客户端确实持有私钥 → 身份可信
如果签名验证失败:
→ 证书是伪造的 → 握手中止
二、mTLS 的工程实现
2.1 使用 OpenSSL 手动配置 mTLS
签发步骤依赖 26 证书工程 中的私有 CA 与 SAN/EKU 约定;此处只演示 mTLS 特有配置。
# 前置: 已有私有 CA(见 26 篇)
# 1. 签发服务器证书
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-nodes -keyout server.key -out server.csr \
-subj "/CN=payment-service"
openssl x509 -req -days 365 \
-in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt \
-extfile <(echo -e "subjectAltName=DNS:payment-service,DNS:payment-service.default.svc.cluster.local\nextendedKeyUsage=serverAuth\nbasicConstraints=CA:FALSE")
# 2. 签发客户端证书
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-nodes -keyout client.key -out client.csr \
-subj "/CN=order-service"
openssl x509 -req -days 365 \
-in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt \
-extfile <(echo -e "subjectAltName=URI:spiffe://cluster.local/ns/default/sa/order-service\nextendedKeyUsage=clientAuth\nbasicConstraints=CA:FALSE")
# 3. 测试 mTLS 连接
# 启动服务器(要求客户端证书)
openssl s_server -accept 8443 \
-cert server.crt -key server.key \
-CAfile ca.crt -Verify 1
# 客户端连接(提供客户端证书)
openssl s_client -connect localhost:8443 \
-cert client.crt -key client.key \
-CAfile ca.crt
# 不提供客户端证书 → 握手失败
openssl s_client -connect localhost:8443 \
-CAfile ca.crt
# SSL routines:ssl3_read_bytes:tlsv13 alert certificate required2.2 Nginx mTLS 配置
server {
listen 8443 ssl;
server_name payment-service;
# 服务器证书
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# mTLS 配置
ssl_client_certificate /etc/nginx/ssl/ca.crt; # 信任的 CA
ssl_verify_client on; # 强制验证客户端证书
# ssl_verify_client optional; # 可选验证(部分接口需要)
ssl_verify_depth 2; # 证书链验证深度
# 将客户端证书信息传递给后端
location / {
proxy_pass http://backend;
proxy_set_header X-Client-CN $ssl_client_s_dn_cn;
proxy_set_header X-Client-Cert $ssl_client_escaped_cert;
proxy_set_header X-Client-Verify $ssl_client_verify;
}
# 部分接口不需要客户端证书
location /health {
ssl_verify_client off;
return 200 "ok";
}
}
2.3 Go 语言 mTLS 实现
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"log"
"net/http"
"os"
)
// mTLS 服务器
func startServer() {
// 加载 CA 证书(用于验证客户端证书)
caCert, _ := os.ReadFile("ca.crt")
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS13,
}
server := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从已验证的客户端证书中提取身份
if len(r.TLS.PeerCertificates) > 0 {
clientCN := r.TLS.PeerCertificates[0].Subject.CommonName
fmt.Fprintf(w, "Hello, %s!\n", clientCN)
}
}),
}
log.Fatal(server.ListenAndServeTLS("server.crt", "server.key"))
}
// mTLS 客户端
func startClient() {
// 加载客户端证书和私钥
clientCert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
// 加载 CA 证书(用于验证服务器证书)
caCert, _ := os.ReadFile("ca.crt")
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: caCertPool,
MinVersion: tls.VersionTLS13,
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}
resp, err := client.Get("https://payment-service:8443/")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}三、证书分发的工程挑战
mTLS 最大的工程挑战不是协议本身,而是证书的分发和轮换。
3.1 三种证书分发模式
模式一: 静态证书(手动分发)
┌──────┐ 手动复制证书 ┌──────┐
│ 运维 ├──────────────────→│ 服务 │
└──────┘ └──────┘
优点: 简单,无额外基础设施
缺点: 不可扩展,轮换困难
适用: 少量服务,内部系统
模式二: 集中式 CA(Secret Store 分发)
┌──────┐ ┌──────────┐ ┌──────┐
│ CA ├───→│ Vault / ├───→│ 服务 │
└──────┘ │ K8s Secret│ └──────┘
└──────────┘
优点: 集中管理,可审计
缺点: Secret Store 成为单点
适用: 中等规模,已有 Vault
模式三: Sidecar 代理(Service Mesh)
┌──────┐ ┌─────────┐ ┌──────────────┐
│ CA ├───→│ Control ├───→│ Sidecar │
│(SPIRE│ │ Plane │ │ (Envoy) │
│ /Istio) └─────────┘ │ ↕ mTLS │
└──────┘ │ App Container│
└──────────────┘
优点: 对应用透明,自动轮换
缺点: 基础设施复杂,sidecar 开销
适用: 大规模微服务
3.2 证书轮换的挑战
为什么证书轮换在 mTLS 中更复杂:
单向 TLS:
只需要轮换服务器证书
→ 更新证书文件 → reload 服务器
→ 客户端不需要任何变更
mTLS:
需要同时轮换服务器证书和客户端证书
→ 如果先轮换服务器的 CA → 旧客户端证书不被信任 → 中断
→ 如果先轮换客户端的 CA → 旧服务器 CA 不信任新证书 → 中断
安全的轮换流程(双 CA 信任期):
1. 服务器同时信任旧 CA 和新 CA
ssl_client_certificate: [old-ca.crt, new-ca.crt]
2. 逐步为客户端签发新 CA 的证书
3. 所有客户端都使用新证书后
移除对旧 CA 的信任
4. 完成轮换
四、SPIFFE 与 SPIRE
4.1 SPIFFE 标准
SPIFFE(Secure Production Identity Framework for Everyone)是 CNCF 毕业项目,定义了一套标准化的服务身份框架。
SPIFFE 的核心概念:
1. SPIFFE ID — 服务的唯一身份标识
格式: spiffe://<trust-domain>/<workload-path>
例如: spiffe://cluster.local/ns/production/sa/payment-service
trust-domain: 信任域(通常是集群或组织)
workload-path: 工作负载的路径
2. SVID (SPIFFE Verifiable Identity Document) — 身份文档
- X.509-SVID: 将 SPIFFE ID 放入证书的 SAN URI 字段
- JWT-SVID: 将 SPIFFE ID 放入 JWT 的 sub 字段
3. Trust Bundle — 信任包
一组根 CA 证书,用于验证 SVID
每个 trust-domain 有自己的 Trust Bundle
4. Workload API — 工作负载 API
Unix Domain Socket 接口
工作负载通过此 API 获取自己的 SVID
不需要在应用中硬编码证书路径
4.2 SPIRE 架构
SPIRE (SPIFFE Runtime Environment) 架构:
┌───────────────────────────────────────────┐
│ SPIRE Server │
│ ┌────────────┐ ┌──────────┐ ┌────────┐│
│ │ Registration│ │ CA │ │ Data ││
│ │ API │ │ (签发 │ │ Store ││
│ │ │ │ SVID) │ │ ││
│ └────────────┘ └──────────┘ └────────┘│
└─────────────┬─────────────────────────────┘
│ Node Attestation
│ + SVID 签发
┌─────────────┼─────────────────────────────┐
│ Node │ │
│ ┌──────────┴──────────┐ │
│ │ SPIRE Agent │ │
│ │ ┌────────────────┐ │ │
│ │ │ Workload API │ │ │
│ │ │ (Unix Socket) │ │ │
│ │ └───────┬────────┘ │ │
│ └──────────┼──────────┘ │
│ │ │
│ ┌──────────┴──┐ ┌───────────────┐ │
│ │ Workload A │ │ Workload B │ │
│ │ (获取 SVID) │ │ (获取 SVID) │ │
│ └─────────────┘ └───────────────┘ │
└────────────────────────────────────────────┘
# SPIRE 部署示例
# 1. 安装 SPIRE Server
kubectl apply -f https://raw.githubusercontent.com/spiffe/spire/main/support/k8s/spire-server.yaml
# 2. 安装 SPIRE Agent(DaemonSet)
kubectl apply -f https://raw.githubusercontent.com/spiffe/spire/main/support/k8s/spire-agent.yaml
# 3. 注册工作负载
kubectl exec -n spire spire-server-0 -- \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://cluster.local/ns/default/sa/payment-service \
-parentID spiffe://cluster.local/ns/spire/sa/spire-agent \
-selector k8s:ns:default \
-selector k8s:sa:payment-service
# 4. 查看注册的工作负载
kubectl exec -n spire spire-server-0 -- \
/opt/spire/bin/spire-server entry show
# 5. 工作负载通过 Workload API 获取 SVID
# 应用代码中使用 go-spiffe 库:
# import "github.com/spiffe/go-spiffe/v2/workloadapi"
# source, _ := workloadapi.NewX509Source(ctx)
# svid, _ := source.GetX509SVID()
# fmt.Println(svid.ID) // spiffe://cluster.local/ns/default/sa/payment-service4.3 SPIFFE 身份 vs 传统证书身份
| 特性 | 传统 X.509 CN | SPIFFE ID |
|---|---|---|
| 身份格式 | CN=payment-service | spiffe://cluster.local/ns/default/sa/payment-service |
| 标准化 | 无统一标准 | CNCF 标准(RFC 待定) |
| 多租户 | 依赖 CN 命名约定 | trust-domain 天然隔离 |
| 跨集群 | 手动配置信任 | Federation API 自动 |
| 证书有效期 | 通常 1 年 | 默认 1 小时(短期证书) |
| 轮换方式 | 手动/Cron | SPIRE Agent 自动轮换 |
五、Service Mesh 中的 mTLS
5.1 Istio 的 mTLS 实现
# Istio mTLS 配置
# 全局启用 mTLS(STRICT 模式)
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT # STRICT: 只接受 mTLS
# PERMISSIVE: 同时接受 mTLS 和明文(迁移期)
# DISABLE: 禁用 mTLS# 特定命名空间的策略
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT
# 排除特定端口(如 Prometheus 指标端口)
---
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: payment-service
namespace: production
spec:
selector:
matchLabels:
app: payment-service
portLevelMtls:
9090:
mode: PERMISSIVE # 指标端口允许明文Istio mTLS 内部实现:
┌────────────────────────────────────────────┐
│ Pod A │
│ ┌─────────┐ localhost ┌─────────────┐│
│ │ App ├──────────────→│ Envoy ││
│ │Container│ (明文 HTTP) │ Sidecar ││
│ └─────────┘ │ ┌────────┐ ││
│ │ │ mTLS │ ││
│ │ │ 加密 │ ││
│ │ └────┬───┘ ││
│ └──────┼──────┘│
└───────────────────────────────────┼───────┘
│ mTLS (加密)
┌───────────────────────────────────┼───────┐
│ Pod B │ │
│ ┌─────────────┐ │ │
│ │ Envoy │ ┌────────┐ │ │
│ │ Sidecar ├───│ mTLS │─────┘ │
│ │ │ │ 解密 │ │
│ └──────┬──────┘ └────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ App │ │
│ │ Container │ (收到明文 HTTP) │
│ └─────────────┘ │
└────────────────────────────────────────────┘
对应用完全透明:
App A 发送明文 HTTP → Envoy A 加密 → 网络 → Envoy B 解密 → App B
应用代码不需要任何 TLS 配置
# 检查 Istio mTLS 状态
istioctl x describe pod payment-service-xxx -n production
# mTLS status: STRICT
# 查看 Envoy 的证书信息
istioctl proxy-config secret payment-service-xxx -n production
# RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER
# default Cert Chain ACTIVE true xxx 2025-08-06T00:00:00Z
# ROOTCA CA ACTIVE true xxx 2035-01-01T00:00:00Z
# 检查 mTLS 连接
kubectl exec -it debug-pod -- \
curl -v http://payment-service.production:8080/
# 如果 STRICT 模式 + 没有 Sidecar → 连接失败
# 如果 PERMISSIVE 模式 → 明文也能通5.2 Linkerd 的 mTLS 实现
Linkerd vs Istio mTLS 的差异:
Linkerd:
- mTLS 默认启用(安装即生效)
- 使用内置 CA(identity controller)
- 证书有效期: 默认 24 小时
- 自动轮换,无需配置
- 不使用 SPIRE(有自己的身份系统)
Istio:
- mTLS 默认 PERMISSIVE(需要手动切 STRICT)
- 使用 istiod 内的 CA(或外部 CA)
- 证书有效期: 默认 24 小时
- 支持 SPIRE 集成
- 更灵活的策略控制
# Linkerd mTLS 验证
linkerd check --proxy
# 查看证书信息
linkerd identity -n production
# 查看 mTLS 连接状态
linkerd viz edges deployment -n production
# SRC DST SRC_P DST_P SECURED
# order payment 100% 100% true
# → SECURED=true 表示使用了 mTLS六、mTLS 的性能开销
6.1 性能影响分析
mTLS 的额外开销(相比单向 TLS):
1. 握手阶段:
- 额外传输: 客户端证书(~1 KB)+ CertificateVerify 签名
- 额外计算: 服务器验证客户端证书签名
- 影响: 握手多传证书与一次验签,延迟常比单向 TLS 高个位数到十数个百分点(随密钥类型与 RTT 变化,须本地脚本对比)
2. 数据传输阶段:
- 无额外开销(加密方式完全相同)
3. 证书轮换:
- 短期证书(1-24 小时)需要频繁轮换
- SPIRE Agent 或 Sidecar 处理,应用无感知
# 测量 mTLS 握手延迟开销
# 单向 TLS
for i in $(seq 1 100); do
start=$(date +%s%N)
echo | openssl s_client -connect server:8443 \
-CAfile ca.crt 2>/dev/null >/dev/null
end=$(date +%s%N)
echo $(( (end - start) / 1000000 ))
done | awk '{sum+=$1; n++} END{print "单向 TLS 平均:", sum/n, "ms"}'
# mTLS
for i in $(seq 1 100); do
start=$(date +%s%N)
echo | openssl s_client -connect server:8443 \
-cert client.crt -key client.key \
-CAfile ca.crt 2>/dev/null >/dev/null
end=$(date +%s%N)
echo $(( (end - start) / 1000000 ))
done | awk '{sum+=$1; n++} END{print "mTLS 平均:", sum/n, "ms"}'
# 输出随 CPU、RTT、密钥类型变化;对比时只关心同环境 mTLS/单向 的差值,不写死 ms 级结论6.2 Service Mesh Sidecar 开销
| 指标 | 无 Sidecar | Istio Envoy(引用区间) | Linkerd proxy(引用区间) |
|---|---|---|---|
| P50 延迟 | 基准 | 常 +1–3 ms 量级 | 常 +1–2 ms 量级 |
| P99 延迟 | 基准 | 高 QPS 下 tail 更明显 | 相对 Istio 略低(架构差异) |
| CPU / 内存 | 基准 | 每 Pod 数十 mCPU、数十 MB 级 | 通常低于 Envoy 同负载 |
这些数值在不同负载下差异较大。必须在目标 QPS 与报文大小下自测(可参考 79 网络 benchmark),不宜当作选型 SLA。
七、mTLS 故障排查
7.1 常见错误
# 错误一: 客户端证书不被信任
# 症状: SSL alert number 48 (unknown_ca)
openssl s_client -connect server:8443 \
-cert client.crt -key client.key \
-CAfile ca.crt 2>&1 | grep "alert"
# → 检查服务器的 ssl_client_certificate 是否包含签发客户端证书的 CA
# 错误二: 证书用途不匹配
# 症状: certificate verify failed
# → 检查客户端证书的 Extended Key Usage
openssl x509 -in client.crt -noout -text | grep -A 1 "Extended Key"
# 客户端证书需要: clientAuth
# 服务器证书需要: serverAuth
# 错误三: SAN 不匹配
# 症状: hostname mismatch
# → 检查服务器证书的 SAN 是否包含连接使用的域名
openssl x509 -in server.crt -noout -ext subjectAltName
# 错误四: 证书过期
openssl x509 -in client.crt -noout -dates
# 短期证书(如 SPIRE 的 1 小时有效期)过期很常见
# → 检查证书轮换机制是否正常工作
# 错误五: 私钥不匹配
# 检查证书和私钥是否匹配
openssl x509 -in client.crt -noout -modulus | openssl md5
openssl rsa -in client.key -noout -modulus | openssl md5
# 两个 MD5 值必须相同7.2 Wireshark 分析 mTLS 握手
# 抓取 mTLS 握手包
tcpdump -i eth0 -w mtls.pcap \
'tcp port 8443' -c 100
# 在 Wireshark 中分析:
# 过滤: tls.handshake.type == 13
# → 显示 CertificateRequest 消息
# 字段: Certificate Authorities → 查看服务器信任的 CA
# 过滤: tls.handshake.type == 11 && ip.src == <client_ip>
# → 显示客户端发送的 Certificate 消息
# 如果 Certificate Length = 0 → 客户端没有合适的证书
# 过滤: tls.handshake.type == 15
# → 显示 CertificateVerify 消息
# 如果没有这个消息 → 客户端没有发送证书
# 用 tshark 快速检查
tshark -r mtls.pcap -Y 'tls.handshake' \
-T fields \
-e frame.number \
-e ip.src \
-e tls.handshake.type \
-e tls.handshake.certificates_length
# 常用的 Wireshark 显示过滤器:
# tls.handshake.type == 13 → CertificateRequest
# tls.handshake.type == 11 → Certificate (客户端或服务器)
# tls.handshake.type == 15 → CertificateVerify
# tls.alert_message.desc == 42 → bad_certificate
# tls.alert_message.desc == 48 → unknown_ca
# tls.handshake.certificates_length == 0 → 空证书(客户端没有合适的证书)八、mTLS 部署策略
8.1 mTLS 与授权的关系
mTLS 解决的是认证(Authentication)——确认”你是谁”。但认证之后还需要授权(Authorization)——决定”你能做什么”。
认证 vs 授权:
mTLS (认证):
"这个请求来自 payment-service"
→ 确认身份
授权策略:
"payment-service 可以访问 /api/charge 但不能访问 /admin"
→ 控制权限
# Istio 的授权策略示例
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: payment-policy
namespace: production
spec:
selector:
matchLabels:
app: payment-service
rules:
- from:
- source:
principals: ["cluster.local/ns/production/sa/order-service"]
to:
- operation:
methods: ["POST"]
paths: ["/api/charge"]
- from:
- source:
principals: ["cluster.local/ns/production/sa/admin-service"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]认证和授权必须一起部署才有意义——只有 mTLS 认证而不做授权,等于给每个服务发了身份证但不检查通行证。在 Service Mesh 中,mTLS 提供认证,AuthorizationPolicy 提供授权,二者缺一不可。
8.3 渐进式迁移
阶段一: PERMISSIVE 模式
所有服务同时接受 mTLS 和明文
→ 不影响现有流量
→ 有 Sidecar 的服务之间自动使用 mTLS
阶段二: 监控 mTLS 覆盖率
观察 mTLS 流量的比例
→ 识别未接入 Service Mesh 的服务
→ 确保所有通信都能用 mTLS
阶段三: STRICT 模式(逐步)
按命名空间逐步切换到 STRICT
→ 先从非关键服务开始
→ 监控是否有连接失败
阶段四: 全局 STRICT
所有命名空间都要求 mTLS
→ 拒绝明文通信
→ 零信任网络目标达成
PERMISSIVE→STRICT 迁移与 [76 网络隔离与零信任](../76-network-isolation/network-isolation.html) §6.3 分段策略一致:mTLS 解决**传输层身份**,L7 AuthorizationPolicy / NetworkPolicy 解决**授权**。
8.4 何时使用 mTLS
| 场景 | 是否需要 mTLS | 原因 |
|---|---|---|
| 微服务内部通信 | ✓ 推荐 | 零信任原则 |
| 数据库连接 | ✓ 推荐 | 防止未授权访问 |
| 第三方 API 集成 | 看情况 | 对方是否支持 |
| 前端到后端 | 通常不需要 | 浏览器无法管理客户端证书 |
| 公网 API | 通常不需要 | 用 API Key/OAuth 代替 |
| IoT 设备通信 | ✓ 推荐 | 设备身份认证 |
mTLS 最适合的场景是”基础设施对基础设施”的通信——服务之间、数据库连接、消息队列。对于面向终端用户的场景(浏览器、移动 App),通常用 OAuth2/JWT 做应用层认证,因为用户设备上管理客户端证书的体验很差。
一个有用的判断标准:如果通信双方都是你控制的基础设施,用 mTLS;如果有一方是终端用户,用应用层认证。两者也可以结合使用——mTLS 保护传输层,JWT 承载业务层的用户身份和权限信息(应用层 token 与 SPIFFE SVID 分工见 76 零信任 §6.4)。
九、谱系、争论与开放问题
9.1 谱系
- 协议层:TLS 1.2
CertificateRequest(RFC 5246)→ TLS 1.3 加密 CertificateRequest(RFC 8446 §4.3.2);与 25 TLS 1.3 工程实践 同源。 - 身份标准:SPIFFE(CNCF 毕业)定义 SPIFFE ID / SVID / Workload API;SPIRE 为参考实现。
- 落地载体:Istio / Linkerd 将 mTLS 下沉到 Sidecar,与 76 零信任 的「默认拒绝 + 持续验证」对齐。
9.2 争论:Mesh mTLS vs 应用内 mTLS
| Sidecar 透明 mTLS | 应用内加载客户端证书 |
|---|---|
| 对业务零侵入 | 可精确控制证书与重试 |
| Sidecar 资源与 tail 延迟 | 每语言一套 TLS 栈 |
| 身份多为 SPIFFE URI | 常仍用 CN / 自建 PKI |
无跨规模统一最优解:小集群可应用内 + 26 PKI;数百微服务更常 Mesh + SPIRE。
9.3 开放问题
- mTLS 与 L7 授权的边界:双向 TLS 只证明 workload 身份,不表达 OAuth scope;SPIFFE Federation 跨 trust domain 与 IdP 联合仍靠各 Mesh 厂商扩展,缺跨云标准部署故事。
- 短期证书 scale:1h SVID + 自动轮换降低泄露窗口,但 CA/QPS 与 冷启动风暴(大规模 Pod 同时重启)对 SPIRE Server 的压力在生产案例里公开数据仍少。
- 后量子迁移:混合 KEM 进入 TLS 1.3 后,客户端证书链与 Mesh 控制面升级节奏不同步——与 25 TLS 1.3 工程实践 §十 同源未决问题。
十、总结
mTLS 解决的核心问题是”你是谁”——在网络层面建立双方的身份互信:
mTLS 的工程复杂度在于证书管理,不在于协议。 协议层面 mTLS 只是多了 CertificateRequest 和客户端的 Certificate 两个消息。但管理成百上千个服务的证书签发、分发和轮换是真正的工程挑战。
短期证书 + 自动轮换是最佳实践。 SPIRE 默认 1 小时证书有效期,Istio 和 Linkerd 默认 24 小时。短期证书的优势是:即使证书泄露,影响窗口很短。而自动轮换消除了人工干预的风险。
Service Mesh 是大规模 mTLS 的最佳载体。 通过 Sidecar 代理处理 mTLS,应用代码完全不需要关心证书。但 Sidecar 引入了额外的延迟和资源开销——须在目标负载下 benchmark(§6.2),不宜引用未标注 QPS 的固定 ms 数。
PERMISSIVE 模式是迁移的关键。 不要试图一步到位切换到 STRICT 模式。先 PERMISSIVE 让新旧流量共存,监控 mTLS 覆盖率,确认所有服务都已接入后再切 STRICT(与 76 零信任 渐进分段一致)。
SPIFFE 是服务身份的可复用标准。 它定义了统一的服务身份格式(SPIFFE ID)、验证文档(SVID)和 API(Workload API)。即使你现在不用 SPIRE,理解 SPIFFE 的身份模型对设计服务间认证方案也有指导意义。
同主题延伸:26 证书工程 · 25 TLS 1.3 工程实践 · 76 网络隔离与零信任 · 28 TLS 性能优化
下一篇:TLS 性能优化:会话恢复、OCSP Stapling 与硬件加速
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】CDN 与 HTTPS:边缘 TLS、证书管理与安全
CDN 的 HTTPS 部署涉及边缘 TLS 终止、证书托管、回源加密等多个工程环节。本文系统拆解 CDN HTTPS 的架构模式、证书管理方案、安全最佳实践与常见故障排查方法。
【网络工程】证书工程:PKI 体系、ACME 与自动化管理
证书过期宕机是最常见也最可避免的生产事故。本文从工程角度剖析 X.509 证书结构、PKI 信任链的工作原理、ACME 协议与 Let's Encrypt 的自动化部署、私有 CA 的搭建实践,以及 cert-manager 在 Kubernetes 中的证书管理方案。覆盖证书监控、轮换策略与过期告警的完整工程体系。
【身份与访问控制工程】服务身份:mTLS、SPIFFE/SPIRE 与 Workload Identity
前 9 篇讨论的都是'人'的身份——用户怎么登录、怎么验证。但微服务世界中,80% 的 API 调用是服务之间的。服务身份(Workload Identity)是整个 IAM 体系的另一半:mTLS 解决'传输层你是谁',SPIFFE/SPIRE 解决'在平台层你是谁且怎么证明',JWT Profile for OAuth 解决'我怎么拿到一个服务身份的 Token'。本文从这三条线拆解服务身份的工程实现。
【零信任安全架构】mTLS 大规模部署的工程现实:联邦、故障排查与根 CA 轮换
mTLS 是零信任服务间通信的基石,但从'单集群内启用 mTLS'到'全公司多集群、混合云的 mTLS',中间隔着 SPIRE 联邦、跨信任域证书验证、mTLS 握手并发瓶颈、连接池协议兼容性和故障排查等工程问题。本文不重复 SPIFFE/SPIRE 基础,而是聚焦大规模部署中才暴露的问题。