纸上得来终觉浅,绝知此事要躬行。=> ufw-docker
1. 问题描述
现在的 Docker 防火墙存在什么问题?
UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管理防火墙的规则。但是当安装了 Docker 之后,UFW 就无法管理 Docker 发布出来的端口了。具体现象,如下所示:
- 在一个对外提供服务的服务器上启用了 UFW 进行防火墙管理,并且默认阻止所有未被允许的传入连接。
- 运行了一个 Docker 容器,并且使用 -p 选项来把该容器的某个端口发布到服务器的所有 IP 地址上。
- UFW 将不会阻止所有对 8080 端口访问的请求,用命令 ufw deny 8080 也无法阻止外部访问这个端口。
# 运行一个httpd服务 # 将容器的80端口发布到服务器的8080端口上 $ docker run -d --name httpd -p 0.0.0.0:8080:80 httpd:alpine
这个问题其实挺严重的,这意味着本来只是为了在内部提供服务的一个端口被暴露在公共网络上。在网络上搜索 ufw docker 可以发现很多的讨论,都是在说这个问题的。基本上可以找到的解决办法就是首先禁用 docker 的 iptables 功能,但这也意味着放弃了 docker 的网络管理功能,很典型的现象就是容器将无法访问外部网络。在有的文章中也提到了可以在 UFW 的配置文件中手工添加一条规则,比如 -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE。但这也只是允许了 172.17.0.0/16 这个网络。如果有了新增的网络,我们也必须手工再为新增的网络添加这样类似的 iptables 规则。
# 禁用docker的iptables功能 # start docker with --iptables=false 在启动docker容器的时候,在DOCKER_OPTS添加--iptables=false来禁止 # 添加iptable规则 # FORWARD chain policy set to ACCEPT $ sudo iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE
2. 期望目标
现在有什么解决方式呢?
目前网络上的解决方案都非常类似,而且也不优雅,我希望一个新的解决方案可以:
- 不要禁用 Docker 的 iptables 功能,像往常一样由 Docker 来管理自己的网络。这样有任何新增的 Docker 网络时都无需手工维护 iptables 规则,也避免了在 Docker 中禁用 iptables 之后可能带来的副作用。
- 公共网络不可以访问 Docker 发布出来的端口,即使是使用类似 -p 0.0.0.0:8080:80 的选项把端口发布在所有的 IP 地址上。容器之间、内部网络之间都可以正常互相访问,只有公共网络不可以访问。 虽然可以让 Docker 把容器的某一个端口映射到服务器的私有 IP 地址上,这样公共网络上将不会访问到这个端口。但是这个服务器可能有多个私有 IP 地址,这些私有 IP 地址可能也会发生变化。
- 可以很方便的允许公共网络直接访问某个容器的端口,而无需额外的软件和配置。就像是用 ufw allow 8080 这样允许外部访问 8080 端口,然后用 ufw delete allow 8080 就不再允许外部访问。
3. 解决方式
现在这个脚本也支持 Docker Swarm 集群了
- 撤销原先的修改
如果已经按照目前网络上搜索到解决方案修改过了,请先修改回来。
# 1.启用Docker的iptables功能 删除所有类似--iptables=false的修改,包括/etc/docker/daemon.json配置文件 # 2.修改默认默认 UFW的默认FORWARD规则改回默认的DROP,而非ACCEPT # 3.删除ufw配置 删除UFW配置文件/etc/ufw/after.rules中与Docker网络相关的规则 # 4.重启服务 如果修改了Docker相关的配置文件,重启 Docker
- 解决方法
目前新的解决方案只需要修改一个 UFW 配置文件即可,Docker 的所有配置和选项都保持默认。然后重启 UFW 服务(sudo systemctl restart ufw)。现在外部就已经无法访问 Docker 发布出来的任何端口了,但是容器内部以及私有网络地址上可以正常互相访问,而且容器也可以正常访问外部的网络。可能由于某些未知原因,重启 UFW 之后规则也无法生效,请重启服务器。
# 在最后添加上如下规则 # 修改UFW的配置文件 => /etc/ufw/after.rules # BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] :DOCKER-USER - [0:0] -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN COMMIT # END UFW AND DOCKER
如果希望允许外部网络访问 Docker 容器提供的服务,比如有一个容器的服务端口是 80。这个命令会允许外部网络访问所有用 Docker 发布出来的并且内部服务端口为 80 的所有服务。请注意,这个端口 80 是容器的端口,而非使用 -p 0.0.0.0:8080:80 选项发布在服务器上的 8080 端口。
# 那就可以用以下命令来允许外部网络访问这个服务 $ sudo ufw route allow proto tcp from any to any port 80
如果有多个容器的服务端口为 80,但只希望外部网络访问某个特定的容器。比如该容器的私有地址为 172.17.0.2,就用类似下面的命令。
$ sudo ufw route allow proto tcp from any to 172.17.0.2 port 80
如果一个容器的服务是 UDP 协议,假如是 DNS 服务,可以用下面的命令来允许外部网络访问所有发布出来的 DNS 服务。
$ sudo ufw route allow proto udp from any to any port 53 $ sudo ufw route allow log-all 53 comment "open udp 53 port"
同样的,如果只针对一个特定的容器,比如 IP 地址为 172.17.0.2。
$ sudo ufw route allow proto udp from any to 172.17.0.2 port 53