TT Lab
开始
学习 学习路径 课程

ICA — Istio 认证助理

新增策略后支付API反而开放了

在 TT Lab 中继续学习

目标

在真实的 Istio sidecar 上,验证工作负载身份与 JWT 主体的组合,并修复放行范围被放大的事故。

为什么重要

仅凭文件名或 kubectl apply 成功,无法确认安全性。用更改了来源、令牌、方法、路径、端口的真实请求,同时确认正常放行与绕过拒绝。四个对比服务器会留到最后,在整体评分时也会再次检查。

环境与安全范围

只修改此 VM 内的 ica-policy 命名空间。这不是生产集群,也不是外部 IdP。k3s v1.35.8+k3s1 和 Istio 1.31.0 已准备好,orders、stranger 以及四个服务器的真实容器正在运行。公钥使用准备好的材料,不要放入真实用户令牌。union 是为复现事故而故意放宽放行范围的隔离对象。会话结束后,VM 和工作文件会被回收。

所有策略都使用 security.istio.io/v1,请求辅助工具是 python3 /opt/fixtures/ica-policy/runtime.py observe 。辅助工具不会修改策略,只输出实际的 HTTP 状态码和正文。应用策略后,请等待传播再重新观测。评分要连续两次得到相同结果才算通过。

步骤

  1. 查询 ica-policy 命名空间的 Pod 列表,只提取 apiVersion、kind 以及每个 items 的 metadata.name、metadata.uid,保存到 /root/ica-policy/inventory.json。包含 sidecar 详情的完整输出可能超过 128KiB 的输入限制。baseline、union、intersection、scoped、orders、stranger 这 6 个必须实际处于 Ready 并且带有 sidecar。如果重新创建了 Pod,请重新记录 UID。
  2. 在 /root/ica-policy/baseline.yaml 中编写并应用 ica-policy 的 allow-baseline AuthorizationPolicy。app=baseline,action=ALLOW,单条规则的 source.principals 只有 cluster.local/ns/ica-policy/sa/orders 一个,operation 为 POST 和 /v1/charges 各一个。不要加入 JWT 条件。通过 baseline 观测,确认 orders 的正常请求和无令牌请求为 200,stranger 带正常令牌的请求为 403。
  3. 只在隔离的 union 对象上,故意复现错误配置。在 /root/ica-policy/union.yaml 中编写并应用 ica-policy 的 jwt-union AuthorizationPolicy。app=union,action=ALLOW,在一条 rules 的一个 from 中只放 requestPrincipals=[https://issuer.example.invalid/*],不要放 to。保留已有的 allow-union。在 union 观测中,确认无令牌的 orders、带正常令牌的 stranger,乃至正常 orders 的 GET /admin/test 都是 200。不要应用到生产环境。
  4. 在 /root/ica-policy/intersection.yaml 中编写并应用 ica-policy 的 allow-intersection 策略。app=intersection,action=ALLOW,在单条规则的同一个 source 中同时放入 principals=[cluster.local/ns/ica-policy/sa/orders] 和 requestPrincipals=[https://issuer.example.invalid/*]。同一条规则的 operation 只允许 POST 和 /v1/charges。把已有的 allow-intersection 替换为这个内容,但不要添加其他 ALLOW。只有正常的 orders 为 200,无令牌、stranger、其他路径必须为 403。
  5. 在 /root/ica-policy/scoped.yaml 中编写并应用 ica-policy 的 jwt-scoped 策略。app=scoped,action=DENY,在单条规则中同时放入 from.source.notRequestPrincipals=[*] 和 to.operation.ports=["8080"]。保留已有的 allow-scoped 不动,不要加入额外的条件、规则、dry-run。只有正常 orders 的支付调用为 200,无令牌、stranger、其他路径必须为 403。
  6. intersection 的初始 JWT 配置同时允许 payments-api 和 other-api 两个 audience。通过 audience 观测确认这一点之后,在 /root/ica-policy/authentication.yaml 中编写并应用 ica-policy 的 jwt-intersection RequestAuthentication。app=intersection,jwtRules 中一条规则的 issuer=https://issuer.example.invalid, audiences=[payments-api], jwks 是 /opt/fixtures/ica-policy/public-jwks.json 中真实公钥的 JSON 字符串。不要放 jwksUri 或额外的签发者。请连同正文一起确认:正常令牌为 200,其他 audience 为 403,其他签发者、过期、格式错误为 401。
  7. 把 ports 观测命令输出的 JSON 保存到 /root/ica-policy/ports.json。orders 在无令牌的情况下向 scoped 调用 POST /v1/charges 时,8080 的响应必须是 403/RBAC 拒绝,8081 的响应必须是 200/synthetic-order。不要手工把观测结果改成成功值。评分会重新发送当前请求来对照。
  8. 在 /root/ica-policy/incident.json 中写入 allow_composition(策略之间是 OR 还是 AND)、source_fields(同一个 source 的字段之间是 OR 还是 AND)、jwt_missing_fix(scoped 中使用的 action)、deny_ports(被保护的字符串端口数组)、audience_error(JWT audience 被拒绝的层:jwt_authn 或 rbac)、principal_error(工作负载身份授权被拒绝的层:jwt_authn 或 rbac)。请保留正常的 intersection 和 scoped 的端口边界。

参考

记录当前网格中的 Pod 身份

查询 ica-policy 命名空间的 Pod 列表,只提取 apiVersion、kind 以及每个 items 的 metadata.name、metadata.uid,保存到 /root/ica-policy/inventory.json。包含 sidecar 详情的完整输出可能超过 128KiB 的输入限制。baseline、union、intersection、scoped、orders、stranger 这 6 个必须实际处于 Ready 并且带有 sidecar。如果重新创建了 Pod,请重新记录 UID。

名称相同并不意味着是同一个 Pod。请区分 UID、Ready 以及实际的 istio-proxy 容器。

不带令牌的正常工作负载也能通过的基线

在 /root/ica-policy/baseline.yaml 中编写并应用 ica-policy 的 allow-baseline AuthorizationPolicy。app=baseline,action=ALLOW,单条规则的 source.principals 只有 cluster.local/ns/ica-policy/sa/orders 一个,operation 为 POST 和 /v1/charges 各一个。不要加入 JWT 条件。通过 baseline 观测,确认 orders 的正常请求和无令牌请求为 200,stranger 带正常令牌的请求为 403。

是否有正常 JWT 与来源工作负载是两个不同的维度。baseline 只限制来源、方法、路径。

单独添加 JWT 放行以复现事故

只在隔离的 union 对象上,故意复现错误配置。在 /root/ica-policy/union.yaml 中编写并应用 ica-policy 的 jwt-union AuthorizationPolicy。app=union,action=ALLOW,在一条 rules 的一个 from 中只放 requestPrincipals=[https://issuer.example.invalid/*],不要放 to。保留已有的 allow-union。在 union 观测中,确认无令牌的 orders、带正常令牌的 stranger,乃至正常 orders 的 GET /admin/test 都是 200。不要应用到生产环境。

新增的放行并不是让已有放行变得更严格的过滤器。请分别说明是哪个条件让每个请求通过的。

在同一个 source 中结合两种身份

在 /root/ica-policy/intersection.yaml 中编写并应用 ica-policy 的 allow-intersection 策略。app=intersection,action=ALLOW,在单条规则的同一个 source 中同时放入 principals=[cluster.local/ns/ica-policy/sa/orders] 和 requestPrincipals=[https://issuer.example.invalid/*]。同一条规则的 operation 只允许 POST 和 /v1/charges。把已有的 allow-intersection 替换为这个内容,但不要添加其他 ALLOW。只有正常的 orders 为 200,无令牌、stranger、其他路径必须为 403。

把两种身份分放在 from 的不同条目中,与放进同一个 source,是不同的。

把“无 JWT 则 DENY”限定在 HTTP 端口上

在 /root/ica-policy/scoped.yaml 中编写并应用 ica-policy 的 jwt-scoped 策略。app=scoped,action=DENY,在单条规则中同时放入 from.source.notRequestPrincipals=[*] 和 to.operation.ports=["8080"]。保留已有的 allow-scoped 不动,不要加入额外的条件、规则、dry-run。只有正常 orders 的支付调用为 200,无令牌、stranger、其他路径必须为 403。

如果只留下 DENY,不满足拒绝条件的请求的最小权限可能会消失。请把已有的 ALLOW 和拦截范围放在一起看。

阻止把用于其他 API 的令牌拿来复用

intersection 的初始 JWT 配置同时允许 payments-api 和 other-api 两个 audience。通过 audience 观测确认这一点之后,在 /root/ica-policy/authentication.yaml 中编写并应用 ica-policy 的 jwt-intersection RequestAuthentication。app=intersection,jwtRules 中一条规则的 issuer=https://issuer.example.invalid, audiences=[payments-api], jwks 是 /opt/fixtures/ica-policy/public-jwks.json 中真实公钥的 JSON 字符串。不要放 jwksUri 或额外的签发者。请连同正文一起确认:正常令牌为 200,其他 audience 为 403,其他签发者、过期、格式错误为 401。

不要新生成密钥。把准备好的公钥以字符串形式插入,并把 audience 允许列表只收窄到所需范围。

区分被保护的端口和保留的端口

把 ports 观测命令输出的 JSON 保存到 /root/ica-policy/ports.json。orders 在无令牌的情况下向 scoped 调用 POST /v1/charges 时,8080 的响应必须是 403/RBAC 拒绝,8081 的响应必须是 200/synthetic-order。不要手工把观测结果改成成功值。评分会重新发送当前请求来对照。

不要混淆 port 与 targetPort,请确认已应用策略中的字符串 ports 列表。这一步并不是要把 8081 也保护起来。

报告策略组合事故的原因

在 /root/ica-policy/incident.json 中写入 allow_composition(策略之间是 OR 还是 AND)、source_fields(同一个 source 的字段之间是 OR 还是 AND)、jwt_missing_fix(scoped 中使用的 action)、deny_ports(被保护的字符串端口数组)、audience_error(JWT audience 被拒绝的层:jwt_authn 或 rbac)、principal_error(工作负载身份授权被拒绝的层:jwt_authn 或 rbac)。请保留正常的 intersection 和 scoped 的端口边界。

不要把所有 403 都当作同一种错误。请把响应正文与是哪个策略拒绝了该请求联系起来报告。