具有docker端口映射的iptables

时间:2019-02-19 15:34:32

标签: docker iptables

当Docker在主机上运行时,将设置notoriously difficult的IPtables规则,我认为我在这篇精彩的博客文章https://unrouted.io/2017/08/15/docker-firewall/

中有一个确定的解决方案

这个博客文章中描述的配置已经为我服务了很长时间,但是现在我遇到了一个以前从未遇到过的问题。

我正在运行一个docker容器,该容器在主机的端口465上公开服务。端口465映射到容器中的端口25。这是模拟这种服务的方法:

$ docker run --rm -it -p 465:25 python:3.6 python3 -m http.server 25

我的问题是我无法从外部访问服务器上的端口465:

$ curl mydomain.com:465
curl: (7) Failed to connect to mydomain.com port 465: No route to host

但是,有趣的是,如果主机上的端口映射到容器中的相同端口,我确实设法访问了该服务。换句话说,当我在主机上运行时:

$ docker run --rm -it -p 465:465 python:3.6 python3 -m http.server 465

然后我可以从外部访问该服务:

$ curl mydomain.com:465
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org...

整个问题归结于我的iptables定义:我知道,因为当我刷新iptables规则时,无论端口映射如何,我都确实设法从外部访问该服务。

这是我的iptable规则:

*filter
# Source: https://unrouted.io/2017/08/15/docker-firewall/
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:FILTERS - [0:0]
:DOCKER-USER - [0:0]

-F INPUT
-F DOCKER-USER
-F FILTERS

-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -s 127.0.0.0/8 -j REJECT
-A INPUT -p icmp --icmp-type any -j ACCEPT
-A INPUT -j FILTERS
-A DOCKER-USER -i eth0 -j FILTERS
-A FILTERS -m state --state ESTABLISHED,RELATED -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 465 -j ACCEPT

-A FILTERS -j REJECT --reject-with icmp-host-prohibited
COMMIT

我应该如何修改iptables,以便无论端口映射如何都可以从外部访问容器?

编辑:

这是失败情况下的完整iptables规则(465:25映射):

$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination        
ACCEPT     all  --  anywhere             anywhere            
REJECT     all  --  loopback/8           anywhere             reject-with icmp-port-unreachable
ACCEPT     icmp --  anywhere             anywhere             icmp any
FILTERS    all  --  anywhere             anywhere            

Chain FORWARD (policy DROP)
target     prot opt source               destination        
DOCKER-USER  all  --  anywhere             anywhere            
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination        

Chain DOCKER (3 references)
target     prot opt source               destination        
ACCEPT     tcp  --  anywhere             172.19.0.4           tcp dpt:3000
ACCEPT     tcp  --  anywhere             172.17.0.3           tcp dpt:smtp

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target     prot opt source               destination        
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere            
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere            
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere            
RETURN     all  --  anywhere             anywhere            

Chain DOCKER-ISOLATION-STAGE-2 (3 references)
target     prot opt source               destination        
DROP       all  --  anywhere             anywhere            
DROP       all  --  anywhere             anywhere            
DROP       all  --  anywhere             anywhere            
RETURN     all  --  anywhere             anywhere            

Chain DOCKER-USER (1 references)
target     prot opt source               destination        
FILTERS    all  --  anywhere             anywhere            

Chain FILTERS (2 references)
target     prot opt source               destination        
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:http
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:https
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:urd
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

1 个答案:

答案 0 :(得分:1)

感谢您在Twitter上与我联系。实际上,我之前从未有人注意过这个问题,我想我知道发生了什么。在您的示例中:

docker run --rm -it -p 465:25 python:3.6 python3 -m http.server 25

如果您使用iptables-save查看完整的防火墙配置,则会看到一堆NAT规则。在*nat部分中您可能会看到类似以下的内容:

-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
... snip ...
-A DOCKER ! -i br-abbaabbaabba -p tcp -m tcp --dport 465 -j DNAT --to-destination 172.18.0.10:25

因此,此规则在PREROUTING阶段执行,并重写传入的数据包,使其看起来总是端口25而不是端口465。并且这发生在filter表{{1}之前}链条运行。

因此,我认为如果您允许流量访问端口25,那么实际上您也可以访问端口INPUT。显然,您不想允许访问所有端口25,因为其中包括主机的端口25。

由于Docker,您此时要做的所有普通技巧都变得更加困难。

选项1

您可以拒绝显式优于隐式路由,并拆分主机与docker规则:

465

您仍然不满意允许端口25的通信。

选项2

我相信,现在Docker不会在*filter :INPUT ACCEPT [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :FILTERS - [0:0] :DOCKER-USER - [0:0] -F INPUT -F DOCKER-USER -F FILTERS -A INPUT -i lo -j ACCEPT -A INPUT ! -i lo -s 127.0.0.0/8 -j REJECT -A INPUT -p icmp --icmp-type any -j ACCEPT # Rules for services running on the host: -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited # Rules for services running in containers: -A DOCKER-USER -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT # This says dport 25, but is actually 465. Yay for prerouting + NAT. # Service on real host port 25 should still be inaccessible because DOCKER-USER # is only accessible via `FORWARD` and not `INPUT`... -A DOCKER-USER -i eth0 -m state --state NEW -m tcp -p tcp --dport 25 -j ACCEPT -A DOCKER-USER -j REJECT --reject-with icmp-host-prohibited COMMIT *raw中放置任何内容,因此可以安全地在其中添加您自己的规则。显然,这些表存在局限性(原始在连接跟踪之前,而mangle仅用于标记连接),因此也不是很好。

选项3

最后,我可以想到的唯一另一件事是*mangle iptables模块可以使用conntrack来解决问题,但是我自己从未尝试过。看着this,您可以尝试:

--ctorigdstport

看起来有点难看,但要清楚地说明正在发生的事情。如果您尝试使用此工具,并且可以正常工作,请告诉我,我将看到有关编写/更新该博客文章的信息。