260 lines
9.8 KiB
Markdown
260 lines
9.8 KiB
Markdown
|
|
---
|
|||
|
|
tags: [service-mesh, mTLS, zero-trust, istio, certificate-management]
|
|||
|
|
create time: 2026-05-17 21:35
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Service Mesh 中的 mTLS 配置详解
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
mTLS(Mutual TLS)是服务网格实现零信任网络的核心机制。本文从 Istio 的三种 mTLS 模式入手,讲解双向证书认证的配置方法、迁移路径和日常排查技巧。
|
|||
|
|
|
|||
|
|
> [!question] 为什么服务间通信需要 mTLS?
|
|||
|
|
>
|
|||
|
|
> HTTP 请求在集群内裸奔是很危险的——一旦某个 Pod 被攻陷,攻击者可以直接监听同一 Namespace 内所有流量。mTLS 确保即使网络完全暴露,窃听者也无法解密通信内容。
|
|||
|
|
>
|
|||
|
|
> 这里有一个常见误解:mTLS 不是万能的。它只保护传输通道,不解决授权问题。一个合法的 A 服务仍然可以调用 B 服务的所有公开接口——只是它没法调 C 服务的接口了。这就是纵深防御的意义。
|
|||
|
|
|
|||
|
|
## mTLS 的基本原理
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant C1 as CL
|
|||
|
|
participant S1 as SV
|
|||
|
|
participant CA1 as CA
|
|||
|
|
Note right of CA1: 签名机构
|
|||
|
|
C1->>S1: TCP Connect
|
|||
|
|
activate S1
|
|||
|
|
C1->>S1: ClientHello
|
|||
|
|
S1->>C1: ServerHello + 证书
|
|||
|
|
S1->>C1: CertificateRequest
|
|||
|
|
C1->>C1: 验签服务端证书
|
|||
|
|
C1->>S1: 客户端证书
|
|||
|
|
S1->>S1: 验签客户端证书
|
|||
|
|
deactivate S1
|
|||
|
|
Note over C1,S1: TLS 握手完成
|
|||
|
|
C1->>S1: 加密数据
|
|||
|
|
S1->>C1: 加密数据
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
上面展示了完整的 mTLS 四次握手过程。与普通 HTTPS 不同,mTLS 要求双方都验证对方证书——服务端不仅确认自己连接的是合法客户端,客户端也确认自己连的是目标服务而非中间人。每个参与方通过证书标识自己的 SPIFFE ID,形成机器级别的信任链。
|
|||
|
|
|
|||
|
|
> [!note] 解释
|
|||
|
|
>
|
|||
|
|
> 这段流程的关键在于两次独立的证书验证:第一次是客户端验证服务端证书(类似 HTTPS),第二次是服务端验证客户端证书(mTLS 特有)。只有两边都通过后,TLS 会话密钥才会被用于后续所有通信的加解密。
|
|||
|
|
|
|||
|
|
## Istio 中的 mTLS 模式
|
|||
|
|
|
|||
|
|
Istio 提供三种 PeerAuthentication 模式,决定了 Sidecar 如何处理入站流量:
|
|||
|
|
|
|||
|
|
| 模式 | 行为 | 安全性 | 适用阶段 |
|
|||
|
|
|------|------|--------|---------|
|
|||
|
|
| UNSET | 继承全局或父命名空间配置 | — | — |
|
|||
|
|
| PERMISSIVE | 接受明文和 TLS 两种流量 | 中等 | 迁移过渡期 |
|
|||
|
|
| STRICT | 仅接受 TLS 连接 | 最高 | 生产就绪态 |
|
|||
|
|
|
|||
|
|
### 从 PERMISSIVE 到 STRICT 的迁移路径
|
|||
|
|
|
|||
|
|
> [!tip] 核心原则
|
|||
|
|
>
|
|||
|
|
> 不要一步到位切换到 STRICT。正确的做法是先用 PERMISSIVE 观察哪些流量没走 mTLS,逐一修复后再升级。
|
|||
|
|
|
|||
|
|
先在一个非核心 Namespace 做实验,验证流量不受影响后逐步推广到生产:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
apiVersion: security.istio.io/v1beta1
|
|||
|
|
kind: PeerAuthentication
|
|||
|
|
metadata:
|
|||
|
|
name: default
|
|||
|
|
namespace: production
|
|||
|
|
spec:
|
|||
|
|
mtls:
|
|||
|
|
mode: PERMISSIVE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
启用 PERMISSIVE 后,通过 Istio 自带的 metrics 找出未使用 mTLS 的流量来源:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看各工作负载收到的明文连接数
|
|||
|
|
kubectl exec -n istio-system deploy/istiod -- istioctl proxy-status
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
也可以从 Grafana 仪表盘中查看 `istio_requests_total{response_code=~"503"}` 的变化趋势——如果切到 STRICT 后某服务的 503 陡增,说明有非 mesh 流量在访问它。
|
|||
|
|
|
|||
|
|
修复完所有非 mesh 流量后,再升级到 STRICT:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
apiVersion: security.istio.io/v1beta1
|
|||
|
|
kind: PeerAuthentication
|
|||
|
|
metadata:
|
|||
|
|
name: default
|
|||
|
|
namespace: production
|
|||
|
|
spec:
|
|||
|
|
mtls:
|
|||
|
|
mode: STRICT
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### DestinationRule 中的端口级控制
|
|||
|
|
|
|||
|
|
某些端口可能不适合 mTLS,比如健康检查端点由 kubelet 发起,没有 Sidecar 证书。可以用 DestinationRule 做细粒度排除:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
apiVersion: networking.istio.io/v1beta1
|
|||
|
|
kind: DestinationRule
|
|||
|
|
metadata:
|
|||
|
|
name: my-service
|
|||
|
|
namespace: production
|
|||
|
|
spec:
|
|||
|
|
host: my-service.default.svc.cluster.local
|
|||
|
|
trafficPolicy:
|
|||
|
|
portLevelSettings:
|
|||
|
|
- port:
|
|||
|
|
number: 8080
|
|||
|
|
tls:
|
|||
|
|
mode: ISTIO_MUTUAL
|
|||
|
|
- port:
|
|||
|
|
number: 9090
|
|||
|
|
tls:
|
|||
|
|
mode: DISABLE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!note] 设计决策
|
|||
|
|
>
|
|||
|
|
> 这里的逻辑是:8080 端口接收来自其他服务的流量,必须走 mTLS;9090 端口只接收 kubelet 的 readiness probe,不需要额外的传输加密。这样既覆盖了服务间的安全需求,又避免了健康检查中断。
|
|||
|
|
|
|||
|
|
## 证书生命周期管理
|
|||
|
|
|
|||
|
|
Service Mesh 自动管理证书轮换,但你需要注意几个关键参数来平衡安全性和可用性:
|
|||
|
|
|
|||
|
|
| 参数 | 推荐值 | 说明 |
|
|||
|
|
|------|--------|------|
|
|||
|
|
| 证书有效期 | 24 小时 | 短生命周期降低泄露风险,过期后自动失效 |
|
|||
|
|
| 轮转提前量 | 1 小时 | 提前签发新证书避免新旧交接时的中断 |
|
|||
|
|
| 根 CA 轮换 | 按需手动触发 | 根密钥应长期保存、极少变动,每次轮换都是高风险操作 |
|
|||
|
|
|
|||
|
|
Sidecar 代理负责整个证书的申请和刷新过程,应用层代码通常无需感知:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func main() {
|
|||
|
|
// Istio Sidecar 自动处理以下事宜:
|
|||
|
|
// 1. 向 Citadel 申请服务身份证书
|
|||
|
|
// 2. 在到期前自动完成轮转
|
|||
|
|
// 3. 热更新 Envoy 的 TLS 上下文
|
|||
|
|
|
|||
|
|
// 你的业务代码照常写即可,不需要关心证书细节
|
|||
|
|
http.ListenAndServe(":8080", nilHandler{})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
如果需要直接读取证书文件做自定义处理(比如某些不走 Sidecar 的老服务),要注意处理文件变更事件并重新加载配置:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 直读证书目录时需要监听文件变更
|
|||
|
|
certFile := "/var/run/secrets/tls/tls.crt"
|
|||
|
|
keyFile := "/var/run/secrets/tls/tls.key"
|
|||
|
|
fsnotify.Watch(certFile, func(e fsnotify.Event) {
|
|||
|
|
cert, _ := tls.LoadX509KeyPair(certFile, keyFile)
|
|||
|
|
reloadTLSConfig(cert)
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!note] 解释
|
|||
|
|
>
|
|||
|
|
> 上面的示例展示了当服务不能依赖 Sidecar 时,如何自行管理证书生命周期。`fsnotify` 监控证书文件变化,检测到更新后用新的证书对重新初始化 TLS 配置,保证服务不会因证书过期而中断。
|
|||
|
|
|
|||
|
|
## 多集群与跨域场景
|
|||
|
|
|
|||
|
|
不同集群可能需要不同的 CA 签发证书,但又需要让它们之间能互相信任。关键是通过统一信任域实现跨集群身份对齐:
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
subgraph ClusterA["集群 A"]
|
|||
|
|
CA_A["CA A<br/>Signer ID: cluster-a.example.com"]
|
|||
|
|
svc_a["svc-a"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph ClusterB["集群 B"]
|
|||
|
|
CA_B["CA B<br/>Signer ID: cluster-b.example.com"]
|
|||
|
|
svc_b["svc-b"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
CA_A -.信任域互联.-> CA_B
|
|||
|
|
svc_a <-->|"mTLS 跨集群"| svc_b
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
配置跨集群信任的核心是让所有 Istio 实例共享同一个 `trust-domain`,同时在 meshConfig 中声明可信任的外部域:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
apiVersion: install.istio.io/v1alpha1
|
|||
|
|
kind: IstioOperator
|
|||
|
|
spec:
|
|||
|
|
values:
|
|||
|
|
global:
|
|||
|
|
trustDomain: example.com
|
|||
|
|
meshConfig:
|
|||
|
|
trustDomains:
|
|||
|
|
- example.com
|
|||
|
|
- other-cluster.example.com
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!summary] 跨集群 mTLS 的三点注意事项
|
|||
|
|
>
|
|||
|
|
> 1. **SPIFFE ID 的一致性**:证书的 SAN 字段必须包含正确的 trust-domain,否则对端会拒绝证书
|
|||
|
|
> 2. **网络可达性**:跨集群的 mTLS 要求 Pod CIDR 之间网络互通,还需要正确配置 Service Entry
|
|||
|
|
> 3. **CA 互信**:要么使用同一个根 CA,要么建立交叉信任链让两边都能验证对方的证书
|
|||
|
|
|
|||
|
|
## 常见问题排查
|
|||
|
|
|
|||
|
|
遇到 mTLS 相关的故障时,可以按以下步骤快速定位:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Step 1: 查看当前 Pod 持有的证书信息
|
|||
|
|
istioctl proxy-config secret <pod-name> -n <namespace>
|
|||
|
|
|
|||
|
|
# Step 2: 检查命名空间的 PeerAuthentication 策略
|
|||
|
|
kubectl get peerauthentication -n <namespace>
|
|||
|
|
|
|||
|
|
# Step 3: 查看是否有冲突的 DestinationRule
|
|||
|
|
kubectl get destinationrule -n <namespace> -o yaml | grep -A 5 tls
|
|||
|
|
|
|||
|
|
# Step 4: 确认 ServiceAccount 是否有证书签发权限
|
|||
|
|
kubectl auth can-i create certificatesigningrequests \
|
|||
|
|
--as=system:serviceaccount:<ns>:<sa-name>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
常见的三类故障及其应对思路:
|
|||
|
|
|
|||
|
|
| 症状 | 原因 | 解决方法 |
|
|||
|
|
|------|------|---------|
|
|||
|
|
| Pod 间调用返回 503,日志显示 certificate verify failed | 证书签发者不被信任 | 检查 PeerAuthentication 是否在正确的 Namespace 生效 |
|
|||
|
|
| 切到 STRICT 后部分服务超时 | 链路中有未注入 Sidecar 的跳板机 | 回到 PERMISSIVE,定位缺口后补装 Sidecar |
|
|||
|
|
| 证书过期后批量失败 | Istiod 异常导致部分 Sidecar 未能续约 | 重启受影响 Pod,排查 Istiod 日志 |
|
|||
|
|
|
|||
|
|
## 安全加固建议
|
|||
|
|
|
|||
|
|
除了基础配置外,以下几个维度也能进一步提升 mTLS 的安全性:
|
|||
|
|
|
|||
|
|
| 维度 | 建议 | 理由 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 证书长度 | RSA 2048 或 ECDSA P-256 | 性能与安全性的平衡点 |
|
|||
|
|
| 加密套件 | 仅允许 ECDHE + AES-GCM / ChaCha20 | 禁用 CBC 模式防 BEAST 等历史漏洞 |
|
|||
|
|
| 最小 TLS 版本 | TLS 1.2 | 旧版本协议存在已知的侧信道攻击 |
|
|||
|
|
| OCSP Stapling | 启用 | 加快证书吊销状态检查,减少握手延迟 |
|
|||
|
|
| SPIFFE ID 格式 | `spiffe://<trust-domain>/ns/<ns>/sa/<sa>` | 标准化标识,便于审计和自动化编排 |
|
|||
|
|
|
|||
|
|
> [!note] 解释
|
|||
|
|
>
|
|||
|
|
> 关于 TLS 版本的取舍:TLS 1.3 在密码学上更优,但会与部分老版本的 gRPC 库和 Envoy 产生兼容性问题。如果你的技术栈较新(Go 1.18+、Envoy 1.24+),优先启用 TLS 1.3;否则保守选 TLS 1.2 更为稳妥。
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
mTLS 是零信任架构中最值得投入的基础设施之一。它的核心价值不在于防御外部攻击——网关已经挡住了第一波流量——而在于限制内部横向移动。当某个 Pod 被入侵时,攻击者无法轻易监听或伪造其他服务间的通信。
|
|||
|
|
|
|||
|
|
记住一个简单的原则:**先宽松后严格,先观察后行动;证书不是装了就完事,要定期审计和轮换。**
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[09-网关鉴权策略]] — 网关层的 JWT/mTLS 分层鉴权设计
|
|||
|
|
- [[RBAC权限模型实战]] — 从 RBAC 到 ABAC 的演进路径
|
|||
|
|
- [[02-服务治理/02-安全机制]] — 服务间认证与授权的完整体系
|