解决iptables和Docker冲突的问题

在一台装有Docker的服务器上,需要利用iptables作为防火墙限制外部访问
图1
如上图,192.168.0.1/3这3台服务器上分布式部署了Web Service,通过容器的7901端口,对外开放80/443端口,192.168.0.1上还部署了Redis示例,开放6379端口。有以下要求:

  • 80/443端口不受限
  • 192.168.0.1/3网段内的主机互相访问6379端口不受限
  • 出站访问不受限
    其他一律不允许。

使用iptables -L命令查看当前iptables设置,发现一些诸如DOCKER之类的Chain,呃…感觉不妙。

使用以下命令重置iptables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 先允许所有,不然有可能会杯具
iptables -P INPUT ACCEPT

iptables -F  # 清空所有默认规则
iptables -X  # 清空所有自定义规则
iptables -Z  # 所有计数器归0

# 内网白名单
iptables -A INPUT -s 192.168.0.1,192.168.0.2,192.168.0.3 -j ACCEPT

# 对外开放的端口
iptables -A INPUT -p tcp -m multiport --dport 22,80,443 -j ACCEPT

# 允许ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT

# 其他入站一律丢弃
iptables -P INPUT DROP
# 所有出站一律绿灯
iptables -P OUTPUT ACCEPT

service iptables save

重启Docker服务,发现iptables规则对容器并没有生效,即,外网可以不受限地访问容器的任意端口。查看iptables,果然又出现了名为DOCKER的Chain。显然,Docker自动修改了iptables。

那么是否可以禁止Docker自动修改iptables?

/etc/docker/daemon.json添加"iptables":false,重启Docker服务,重新设置iptables,嗯,很好,iptables不会被自动修改了,然而,容器的任何出站入站连接都不通了( ╯□╰ )

是时候研究以下iptables了

图2
如上图,iptables分为表(tables)、链(chain)和规则(rules)三个层面,我们一般只用到filter表,其中包含:

  • INPUT,输入链。发往本机的数据包通过此链。
  • OUTPUT,输出链。从本机发出的数据包通过此链。
  • FORWARD,转发链。本机转发的数据包通过此链。

由于Docker默认通过NAT(网络地址翻译)连接默认虚拟网桥(docker0)和容器的默认网关(eth0),所以设置INPUT是没有用的,应该设置FORWARD链。而由于FORWARD链默认为

1
Chain FORWARD (policy DROP)

所有转发都被拦截,因此第二次出现的现象也就不难解释了。

查阅Docker官方文档,发现以下内容

On Linux, Docker manipulates iptables rules to provide network isolation.While this is an implementation detail and you should not modify the rules Docker inserts into your iptables policies, it does have some implications on what you need to do if you want to have your own policies in addition to those managed by Docker.

看来,直接禁止Docker自动修改iptables是不可取的,会导致很多更复杂的问题。

利用DOCKER-USER链设置容器访问规则

如Docker官方文档所述

By default, all external source IPs are allowed to connect to the Docker host. To allow only a specific IP or network to access the containers, insert a negated rule at the top of the DOCKER-USER filter chain.

因此我们首先需要将Docker升级到v19.03.4及以上版本,以支持DOCKER-USER链。然后修改iptables:

1
2
3
4
5
6
7
8
9
10
# 其他入站一律丢弃
iptables -I DOCKER-USER -i eth0 -j DROP
# 允许主动连接其他主机
iptables -I DOCKER-USER -m state --state established,related -j ACCEPT
# 对外开放的端口
iptables -I DOCKER-USER -p tcp -m multiport --dport 7901 -j ACCEPT
# 内网白名单
iptables -I DOCKER-USER -s 192.168.0.1,192.168.0.2,192.168.0.3 -j ACCEPT

service iptables save

最后生成的iptables如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Chain FORWARD (policy DROP)
num target prot opt source destination
1 DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
...
Chain DOCKER-USER (1 references)
num target prot opt source destination
1 ACCEPT all -- 192.168.0.1 0.0.0.0/0
2 ACCEPT all -- 192.168.0.2 0.0.0.0/0
3 ACCEPT all -- 192.168.0.3 0.0.0.0/0
4 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 7901
5 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
6 DROP all -- 0.0.0.0/0 0.0.0.0/0
7 RETURN all -- 0.0.0.0/0 0.0.0.0/0

参考资料:
Docker and iptables
Docker与IPtables
iptables 添加,删除,查看,修改
linux平台下防火墙iptables原理