背景
笔者用Docker部署了WordPress,将宿主机的TCP:8000端口映射到容器的80端口。为了只允许特定IP访问容器,本想通过iptables设置:
iptables -A INPUT -s x.x.x.x -p tcp --dport 8000 -j ACCEPT
iptables -A INPUT -p tcp --dport 8000 -j DROP
阻止外网对8000端口的访问,然而设置后发现并没有效果,外网依然能通过8000端口访问WordPress页面。
解决办法
用iptables修改Docker的防火墙行为,需要使用DOCKER-USER链,而不是INPUT。以下是修改后的设置:
iptables -A DOCKER-USER -s x.x.x.x -p tcp --dport 80 -j ACCEPT
iptables -A DOCKER-USER -p tcp --dport 80 -j DROP
# 这里dport设置的端口写成转发后的80而不是8000并不是因为写错。原因后面说。
设置完成后,外网不再能通过8000打开WordPress页面。
🤔为什么会这样?
一言以蔽之,访问Docker的流量不会经过INPUT链。
Docker在启动时会在nat表的PREROUTING链中插入DNAT规则。该规则会将访问容器的流量导入到Docker创建的FORWARD链中,而不经过INPUT链。
举个例子。假设宿主机IP为192.168.1.100,Docker端口设置为宿主机8000转发到容器80。那么当有数据包访问宿主机的8000端口时,该数据包:
- 在
nat表的PREROUTING链中,被Docker设置的规则将传送目标从192.168.1.100:8000修改为172.17.0.2:80(172.17.0.2为Docker网桥创建的IP)。 - Linux内核识别出目标地址
172.17.0.2不是宿主机的IP(宿主机是172.17.0.1,即Docker网桥IP),于是将数据包从INPUT路径切换到FORWARD路径。 FORWARD链指定数据包被送到DOCKER-USER链。- 在
DOCKER-USER链经过规则匹配后返回FORWARD链,继续匹配后续的规则(如DOCKER-ISOLATION-STAGE、DOCKER等)。
这也解释了在DOCKER-USER链中设置的目标端口为什么是80:数据包的传送目标IP、端口被修改之后才会匹配DOCKER-USER定义的规则。
🤔DOCKER-USER具体在什么地方?
用iptables -L FORWARD查看Docker对FORWARD链的设置。可以看到DOCKER-USER链是FORWARD链中的第一条规则,所以对其设置的规则会最先被匹配,优先级最高。
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE all -- 0.0.0.0/0 0.0.0.0/0
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
