我们的主机网络防护是基于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机制>>分析过这种情况。
参考: