云原生(五)iptables 及 istio 流量劫持

1,109 阅读6分钟

iptables 概览

在 Linux 中,netfilter 是防火墙真正的安全框架,位于内核空间,有网络地址转换(NAT)、数据包修改、数据包过滤等防火墙功能。

iptablesnetfilter 的命令行管理工具,在用户空间。

当我们启用防火墙功能,报文需要经过如下“链”:

image.png

如果报文需要转发,则报文不会经过 input 链进入用户空间,而是直接在内核空间中经过 forward 链和 postrouting 链转发出去。

我们对每个“链”设置了一堆规则,但是这些规则中有些很相似,那这些相似功能的规则是能够被放到一起的,这就组成了表。

image.png

filter表:负责过滤功能,防火墙;内核模块:iptables_filter

nat表:network address translation,网络地址转换功能;内核模块:iptable_nat

mangle表:拆解报文,做出修改,并重新封装 的功能;iptable_mangle

raw表:关闭nat表上启用的连接追踪机制;iptable_raw

istio 流量劫持

网格内的 Pod 启动时,会在 Pod 中启动 istio-init 容器,以下是我截取的该容器的启动命令:

istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i '*' -x "" -b '*' -d 15090,15020

可以理解为,istio-init 容器是通过 istio-iptables 命令来给主机注入 iptables 规则。

istio-init容器主要是地址转换的功能,所以修改的是 iptables 的 NAT 表。

该容器存在的意义就是让 sidecar 代理可以拦截所有的进出 pod 的流量,15090 端口(Mixer 使用)和 15020 端口(Prometheus)除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再拦截应用容器的出站(outbound)流量经过 sidecar 处理(通过 15001 端口监听)后再出站。关于 Istio 中端口用途请参考 Istio 官方文档

到 sidecar 进程的命名空间下查看注入的所有 iptables 规则:

# 查看 NAT 表中规则配置的详细信息。
$ iptables -t nat -L -v
# PREROUTING 链:用于目标地址转换(DNAT),将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上。
Chain PREROUTING (policy ACCEPT 2701 packets, 162K bytes)
 pkts bytes target         prot opt in     out     source               destination
 2701  162K ISTIO_INBOUND  tcp  --  any    any     anywhere             anywhere

# ISTIO_INBOUND 链:将所有入站流量重定向到 ISTIO_IN_REDIRECT 链上,目的地为 15090(mixer 使用)和 15020Ingress gateway 使用,用于 Pilot 健康检查)端口的流量除外,发送到以上两个端口的流量将返回 iptables 规则链的调用点,即 PREROUTING 链的后继 POSTROUTINGChain ISTIO_INBOUND (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     tcp  --  any    any     anywhere             anywhere             tcp dpt:ssh
    2   120 RETURN     tcp  --  any    any     anywhere             anywhere             tcp dpt:15090
 2699  162K RETURN     tcp  --  any    any     anywhere             anywhere             tcp dpt:15020
    0     0 ISTIO_IN_REDIRECT  tcp  --  any    any     anywhere             anywhere

# ISTIO_IN_REDIRECT 链:将所有的入站流量跳转到本地的 15006 端口,至此成功的拦截了流量到 Sidecar 中。
Chain ISTIO_IN_REDIRECT (3 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 REDIRECT   tcp  --  any    any     anywhere             anywhere             redir ports 15006
    
# 随后 Sidecar 向业务容器建立连接, 之后从业务容器出站的流量会被 netfilter 拦截进 OUTPUT 链
# OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上。
Chain OUTPUT (policy ACCEPT 79 packets, 6761 bytes)
 pkts bytes target        prot opt in     out     source               destination
   15   900 ISTIO_OUTPUT  tcp  --  any    any     anywhere             anywhere
    
# ISTIO_OUTPUT 链:选择需要重定向到 Envoy(即本地) 的出站流量,所有非 localhost 的流量全部转发到 ISTIO_REDIRECT。为了避免流量在该 Pod 中无限循环,所有到 istio-proxy 用户空间的流量都返回到它的调用点中的下一条规则,本例中即 OUTPUT 链,因为跳出 ISTIO_OUTPUT 规则之后就进入下一条链 POSTROUTING。如果目的地非 localhost 就跳转到 ISTIO_REDIRECT;如果流量是来自 istio-proxy 用户空间的,那么就跳出该链,返回它的调用链继续执行下一条规则(OUTPUT 的下一条规则,无需对流量进行处理);所有的非 istio-proxy 用户空间的目的地是 localhost 的流量就跳转到 ISTIO_REDIRECTChain ISTIO_OUTPUT (1 references)
 pkts bytes target             prot opt in     out     source               destination
    0     0 RETURN             all  --  any    lo      127.0.0.6            anywhere
    0     0 ISTIO_IN_REDIRECT  all  --  any    lo      anywhere            !localhost            owner UID match 1337
    0     0 RETURN             all  --  any    lo      anywhere             anywhere             ! owner UID match 1337
   15   900 RETURN             all  --  any    any     anywhere             anywhere             owner UID match 1337
    0     0 ISTIO_IN_REDIRECT  all  --  any    lo      anywhere            !localhost            owner GID match 1337
    0     0 RETURN             all  --  any    lo      anywhere             anywhere             ! owner GID match 1337
    0     0 RETURN             all  --  any    any     anywhere             anywhere             owner GID match 1337
    0     0 RETURN             all  --  any    any     anywhere             localhost
    0     0 ISTIO_REDIRECT     all  --  any    any     anywhere             anywhere    
    
# ISTIO_REDIRECT 链:将所有流量重定向到 Sidecar(即本地) 的 15001 端口。
Chain ISTIO_REDIRECT (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 REDIRECT   tcp  --  any    any     anywhere             anywhere             redir ports 15001
    
## 从 Sidecar 出站的流量会被 netfilter 拦截到 OUTPUT 链,OUTPUT 链会将流量重定向到 ISTIO_OUTPUT 链,再之后流量直接 RETURN 便到了 POSTROUTING 链

# POSTROUTING 链:所有数据包流出网卡时都要先进入 POSTROUTING 链,内核根据数据包目的地判断是否需要转发出去,我们看到此处未做任何处理。
Chain POSTROUTING (policy ACCEPT 79 packets, 6761 bytes)
 pkts bytes target     prot opt in     out     source               destination

通过 istio-iptables 执行这条命令的意义就是拦截所有进出 Pod 的流量,15090 端口和 15020 端口除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再拦截应用容器的出站流量(outbound)到 sidecar,通过 15001 端口监听 outbound

以下内容参考 ServiceMesh 社区中的文档。以 bookinfo 服务为例,观察 iptables 中“链”的详细调用过程:

image.png

红色数字:

  1. productpage 服务向 reviews 服务发送 TCP 连接请求
  2. 请求进入 reviews 服务所在Pod内核空间,被 netfilter 拦截入口流量,经过 PREROUTING 链然后转发至 ISTIO_INBOUND
  3. ISTIO_INBOUND 链中被规则 -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT 拦截再次转发至 ISTIO_IN_REDIRECT
  4. ISTIO_IN_REDIRECT 链直接重定向至 sidecar 监听的 15006 入口流量端口
  5. 在 sidecar 内部经过一系列入口流量治理动作后,发出TCP连接请求 reviews 服务,这一步对 sidecar 来说是出口流量,会被 netfilter 拦截转发至出口流量 OUTPUT
  6. OUTPUT 链转发流量至 ISTIO_OUTPUT
  7. 目的地为 localhost,不能匹配到转发规则链,直接 RETURN 到下一个链,即POSTROUTING
  8. sidecar 发出的请求到达 reviews 服务 9080 端口
  9. reviews 服务处理完业务逻辑后响应 sidecar,这一步对 reviews 服务来说属于出口流量,再次被 netfilter 拦截转发至出口流量 OUTPUT
  10. OUTPUT 链转发流量至 ISTIO_OUTPUT
  11. 发送非 localhost 请求且为 istio-proxy 用户空间的流量被转发至 ISTIO_REDIRECT
  12. ISTIO_REDIRECT链直接重定向至 sidecar 监听的 15001 出口流量端口
  13. 在 sidecar 内部经过一系列出口流量治理动作后继续发送响应数据,响应时又会被netfilter 拦截转发至出口流量 OUTPUT
  14. OUTPUT 链转发流量至 ISTIO_OUTPUT
  15. 流量直接 RETURN 到下一个链,即 POSTROUTING

至此,istio 对流量的整个“劫持”过程便完成了。