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

【网络工程】mTLS 工程实践:服务间双向认证

文章导航

分类入口
network
标签入口
#tls#mtls#spiffe#spire#istio#certificate#zero-trust

目录

在传统的 TLS 握手中,只有客户端验证服务器的身份——服务器发送证书,客户端验证证书的有效性。但在微服务架构中,服务 A 调用服务 B 时,服务 B 也需要知道”调用方是谁”。这就是 mTLS(Mutual TLS,双向 TLS)的核心价值:双方互相验证身份。

mTLS 不是新技术。它在 TLS 协议中从一开始就被定义(RFC 2246 的 CertificateRequest 消息)。但它在微服务和零信任架构中的大规模应用,是最近几年才开始的工程实践。

一、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

# 前置: 已有私有 CA(参考上一篇)

# 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 required

2.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-service

4.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 签名
   - 额外计算: 服务器验证客户端证书签名
   - 影响: 握手延迟增加约 5-15%
   
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"}'

# 典型结果 (局域网, ECDSA P-256):
# 单向 TLS 平均: 2.1 ms
# mTLS 平均:     2.4 ms
# 额外开销: ~0.3 ms (~14%)

6.2 Service Mesh Sidecar 开销

指标 无 Sidecar Istio Envoy Linkerd proxy
P50 延迟 基准 +2-3 ms +1-2 ms
P99 延迟 基准 +5-10 ms +3-5 ms
CPU 基准 +50-100m/pod +20-50m/pod
内存 基准 +40-60 MB/pod +20-30 MB/pod

这些数值在不同负载下差异较大。建议在实际业务场景中做基准测试,而不是依赖通用数据。高 QPS 场景下,Sidecar 的 CPU 开销可能比低 QPS 场景高出 2-3 倍。

七、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
  → 拒绝明文通信
  → 零信任网络目标达成

8.4 何时使用 mTLS

场景 是否需要 mTLS 原因
微服务内部通信 ✓ 推荐 零信任原则
数据库连接 ✓ 推荐 防止未授权访问
第三方 API 集成 看情况 对方是否支持
前端到后端 通常不需要 浏览器无法管理客户端证书
公网 API 通常不需要 用 API Key/OAuth 代替
IoT 设备通信 ✓ 推荐 设备身份认证

mTLS 最适合的场景是”基础设施对基础设施”的通信——服务之间、数据库连接、消息队列。对于面向终端用户的场景(浏览器、移动 App),通常用 OAuth2/JWT 做应用层认证,因为用户设备上管理客户端证书的体验很差。

一个有用的判断标准:如果通信双方都是你控制的基础设施,用 mTLS;如果有一方是终端用户,用应用层认证。两者也可以结合使用——mTLS 保护传输层,JWT 承载业务层的用户身份和权限信息。

九、总结

mTLS 解决的核心问题是”你是谁”——在网络层面建立双方的身份互信:

  1. mTLS 的工程复杂度在于证书管理,不在于协议。 协议层面 mTLS 只是多了 CertificateRequest 和客户端的 Certificate 两个消息。但管理成百上千个服务的证书签发、分发和轮换是真正的工程挑战。

  2. 短期证书 + 自动轮换是最佳实践。 SPIRE 默认 1 小时证书有效期,Istio 和 Linkerd 默认 24 小时。短期证书的优势是:即使证书泄露,影响窗口很短。而自动轮换消除了人工干预的风险。

  3. Service Mesh 是大规模 mTLS 的最佳载体。 通过 Sidecar 代理处理 mTLS,应用代码完全不需要关心证书。但 Sidecar 引入了额外的延迟和资源开销——P50 约增加 1-3 ms,每个 Pod 额外消耗 20-60 MB 内存。

  4. PERMISSIVE 模式是迁移的关键。 不要试图一步到位切换到 STRICT 模式。先 PERMISSIVE 让新旧流量共存,监控 mTLS 覆盖率,确认所有服务都已接入后再切 STRICT。

  5. SPIFFE 是服务身份的未来标准。 它定义了统一的服务身份格式(SPIFFE ID)、验证文档(SVID)和 API(Workload API)。即使你现在不用 SPIRE,理解 SPIFFE 的身份模型对设计服务间认证方案也有指导意义。


上一篇:证书工程:PKI 体系、ACME 与自动化管理

下一篇:TLS 性能优化:会话恢复、OCSP Stapling 与硬件加速

同主题继续阅读

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

2025-08-04 · network

【网络工程】证书工程:PKI 体系、ACME 与自动化管理

证书过期宕机是最常见也最可避免的生产事故。本文从工程角度剖析 X.509 证书结构、PKI 信任链的工作原理、ACME 协议与 Let's Encrypt 的自动化部署、私有 CA 的搭建实践,以及 cert-manager 在 Kubernetes 中的证书管理方案。覆盖证书监控、轮换策略与过期告警的完整工程体系。

2025-07-29 · network

【网络工程】网络隔离与微分段:VLAN、SDN 策略与零信任

网络隔离是安全架构的基石。本文从传统 VLAN 的 4096 限制、VXLAN 的 Overlay 隔离机制、SDN 下的 Calico/Cilium Network Policy 工程实践、微分段的设计方法论,到零信任网络架构的分段策略,系统讲解从物理隔离到软件定义隔离的演进和工程落地。


By .