Day 14 / 共 20 天 · 第 3 周 sidecar 与安全

mTLS 与 CA

服务网格最大的安全卖点:服务间自动双向 TLS(mTLS)——零改代码就让所有服务间通信加密 + 双向认证。今天看 istiod 内建的 CA 怎么给每个工作负载签发身份证书。Day 12 说管家会"发证书",但证书从哪来、凭什么可信?今天补上这块——它是整个安全体系的地基。

📍 你在整门课的位置 · 第 3 周「sidecar 与安全」(W1 控制面全景 ✓ · W2 xDS 生成 ✓)
D11 sidecar 注入 D12 pilot-agent D13 bootstrap D14 mTLS·CA D15 SDS·授权
L01

mTLS 是什么

🤔 痛点:集群内网就一定安全吗? 很多人觉得"服务都在内网,明文通信没事"。可一旦有一个 Pod 被攻破,攻击者就能在内网随意嗅探流量(偷数据)、冒充任意服务发请求(横向移动)——因为大家彼此不验证身份。光靠网络隔离早已不够。
💡 本质:进门先"互验工牌",全程加密对讲 mTLS 就像公司里两个员工碰头办事:不光你要看对方工牌(服务端出证书),对方也要看你的工牌(客户端出证书)——双向亮证。工牌由公司行政部(CA)统一签发,谁都伪造不了。验完身份,两人还用只有彼此懂的暗号说话(加密)。于是就算内网里混进坏人,没有合法工牌他既进不来(认证)也听不懂(加密)。今天的核心问题就是:这张"工牌"(证书)谁发、凭什么信。
⚠️ 常见误解:以为 mTLS 只是"把 HTTP 变 HTTPS 加个密"。其实它比普通 TLS 多了关键一环——双向身份认证。普通 HTTPS 只有你验证网站,mTLS 是双方互验。加密防窃听,认证防冒充,两个好处缺一不可。
mTLS = "双向亮证件" 普通 HTTPS(TLS)只有服务端出示证书(你访问银行网站,验证网站是真的)。mTLS(mutual TLS,双向 TLS)则要求双方都出示证书——客户端也要证明自己是谁。在服务网格里:服务 A 调服务 B,A 和 B 各自出示证书互相验证身份,通信全程加密。好处:①流量加密(防窃听);②身份认证(防冒充——只有持有合法证书的服务才能通信)。而且这一切由 sidecar Envoy 自动完成,业务代码零改动。关键问题:证书从哪来?谁签发?谁保证证书对应的身份可信?这就是 CA 的活。
L02

SPIFFE 身份

// pkg/spiffe/spiffe.go
// type Identity struct{ TrustDomain; Namespace; ServiceAccount }(:56)
// String()(:80)→ spiffe://<trust-domain>/ns/<ns>/sa/<sa>
SPIFFE = "服务的身份证号规范" 人有身份证号,服务也需要统一的身份标识。SPIFFE 是一个开放标准,规定服务身份写成 spiffe://集群/ns/命名空间/sa/服务账号 的格式。比如 spiffe://cluster.local/ns/default/sa/reviews 表示"default 命名空间里 reviews 服务账号的工作负载"。Istio 把这个 SPIFFE 身份写进证书的 SAN(主体备用名)字段——于是"验证证书"就等于"验证服务身份"。mTLS 时对端一看证书里的 SPIFFE URI,就知道"你是谁",据此做授权(Day 15)。PeerCertVerifier:319)就是校验对端证书 SAN 的。
📝 举个例子:一张工牌上的身份证号 default 命名空间、服务账号 reviews 的工作负载 → 它的 SPIFFE 身份就是 spiffe://cluster.local/ns/default/sa/reviews → istiod 把这串字符写进证书的 SAN 字段。 于是 mTLS 握手时,对端 Envoy 一读证书 SAN,就知道"哦,来的是 default/reviews",据此决定放不放行(Day 15 授权)。验证书 = 验身份,就是这么接上的。
L03

内建 CA

// security/pkg/pki/ca/ca.go
// NewSelfSignedIstioCAOptions(:127):优先读 istio-ca-secret,否则 cacerts,
//   都没有就自签根 CA 并写回 Secret
// NewPluggedCertIstioCAOptions(:288):加载运维提供的密钥/证书(cacerts)
// IstioCA.sign(:478):校验 CSR 签名 → 按 maxCertTTL 夹逼 TTL → GenCertFromCSR 签出
CA = "签发身份证的机关" CA(Certificate Authority,证书颁发机构)是"信任的根"。istiod 内建了一个 CA——它要么自己生成一个根证书(自签,测试/默认),要么用运维提供的根证书(生产,接企业 PKI)。所有工作负载的证书都由这个 CA 签发。因为大家的证书都来自同一个 CA,所以能互相验证——"你的证书是我们共同信任的 CA 签的,那我信你"。这就是 PKI(公钥基础设施)的信任传递。根证轮转(selfsignedcarootcertrotator.go)保证根证快过期时平滑更换。
L04

证书签发 RPC

// security/pkg/server/ca/server.go:75 CreateCertificate(每个工作负载调的 RPC)
//   :79 security.Authenticate(ctx, s.Authenticators)  // 先认证调用者身份
//   :113 组 ca.CertOpts(SAN = caller.Identities)
//   :123 Sign / SignWithCertChain
//   :142 返回 叶证书 + 证书链 + 根证
读法:工作负载(通过 pilot-agent,Day 12)向 istiod 的 CA 发 CreateCertificate gRPC,带一个 CSR(证书签名请求)。CA 先认证调用者是谁(L05),再据此身份签发证书(SAN 填对应的 SPIFFE 身份)。关键:SAN 不是调用者随便填的,而是 CA 根据认证结果决定的——防止冒充。
L05

JWT → 身份认证

// security/pkg/server/ca/authenticate/kubeauth/kube_jwt.go:119 authenticate
//   :126 tokenreview.ValidateK8sJwt(...)  // 用 K8s TokenReview 验证 JWT
//   :136 返回 Caller{Identities: [spiffe.MustGenSpiffeURI(mesh, ns, sa)]}  // JWT→SPIFFE
"谁能拿到 token,谁就是那个身份" 工作负载怎么向 CA 证明"我就是 reviews 服务"?靠 Kubernetes 的 ServiceAccount Token(一个 JWT)。注入时(Day 11)给 Pod 挂载了 istio-token(该 Pod 的 SA JWT)。agent 用这个 JWT 作为凭证调 CA。CA 的 KubeJWTAuthenticator 用 K8s 的 TokenReview API 验证这个 JWT 真伪,验证通过就把"命名空间 + 服务账号"翻译成 SPIFFE 身份,作为证书 SAN 签发。于是信任链是:K8s 保证"只有 reviews Pod 能拿到 reviews 的 SA token" → 谁持有该 token 谁就拥有该 SPIFFE 身份。这是把 K8s 的身份体系"桥接"到 mTLS 证书体系的关键一步。
👶 小白 vs 👨‍🏫 老师 👶:既然 agent 自己发 CSR,它能不能填一个"我是 admin"的身份骗到超级证书?
👨‍🏫:填不了。SAN(身份)不是调用者说了算,而是 CA 根据"你出示的那个 JWT 属于谁"来决定的。你只有 reviews 的 SA token,CA 就只给你签 reviews 的身份。想冒充 admin?你得先偷到 admin 的 SA token——而 K8s 保证只有 admin 的 Pod 能拿到它。信任的根扎在 K8s 的 SA 体系上。
L06

证书轮转

证书有有效期(默认较短,如 24h)。SecretManagerClientsecurity/pkg/nodeagent/cache/secretcache.go)在 agent 侧管理:generateNewSecret:572)生成 CSR 调 CA;rotateTime:652)带 jitter 的宽限期;快过期就自动重签、通过 OnSecretUpdate 推给 Envoy 热更新(Day 12 SDS)。

为什么证书要"短命 + 自动轮转"? 传统证书有效期几个月甚至几年——一旦泄露,攻击者能用很久。Istio 用"短命证书"(几小时到一天)+ 自动轮转:证书快过期,agent 自动重新申请、无缝替换。好处:即使某张证书泄露,很快失效,危害窗口极小;且全自动,运维零负担。这是"零信任安全"的实践——不信任任何长期凭证,持续重新验证身份。jitter(随机抖动)避免所有 Pod 同时轮转打爆 CA。
L07

端到端信任链

完整信任链(串起来)
① 注入时给 Pod 挂 istio-token(K8s SA JWT,Day 11)
② agent 用 JWT 调 istiod CA 的 CreateCertificate(Day 12)
③ CA 的 KubeJWTAuthenticator 用 TokenReview 验 JWT → 翻译成 SPIFFE 身份(今天)
④ CA 用根证签发工作负载证书(SAN = SPIFFE 身份)
⑤ agent 缓存证书、定时轮转,通过 SDS 推给 Envoy(Day 12/15)
⑥ 两个 Envoy 建 mTLS 连接,各自校验对端证书的 SPIFFE 身份
K8s 的 SA 体系 → JWT → CA 认证 → SPIFFE 证书 → mTLS。每一环都可验证,没有长期明文密钥。这就是 Istio "零信任、自动 mTLS" 的完整实现。
K8s SA JWT注入时挂载(Day11) CA 认证 JWTTokenReview 验真→ SPIFFE 身份 CA 签发证书SAN=SPIFFE SDS 推给Envoy(Day15) mTLS双向互验 「谁能拿到 token → 谁就拥有那个身份 → 谁就拿到对应证书」环环可验、无长期明文密钥
端到端信任链:K8s 的身份体系被一步步"桥接"成 mTLS 证书身份。每一环都能被独立验证,这就是"零信任"。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • mTLS 和普通 TLS 的区别?服务网格 mTLS 的两大好处?
  • SPIFFE 身份格式?它和证书什么关系?
  • 内建 CA 的作用?自签 vs 插件根证?
  • CreateCertificate RPC 怎么防冒充?JWT 怎么变成 SPIFFE 身份?
  • 证书为什么要短命+轮转?端到端信任链的六步?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/istio
grep -n 'func (i Identity) String\|type Identity struct' pkg/spiffe/spiffe.go
grep -n 'func (s \*Server) CreateCertificate\|func (ca \*IstioCA) sign' security/pkg/server/ca/server.go security/pkg/pki/ca/ca.go
grep -n 'func.*authenticate\|MustGenSpiffeURI' security/pkg/server/ca/authenticate/kubeauth/kube_jwt.go
明天预告 · Day 15(第3周收官)SDS + 认证授权——SDS 怎么把证书分发给 Envoy、PeerAuthentication 怎么控制 mTLS 模式、AuthorizationPolicy 怎么翻译成 Envoy RBAC。安全策略的完整落地。
← Day 13 bootstrap Day 15 · SDS + 认证授权 →