证明“是谁”不等于证明“能做什么”

orders 以正确工作负载证书连接库存,只说明对端代理能够验证它的工作负载身份;一名最终用户带有效 JWT,也只说明令牌在指定签发者、密钥、受众及有效期等规则下通过校验。业务是否放行,还要看作用于 inventory 的授权策略与请求属性。对等身份来自 mTLS,最终用户身份来自请求令牌;两者不能互相替代,更不能把客户端自填的 X-Request-ID 当作认证结果。

Istio 的 RequestAuthentication 有一个很容易写错的边界:携带了不合法的令牌会被拒绝;没有携带令牌的请求默认仍能进入后续处理。如果业务要求“必须提供有效 JWT”,还需增加匹配 requestPrincipals 的 AuthorizationPolicy。在 HTTP 路由中,拒绝可能发生于请求认证、授权、TLS 握手,甚至应用自身;只有确认策略作用域、代理记录和应用是否收到该请求,才能说清拒绝位置。

mTLS 工作负载身份、JWT 用户主体与授权规则按不同条件检查,同一条请求可在不同位置被拒绝

把两种身份与两阶段策略拆开测试

仍使用 orders → inventory-v1。先为两个工作负载配置不同 ServiceAccount,确认目标确实启用入站 mTLS;为隔离的测试 JWT 签发者配置 RequestAuthentication,固定 issuer、JWKS、audience,并禁止日志或证据目录保存令牌本身。第一阶段不施加要求令牌的 ALLOW 规则,让带有效令牌、不带令牌、带坏签名令牌的三种请求分别经过库存入站代理。按官方示例的认证语义,前两种可以继续处理,坏令牌会在请求认证处被拒绝;结果若不同,先检查资源是否选中了正确目标。

第二阶段再给同一库存工作负载配置限定来源为 orders 对等身份、且要求有效 requestPrincipals 的 ALLOW 策略。分别发来自正确 ServiceAccount+有效令牌、错误 ServiceAccount+有效令牌、正确 ServiceAccount+无令牌、正确 ServiceAccount+坏令牌四组请求。每组用不同请求 ID 关联入站代理和库存应用日志:期望第一组允许,第二组因对等身份不符拒绝,第三组因缺少用户主体拒绝,第四组在 JWT 校验阶段拒绝。JWT 校验失败常在 HTTP 层表现为 401,授权拒绝常表现为 403,但应以实际代理详情为准;mTLS 失败可能根本没有 HTTP 响应。

需要比较 DENY 与 ALLOW 时,单独加入一条只针对实验路径的 DENY,证明即使已有 ALLOW 也不会覆盖它;结束前撤回策略,避免后续章节误把遗留规则当新故障。策略声明要连同其命名空间、selector、匹配路径、目标代理当前配置一起保存。对裸 TCP 不要套用要求 HTTP 路径/JWT 的断言。

用例 预期判断依据 实际 HTTP/连接、拒绝位置和应用记录
正确对等身份+合法 JWT 入站身份、用户主体均匹配 NOT_RUN:无 Istio 集群
错误对等身份+合法 JWT mTLS 已认证但授权来源不匹配 NOT_RUN
正确对等身份+无 JWT 仅配置请求认证时可通过;要求主体后拒绝 NOT_RUN
正确对等身份+坏 JWT 认证失败,不等同于授权拒绝 NOT_RUN

用本机临时签名密钥执行四组身份对照

以下命令在 examples/service-mesh 目录执行,使用 04 章 Kubernetes 1.33.4 / Istio 1.27.0 sidecar 基线,应用容器名 app。先清理前章授权、故障与出口策略;下列预期值尚不是集群实测结果。

1
2
3
4
5
export SM_CONTEXT=kind-service-mesh-sidecar
k() { kubectl --context="$SM_CONTEXT" -n service-mesh-lab "$@"; }
k apply -f mainline/13/probe.yaml
k rollout status deploy/sm-probe --timeout=120s
OUT="evidence/mainline-21/$(date -u +%Y%m%dT%H%M%SZ)"; mkdir -p "$OUT"

mainline/21/issue.py 用 OpenSSL 生成临时 RSA 密钥和 1 小时有效期的 RS256 JWT,受众固定 inventory;一个令牌签名正确,另一个只损坏签名字节。生成器同时验证这两个签名的正反例。公钥以内联 JWKS 放入 RequestAuthentication,实验不依赖公网 JWKS 服务。目录权限为 0700,令牌和私钥不进入证据目录或 Git。本地签名正反例记录见 examples/service-mesh/evidence/verification/mainline-13-25/jwt.log 与 jwt.exit;它只证明生成器能识别坏签名,不证明代理已经执行 JWT 准入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
JWT_DIR="$(mktemp -d)/jwt"; python3 mainline/21/issue.py "$JWT_DIR"
k apply -f mainline/20/strict.yaml
k apply -f mainline/19/alternate.yaml
k rollout status deploy/sm-probe-alt --timeout=120s
k apply -f "$JWT_DIR/request-authentication.json"
k exec deploy/sm-probe -c curl -- curl -H 'x-request-id: ch21-anonymous-before' -fsS 'http://inventory:8080/quote?sku=paper' > "$OUT/anonymous-before.json"
k exec -i deploy/sm-probe -c curl -- sh -c 'read -r token; curl -H "x-request-id: ch21-invalid-code" -sS -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $token" http://inventory:8080/quote?sku=paper' < "$JWT_DIR/invalid.token" > "$OUT/invalid-code.txt"
k apply -f mainline/21/allow.yaml
k exec -i deploy/sm-probe -c curl -- sh -c 'read -r token; curl -H "x-request-id: ch21-allowed" -fsS -H "Authorization: Bearer $token" http://inventory:8080/quote?sku=paper' < "$JWT_DIR/valid.token" > "$OUT/allowed.json"
k exec -i deploy/sm-probe-alt -c curl -- sh -c 'read -r token; curl -H "x-request-id: ch21-wrong-sa" -sS -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $token" http://inventory:8080/quote?sku=paper' < "$JWT_DIR/valid.token" > "$OUT/wrong-sa.txt"
k exec deploy/sm-probe -c curl -- curl -H 'x-request-id: ch21-missing-token' -sS -o /dev/null -w '%{http_code}\n' 'http://inventory:8080/quote?sku=paper' > "$OUT/missing-token.txt"
test "$(cat "$OUT/invalid-code.txt")" = 401
test "$(cat "$OUT/wrong-sa.txt")" = 403
test "$(cat "$OUT/missing-token.txt")" = 403
k apply -f mainline/21/deny.yaml
k exec -i deploy/sm-probe -c curl -- sh -c 'read -r token; curl -H "x-request-id: ch21-deny-code" -sS -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $token" http://inventory:8080/quote?sku=paper' < "$JWT_DIR/valid.token" > "$OUT/deny-code.txt"
test "$(cat "$OUT/deny-code.txt")" = 403
k delete -f mainline/21/deny.yaml
k delete -f mainline/21/allow.yaml
k delete -f "$JWT_DIR/request-authentication.json"
k exec deploy/sm-probe -c curl -- curl -H 'x-request-id: ch21-recovered' -fsS 'http://inventory:8080/quote?sku=paper' > "$OUT/recovered.json"

随后删除 mainline/19/alternate.yaml、mainline/20/strict.yaml 和临时 JWT_DIR。命令通过 stdin 传令牌,避免将令牌展开到 kubectl 命令参数或输出日志;不要启用 shell tracing。allow.yaml 在同一个 source 条件中同时要求 orders 的 mTLS principal 与 issuer/alice 的 requestPrincipal,二者是 AND。DENY 加入后即使令牌与身份正确也应拒绝,删除后恢复,再撤回全部实验策略。

此处 curl 从 orders 身份探针直接请求库存,应用链未自动转发 Authorization;不能拿 frontend 未传令牌导致的失败误判成 JWT 校验故障。内联 JWKS 不验证远程密钥抓取、缓存和轮换,那些仍需独立实验。

固定源码入口:Istio 1.27.0 pilot/pkg/networking/plugin/authz/authorization.go。该链接用于核对配置生成或执行机制;完整资源和命令保存在 examples/service-mesh/mainline/21/。静态检查通过不等于集群实验通过。

练习与参考解答

  1. 给库存服务加了 RequestAuthentication,匿名请求仍成功,是否说明 JWT 策略失效?参考解答:不一定;该资源验证已携带的令牌,要求必带需要单独的授权条件。
  2. 有效 JWT 的请求被拒绝,能否直接换一张 JWT 重试?参考解答:先检查 mTLS 来源、授权策略作用域、请求路径、受众和代理拒绝详情;有效用户令牌并不保证工作负载有权限。

官方资料与系列导航

系列导航:20 mTLS 开启后哪些连接受到保护 → 21 认证成功为何仍会被拒绝(本文)→ 22 访问日志怎样区分代理失败和业务失败。