有了 mTLS 为什么还要验证 JWT
一句话总结
在服务网格中,“是谁发出了请求”有两个答案。哪个工作负载建立了连接,由 mTLS 证书证明;请求代表哪个人,由 JWT 证明。sidecar 代理两者都能验证,但二者不能互相替代。
为什么需要它
在前面的模块中,令牌验证一直是应用代码的职责。如果有二十个服务,就有二十处相同的验证代码,其中一处漏掉了 aud 检查,或者库的更新慢了一拍,就会出问题。网格把这项验证转移到每个服务前面的代理上。验证规则变成一处配置,错误的令牌在到达应用代码之前就会被拦下。
不过引入网格后,人们常常产生这样的错觉:“服务之间已经启用了 mTLS,认证就完成了。”mTLS 证明的是建立连接的工作负载——连到订单服务上的是前端 Pod 这一事实。这个请求是 Alice 的还是 Bob 的,证书里并没有。这正是 Istio 安全概念文档把认证拆分为 peer authentication(服务对服务)和 request authentication(最终用户)来讲解的原因。
工作原理
Envoy 的 jwt_authn 过滤器会验证签名、签发者和受众。公钥以 JWK Set 的形式获取,除了远程 URL,也可以通过文件或内联字符串(local_jwks)提供。用令牌头部的 kid 选择使用哪把密钥——轮换密钥时能把旧密钥和新密钥放在同一个 JWKS 中,靠的就是这一点。
配置参考中本模块用到的字段如下。
forward 기본값 false. 검증에 성공하면 원래 토큰을 요청에서 지운다.
forward_payload_header 검증된 페이로드를 base64url(JSON) 으로 이 헤더에 실어 보낸다.
payload_in_metadata 헤더 대신 동적 메타데이터에 넣는다(RBAC 같은 다음 필터용).
rules 경로별 요구. 처음 맞는 규칙이 이기고, requires 가 비면 검증하지 않는다.
allow_missing_or_failed 없든 틀리든 통과시킨다 — 잘못 쓰면 필터가 장식이 된다.
forward 默认关闭的原因很重要。上游只需要收到已验证的声明,而收到原始令牌的上游可以把该令牌再用到其他服务上。令牌流转的距离越短,可能泄露的地方就越少。
拒绝码也要区分。令牌缺失、签名错误或已过期时是 401,签名正确但 aud 不是这个服务时是 403。RFC 7519 写明,如果处理方不在 aud 中,就必须(MUST)拒绝该 JWT。
mTLS 这边是监听器的 TLS 配置。开启 require_client_certificate 后,没有有效客户端证书的连接会被拒绝。已验证证书的信息通过 x-forwarded-client-cert(XFCC)请求头传给上游。URI 键中放的是证书的 URI SAN,在网格里则是形如 spiffe://신뢰도메인/...(占位符为信任域)的 SPIFFE ID。HCM 的 forward_client_cert_details 默认是 SANITIZE,会丢弃收到的 XFCC,而 SANITIZE_SET 在 mTLS 连接时丢弃收到的值,并用自己验证过的值重新填充。
在现场相遇的样子
最常见的事故是“相信请求头”的上游。上游把 x-jwt-payload 当作身份来信任,如果客户端经由代理跳过验证的公共路径,直接附上这个请求头发送,上游就会照单全收这个假身份。在做验证的路径上,Envoy 会用验证后的值覆盖该请求头,但公共路径上没有人去动它。所以在公共路径上要显式删除这个请求头。
第二个是无令牌的请求。Istio 文档写明,只配置 RequestAuthentication 时,没有令牌的请求默认会通过,要拒绝就需要另外设置授权规则。“有令牌就检查”和“必须有令牌”是不同的配置。直接用 Envoy 来写的话,带 requires 的规则就是这两者的差别。
下一项实验要做什么
用 cryptography 生成 RS256 密钥和 JWKS,并手工签发令牌。给 Envoy 配上 jwt_authn,确认只有有效令牌能到达上游,缺失、伪造、过期的是 401,受众不同的是 403,都在上游之前被拦下。把验证过的声明作为请求头传下去,并在公共路径上删除伪造的请求头。最后加上 mTLS 链,查看工作负载身份和用户身份是分别到达的。