在刚开始接触容器的时候,不知道大家有没遇到过这样的困惑:“我创建的 MySQL 容器怎么直接对外了?”,“为什么我添加的防火墙拦截规则没有用?”,“我就重载了一下防火墙,容器怎么访问不了了?”……
实际上以上这些都跟容器与防火墙的配合机制有关,只要我们弄清楚数据包在防火墙中是怎么个处理方式,上面的问题就迎刃而解了,下面我们就以 DOCKER 与 IPTABLES 为例子来详细分析一下整个的过程。
首先我们先来简单了解一下什么是 IPTABLES。在 Linux 生态系统中,iptables 是使用最广泛的防火墙工具之一,它位于用户态,通过 getsockopt/setsockopt 等 IPC 方式与内核协议栈中的Netfilter框架进行交互。规则真正触发执行在内核态,但规则本身不会被存储,所以当机器重启的时候,内核中的规则会消失,因此需要依赖用户态的工具重新写入到内核之中。
Netfilter 中提供了 5 个 hook 点。包经过协议栈时会触发内核模块注册在这里的处理函数 。触发哪个 hook 取决于包的方向(是发送还是接收)、包的目的地址、以及包在上一个 hook 点是被丢弃还是拒绝等等。下面几个 hook 是内核协议栈中已经定义好的:
注册处理函数时必须提供优先级,以便 hook 触发时能按照优先级高低调用处理函数。这使得多个模块(或者同一内核模块的多个实例)可以在同一hook 点注册,并且有确定的处理顺序。内核模块会依次被调用,每次返回一个结果给 netfilter 框架,提示该对这个包做什么操作。
Netfilter 中的 5 个 hook 点分别对应了 IPTABLES 中的 PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING 5 条链,所有进入到协议栈中的数据包都会按照下表所示的顺序处理。
我们以 Wan to Container 的数据包为例子逐条规则进行分析
eccc3a75130b mysql:5.7.14 "docker-entrypoint.s…" 3 days ago Up 3 days 0.0.0.0:13306->3306/tcp docker-phabricator_mysql_1
这个环境中启动了一个映射到主机 13306 端口的 mysql 容器,以此来分析下从主机外的网络访问 13306 端口,数据包经过 iptables 的时候究竟是怎么被处理的。
首先需要明确此时主机收到的数据包,源IP地址为发送方的 IP 地址,源端口为发送方的某个用户端口,目的IP为主机的IP地址,目的端口为 13306,进入到主机网卡 em1。
根据这个流向图可知,主机收到外部网络发送的数据包后首先会进入到 PREROUTING 链中,按照 raw-mangle-nat 表的顺序执行,raw 表的作用是提供一个让包绕过连接跟踪的框架,而 mangle 表是用于修改数据包以及做标记等的作用,在这个场景中我们没有使用到这两个表,直接进入 nat 表中来分析:
可以看到 nat 表中有这么一条规则,匹配目标地址类型属于主机系统本地网络地址的数据包进入到 DOCKER 子链处理。我们这个数据包目标地址显然属于本机的网络地址,因此会匹配到 nat 中的这条规则,从而进入到 DOCKER 子链中
然后在 DOCKER 子链中,匹配到 31 号规则,做了一次 DNAT,数据包中的目的 IP 修改为容器的 IP 地址 192.168.80.2,目的端口改为 3306,到这里 PREROUTING 链走完,进入Routing Decision 环节做路由判断(注意此时的数据包目的地址和端口已经改变,在做后面的防火墙策略的时候要留意到这一点)
目的地址为 192.168.80.2 不是本机地址,即将进入到 FORWARD 链中。查看路由,目的地为 192.168.80.0/20 网段的数据,将通过 docker 网桥 br-bad8080248a3 流出转发至绑定到该网桥的容器ip中。(此时数据包对应的流入的网卡为 em1,流出的网卡为 br-bad8080248a3)
随后进入 FORWARD 链中,同样在这里 mangle 表没有用到,进入 filter 表中进行规则判断,可以看到 filter 中默认规则为 DROP,数据包进入匹配到第一条 DOCKER-USER 子链
在 DOCKER-USER 子链中,没有匹配其他具体的规则,RETURN 回到主链
回到主链后,2号规则匹配成功进入 DOCKER-ISOLATION-STAGE-1
在 DOCKER-ISOLATION-STAGE-1 中 RETURN 回到 FORWARD 主链
从上面也可以看到,为了隔离在不同的 bridge 网络之间的容器,Docker 用了两个 DOCKER-ISOLATION 阶段,两条链条配合起来的规则,实现的就是从一个 bridge 网络发出进入到另一个 bridge 网络中的数据包将会被直接 DROP 掉。
回到 FORWARD 主链中,顺序执行,匹配到 4 号规则进入到 DOCKER 子链,然后在 DOCKER 子链中,匹配目的地址为 192.168.80.2,目的端口 3306,流出网卡为 br-bad8080248a3 的规则,被 ACCEPT 通过,最终数据包进入到容器中被应用程序处理。
随后数据包流出,通过路由后流出网卡为主机网卡 em1,在 POSTROUTING 做源地址的 NAT 转化为主机的 ip 之后回包。
其他流向的分析过程跟上面大同小异,我们只需要明确下具体的数据包流向逐一分析即可,这里也给出对应的流向情况供参考。
Wan to Container | Container to Wan | Host to Container | Container to Host | Container to Container |
---|---|---|---|---|
PREROUTING -> FORWARD -> POSTROUTING | PREROUTING -> FORWARD -> POSTROUTING | OUTPUT -> POSTROUTING | PREROUTING -> INPUT -> OUTPUT -> POSTROUTING | PREROUTING -> FORWARD -> POSTROUTING |
不管是在物理机环境还是在 docker、kubernetes 这样的容器环境,只要我们搞清楚了防火墙对数据包的处理顺序以及不同的规则会对数据包做什么样的变更,那么我们在做防火墙策略的时候就能在正确的“节点”上做出正确的策略,在排查网络问题的时候也能更快定位到问题了。