我们的主机网络防护是基于netfilter实现。最近遇到需要对访问主机上Docker容器的流量进行防护。几年前其实就处理过这个场景,时间久远忘记了,重新梳理一下记录下来。
我们的主机网络防护模块的hooknum为LOCAL_IN和POST_ROUTING, 并且hook的优先级为NF_IP_PRI_FIRST, 也就是在hooknum位置最先运行。
从宿主机外部访问主机上容器的场景
之前的文章<<从外部访问Docker桥接网络容器路径分析>>分析了从外部访问Docker桥接网络的网络路径。
- 从外部到容器的数据包会流经
PRE_ROUTING,FORWARD和POST_ROUTING阶段,在PRE_ROUTING阶段会进行DNAT, 将目的IP/PORT, 修改为容器的IP/PORT。 - 从容器到外部的数据包会流经
PRE_ROUTING,FORWARD和POST_ROUTING阶段,在POST_ROUTING阶段会进行SNAT, 将源IP/PORT修改为外部宿主机的IP/PORT。
从网络路径来看,在POST_ROUTING阶段数据包上的地址是容器本身的地址, 因而我们可以简单的将容器IP/PORT端口在规则中配置,就可以实现对于访问容器内部流量的防护。
可以通过内核模块来验证这种场景下的一下, 源码如下:
1 | #define pr_fmt(fmt) "[%s]: " fmt, KBUILD_MODNAME |
测试环境中,容器内部监听8080, 宿主机上监听8081转发到容器内的8080。在另外一台机器访问宿主机的8081端口,查看内核模块的输出结果。
入数据包的结果是:
1 | [3598646.157916] [nftrace]: ==================================================== |
因为在CentOS7的3.10版本内核上,注册netfilter函数会在所有的network namespace上生效,因而也会有在容器本身的namespace中独立的netfilter流经过程:
1 | [3598646.157983] [nftrace]: ==================================================== |
容器响应包的结果:
1 | [3598646.158033] [nftrace]: ==================================================== |
主机容器之间访问
我们接着用上边的内核模块分析这种场景。我们从容器172.17.0.4访问172.17.0.2:8080。
请求包结果:
1 | [3612381.406210] [nftrace]: ==================================================== LOCAL_OUT |
首先,请求数据包流经的是容器172.17.0.4所在namespace的LOCAL_OUT和POST_ROUTING阶段,然后直接进入到172.17.0.2容器所在namespace的PRE_ROUTING和LOCAL_IN阶段。
响应包也类似:
1 | [3612381.406274] [nftrace]: ==================================================== LOCAL_OUT |
依次是172.17.0.2容器所在namespace的LOCAL_OUT、POST_ROUTING和172.17.0.2容器所在的namespace的PRE_ROUTING和LOCAL_IN阶段。
并没有流经宿主机所在的init_net的netfilter阶段。
这是因为同一主机上不同容器之间的流量是通过Linux bridge进行二层转发,默认情况下,不会调到到三层的netfilter框架。但内核中有参数可以调整该行为,默认是关闭的。
1 | net.bridge.bridge-nf-call-ip6tables = 0 |
将net.bridge.bridge-nf-call-iptables参数设为1后,再次进行相同的测试, 结果如下:
1 | [3615588.895871] [nftrace]: ==================================================== |
从结果分析,数据包也流经了init_net的PRE_ROUTING、FORWARD和POST_ROUTING阶段。回包也是一样的。因此,对于容器间的访问,我们也可以在POST_ROUTING阶段进行规则的配置来实现容器防护需求。
其实,从上述的结果看,如果不基于现有的主机防护能力,其实最理想的位置是FORWARD位置,这个位置的入设备和出设备都已经在参数中明确。如果只是在netfilter中实现过滤,在容器本身的namespace中也可以实现流量过滤,但如果需要使用NFQUEUE将数据包上送到用户态处理,那就需要所有的容器namespace中都运行收包检测程序,这种方案就不太适用了。之前的文章<<Kubernetes POD环境的NFQUEUE机制>>分析过这种情况。
参考: