笔者在旷视科技从事私有云平台开发,在工作中遇到了在服务器上防火墙无法阻止docker容器bridge模式下服务的问题。
背景
常见的linux防火墙方式为在iptables的INPUT链,设置白名单,端口或者ip
sudo iptables -L INPUT -nv --line
如图所示, 防火墙对于10.199.1.26, 10.122.104.147, 10.199.1.25 三个为可通行ip, 以及docker0网卡的请求时可通行的。
问题
服务器上如果docker container是以bridge模式启动,则防火墙规则失效。比如docker 启动bridge模式启动nginx,则防火墙无法阻拦。
原因
解释原因之前,先抛出一个问题,docker 运行如下命令
docker run -d -p 27111:80 nginx
是如何把服务器27111端口映射到容器内80端口的呢?
这一切还要从linux网络,以及docker 如何使用Linux网络说起。下图是linux网络经典图,linux的iptables对外暴露5链4表。
当请求发送到机器上,会先经过 PREROUTING链路,经路由判断后选择 INPUT 或 FORWAR ,进入INPUT链路会走INPUT的处理规则,然后进入到上层协议栈,从OUTPUT链路返回,经POSTROUTING发出,进入FORWARD的链路也会经POSTROUTING发出。
Docker利用Linux iptables特性,会在PREROUTING链路创建DOCKER子链,运行
docker run -d -p 27111:80 nginx
时,会在DOCKER链 nat表上做转发。如图所示:会把27111端口的请求转发到 172.17.0.11:80, 而172.17.0.11:80就是nginx container 的网络。
补充: docker是通过创建docker0虚拟网卡然后通过veth pair绑定到宿主机网卡:
一个完整的访问docker bridge container 服务网络请求为:
- 请求打到PREROUTING链,PREROUTING nat表做转发到docker container的网络里
- 请求经过FORWARD链,进入container服务
- 请求从container出来后还会走FORWARD链
- 走POSTROUTING链离开
所以INPUT链路的过滤规则无法约束到docker bridge模式的容器服务,就是因为docker 桥模式是在PREROUTING链路做转发。
查阅docker官方文档,发现docker提供DOCKER-USER链路来做辅助防火墙,DOCKER-USE链是挂载在FORWARD链上的子链,看起来可以在DOCKER-USE做过滤规则。但是新的问题来了,docker bridge每启动一个服务, 内部ip会有变化。比如启动一个Nginx 内部Ip为172.17.0.11,启动第二个Nginx ip为172.17.0.12。由于防火墙白名单通常为安全ip + 暴露端口等模式,那么依托DOCKER-USE的防火墙设计思路为:docker bridge 启动container,监听docker event事件,如果container监听的本机端口是暴露端口,那么DOCKER-USE添加端口,accept规则,同时添加安全ip的accept规则。非常的麻烦。
本人的设计思路如下:
由于我们的服务器不做路由转发,所以在PREROUTING链路做防火墙。
创建FIREWALL子链挂载到PREROUTING上DOCKER子链之前,设置安全ip, 端口ACCEPT规则
- 当请求到达机器时,如果是安全ip或者访问暴露端口,那么ACCPET或者RETURN
- 其他请求到达时,正常的操作是DROP掉,但是由于PREROUTING链路没有DROP操作,所以需要把这些请求DNAT到一个特殊ip比如129.129.129.129。
-
把FIREWALL挂载到FORWARD上,DOCKER子链之前,(iptables链路挂载准确的说是链路的某些表挂载到某些链路的某些表下),当访问特殊ip,如129.129.129.129 drop请求。
firewall-design.png
优缺点
优点: 简单有效
- 有效实现面对容器挑战下linux的防火墙实现
- 不需要动态更新Iptables
缺点
- 特殊ip不能和docker默认网段以及服务器网段重合
结束语
爱上写作