合作方日志看到的来源
目标
在真实的 Istio 网格中依次设置 Sidecar 范围、REGISTRY_ONLY、ServiceEntry、出口网关和网关授权,并通过接收方的日志和状态码,确认每一个分别拦住了什么、拦不住什么。
为什么重要
出站流量的默认值是“去哪儿都放行”,所以什么都不做的话,网格并不知道谁在往哪里出站。即使设置了这些手段,如果分不清它们是命名空间配置还是网关的强制,就会留下“以为已经拦住”的漏洞。养成用接收方(合作方)看到的来源来确认的习惯,就能看出这个漏洞。
步骤
- 用
kubectl apply -f /opt/fixtures/istlab/egress-app.yaml部署材料(网格外outside中的 partner、网格内shop中的 client、other中的 intruder),等待 Pod 就绪。然后在/root/istlab-egress/01-baseline.txt中写入三行——在 client 中访问http://partner.outside/的状态码svc=,直接用 partner 的 Pod IP 访问的状态码podip=,client sidecar 所知的集群数量clusters=(istioctl proxy-config clusters client.shop -o json | jq length)。 - 在
/root/istlab-egress/sidecar.yaml中写入并应用命名空间shop的Sidecardefault——egress hosts 只有./*(同一命名空间)和istio-system/*两个。应用后,把 client 的集群数量以clusters=写入/root/istlab-egress/02-scope.txt。 - 在
/root/istlab-egress/sidecar.yaml的 Sidecar 中加入outboundTrafficPolicy.mode: REGISTRY_ONLY并重新应用。然后在 client 中带上x-request-id访问 partner 的 Pod IP,并把该请求留下的 client sidecar 访问日志中的那一行原样保存到/root/istlab-egress/03-blocked.log。 - 在
/root/istlab-egress/serviceentry.yaml中写入并应用命名空间shop的ServiceEntrypartner-api——主机api.partner.test,location: MESH_EXTERNAL,端口 80 HTTP,resolution: DNS,端点地址partner.outside.svc.cluster.local。应用后,把在 client 中解析api.partner.test得到的地址以vip=写入/root/istlab-egress/04-se.txt,并把http://api.partner.test/的状态码以code=写入。 - 在
/root/istlab-egress/egress.yaml中写入并应用三个资源——(1) 命名空间shop的 IstioGatewaypartner-egress(选择器istio: egressgateway,端口 80、协议 HTTPS、tls.mode: ISTIO_MUTUAL,主机api.partner.test),(2)istio-system的DestinationRuleegressgateway-for-partner(主机istio-egressgateway.istio-system.svc.cluster.local,子集partner的端口 80 使用ISTIO_MUTUAL和sni: api.partner.test),(3)shop的VirtualServicepartner-via-egress(主机api.partner.test,gateways 为partner-egress和mesh——来自mesh的请求转到出口网关的子集partner的 80 端口,来自网关的请求转到api.partner.test的 80 端口)。应用后,在/root/istlab-egress/05-path.txt中写入出口网关 Pod 的 IPegress_pod_ip=,以及针对在路径中附加了标记发出的请求,partner 日志所记录的来源partner_saw=。 - 在
other命名空间的 intruder 中访问两次,并写入/root/istlab-egress/06-bypass.txt——直接访问http://partner.outside/的状态码intruder_direct=,http://api.partner.test/的状态码intruder_via_egress=。再把在 client 中做同样直接访问的结果以client_direct=补充进去。 - 在
/root/istlab-egress/egress-authz.yaml中写入并应用istio-system的AuthorizationPolicypartner-only-shop——选择器istio: egressgateway,action: ALLOW,来源主体cluster.local/ns/shop/sa/client,目标主机api.partner.test。应用后,client 访问api.partner.test应为 200,intruder 发出的同样请求应为 403。 - 在
/root/istlab-egress/08-report.md中写入六行——clusters_before=、clusters_after=(第 1、2 步),blocked_code=(第 3 步日志行中的状态码),partner_saw=(第 5 步中 partner 看到的来源是出口网关则为egressgateway,是 client 则为client),intruder_direct=、intruder_via_egress=(第 6 步)——并在其下写出学到的内容,至少四行。
参考
- 这台 VM 的准备需要 2–4 分钟。sidecar 的 DNS 代理已开启,所以可以按名称访问 ServiceEntry 主机。
- 在 client 中输入命令时,要像
kubectl -n shop exec client -c curl -- curl …那样指定容器。 - partner 的日志用
kubectl -n outside logs partner查看,网关日志用kubectl -n istio-system logs deploy/istio-egressgateway查看。在 sidecar 日志中要带上请求 ID 来查找;经过网关的请求,网关会重新生成 ID,所以要在路径中附加标记来查找。 - 修改配置之后,要过几秒钟才会扩散到代理。
- 常见错误——在 VirtualService 中漏掉
mesh网关。这样 sidecar 不会经过出口网关而是直接出站,合作方日志里记录的是 client 的 Pod IP。
默认值下去哪儿都能出站
用 kubectl apply -f /opt/fixtures/istlab/egress-app.yaml 部署材料(网格外 outside 中的 partner、网格内 shop 中的 client、other 中的 intruder),等待 Pod 就绪。然后在 /root/istlab-egress/01-baseline.txt 中写入三行——在 client 中访问 http://partner.outside/ 的状态码 svc=,直接用 partner 的 Pod IP 访问的状态码 podip=,client sidecar 所知的集群数量 clusters=(istioctl proxy-config clusters client.shop -o json | jq length)。
网格默认的外部策略是 ALLOW_ANY。前往 sidecar 不认识的目的地(不是服务的 Pod IP、外部地址)的请求,会经 PassthroughCluster 原样放走。而且 sidecar 默认知道集群中的所有服务,所以服务一多,所有代理的配置都会随之变大。把这两个事实用数字记下来,后面的步骤就能比较出变化了什么。
用 Sidecar 资源缩小代理所知的范围
在 /root/istlab-egress/sidecar.yaml 中写入并应用命名空间 shop 的 Sidecar default——egress hosts 只有 ./*(同一命名空间)和 istio-system/* 两个。应用后,把 client 的集群数量以 clusters= 写入 /root/istlab-egress/02-scope.txt。
名称为 default 且没有选择器的 Sidecar 会作用于该命名空间的所有 sidecar。egress hosts 是“这个代理需要知道的服务”列表,不在其中的命名空间的服务就会从配置中去掉——在有数千个服务的网格中,这是降低代理内存占用和 istiod 推送负担的主要办法。也请看一下缩小后的集群列表中是否已经没有了 partner.outside。
拦下未知目的地——REGISTRY_ONLY
在 /root/istlab-egress/sidecar.yaml 的 Sidecar 中加入 outboundTrafficPolicy.mode: REGISTRY_ONLY 并重新应用。然后在 client 中带上 x-request-id 访问 partner 的 Pod IP,并把该请求留下的 client sidecar 访问日志中的那一行原样保存到 /root/istlab-egress/03-blocked.log。
REGISTRY_ONLY 的意思是“只能去服务注册表中存在的地方”。sidecar 不认识的目的地不再走 PassthroughCluster,而是走 BlackHoleCluster,返回 502。这个命名空间的注册表在第 2 步中已经缩小了,所以 partner.outside 服务现在也成了“不认识的地方”。要在日志中找到请求,请自己附上请求 ID——kubectl -n shop logs client -c istio-proxy | grep <id>。
把合作方登记到注册表——ServiceEntry
在 /root/istlab-egress/serviceentry.yaml 中写入并应用命名空间 shop 的 ServiceEntry partner-api——主机 api.partner.test,location: MESH_EXTERNAL,端口 80 HTTP,resolution: DNS,端点地址 partner.outside.svc.cluster.local。应用后,把在 client 中解析 api.partner.test 得到的地址以 vip= 写入 /root/istlab-egress/04-se.txt,并把 http://api.partner.test/ 的状态码以 code= 写入。
ServiceEntry 把网格之外的目的地登记到注册表,使它在 REGISTRY_ONLY 之下也能出站。不过 api.partner.test 这个名称在集群 DNS 中并不存在。这个网格开启了 sidecar 的 DNS 代理,所以 sidecar 会对这个名称应答自动分配的虚拟 IP(240.240.x.x)。这个值也在 ServiceEntry 的 status.addresses 中。请在 Pod 中用 nslookup api.partner.test 确认。
把出站路径汇聚到一个出口网关
在 /root/istlab-egress/egress.yaml 中写入并应用三个资源——(1) 命名空间 shop 的 Istio Gateway partner-egress(选择器 istio: egressgateway,端口 80、协议 HTTPS、tls.mode: ISTIO_MUTUAL,主机 api.partner.test),(2) istio-system 的 DestinationRule egressgateway-for-partner(主机 istio-egressgateway.istio-system.svc.cluster.local,子集 partner 的端口 80 使用 ISTIO_MUTUAL 和 sni: api.partner.test),(3) shop 的 VirtualService partner-via-egress(主机 api.partner.test,gateways 为 partner-egress 和 mesh——来自 mesh 的请求转到出口网关的子集 partner 的 80 端口,来自网关的请求转到 api.partner.test 的 80 端口)。应用后,在 /root/istlab-egress/05-path.txt 中写入出口网关 Pod 的 IP egress_pod_ip=,以及针对在路径中附加了标记发出的请求,partner 日志所记录的来源 partner_saw=。
一个 VirtualService 负责两个区段。在 sidecar(mesh)处,把目的地改为出口网关;在网关处,发往真正的目的地。从 sidecar 到网关这一段的 mTLS 不会自动建立——如果把 Gateway 设为明文 HTTP,请求虽然能通过,但网关不知道发送方的身份(第 7 步需要)。把 DestinationRule 放在 istio-system 中,是为了让其他命名空间的 sidecar 也能找到子集 partner——放在 shop 中,只有 shop 的 sidecar 看得到。是否经过了网关,请在接收方确认。网关会重新生成 x-request-id,所以要像 http://api.partner.test/<표식>(占位符为标记)那样在路径中附加标记,到 partner 日志中查找,并查看该行的来源是否是出口网关的 Pod IP。
绕过网关的路径仍然存在
在 other 命名空间的 intruder 中访问两次,并写入 /root/istlab-egress/06-bypass.txt——直接访问 http://partner.outside/ 的状态码 intruder_direct=,http://api.partner.test/ 的状态码 intruder_via_egress=。再把在 client 中做同样直接访问的结果以 client_direct= 补充进去。
Sidecar 资源和 REGISTRY_ONLY 只是 shop 的代理配置。其他命名空间的代理仍然是 ALLOW_ANY,而没有 sidecar 的 Pod 则根本不知道这些配置。Istio 文档也指出,仅靠出口网关无法强制“所有外部流量都经过网关”,必须同时使用网络策略之类的其他手段。请把这个漏洞用数字记下来。
决定谁可以从网关出站
在 /root/istlab-egress/egress-authz.yaml 中写入并应用 istio-system 的 AuthorizationPolicy partner-only-shop——选择器 istio: egressgateway,action: ALLOW,来源主体 cluster.local/ns/shop/sa/client,目标主机 api.partner.test。应用后,client 访问 api.partner.test 应为 200,intruder 发出的同样请求应为 403。
从 sidecar 到出口网关是 mTLS,所以网关知道发出请求的工作负载的身份(SPIFFE)。因此可以在网关这一处决定“哪个命名空间、哪个服务账号可以访问这个合作方”——设置出口网关的真正理由就在于此。只要存在一条 ALLOW 策略,不匹配的请求就会全部被拒绝。
整理在哪里拦住了出站路径
在 /root/istlab-egress/08-report.md 中写入六行——clusters_before=、clusters_after=(第 1、2 步),blocked_code=(第 3 步日志行中的状态码),partner_saw=(第 5 步中 partner 看到的来源是出口网关则为 egressgateway,是 client 则为 client),intruder_direct=、intruder_via_egress=(第 6 步)——并在其下写出学到的内容,至少四行。
值从前面步骤的文件中抄过来。说明行中请用自己的话写下“Sidecar、REGISTRY_ONLY、出口网关、授权策略各自拦住什么、拦不住什么”。