【问题标题】:iptables with docker port mapping带有 docker 端口映射的 iptables
【发布时间】:2019-02-21 05:03:03
【问题描述】:

Iptables 规则是 notoriously difficult 在 Docker 在主机上运行时设置,我认为我在这篇精彩的博文中找到了明确的解决方案: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

【问题讨论】:

    标签: docker iptables


    【解决方案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。这发生在 filterINPUT 链运行之前。

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

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

    选项 1

    您可以采用显式优于隐式路由并拆分主机与 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
    

    仍然不满意您允许流量到端口 25..

    选项 2

    我相信现在 Docker 没有在 *raw*mangle 中添加任何内容,因此在其中添加您自己的规则是安全的。显然,这些表存在局限性(raw 是在连接跟踪之前,mangle 仅用于标记连接)所以也不是很好。

    选项 3

    最后,我认为conntrack iptables 模块可能有--ctorigdstport 的答案,但我自己从未尝试过。看看this,你可以试试:

    iptables -A FILTERS -p tcp --dport 25 -m conntrack --ctstate NEW --ctorigdstport 465 -j ACCEPT
    

    看起来有点难看,但对正在发生的事情很明确。如果您尝试了这个并且它有效,请告诉我,我会看看如何编写/更新该博客文章。

    【讨论】:

    • 感谢您的回答!我很快就会试试这个。
    • 我可以确认选项 1 和 3 都有效 \o/ 我没有尝试过选项 2,因为坦率地说我不明白。对于选项 3,iptables 需要了解我的容器的内部端口才能运行仍然有点令人失望,但无论如何我都会感到满意。感谢您花时间回答!
    • 确实如此。总是使用--ctorigdstport 而不费心提供--dport 是很诱人的。但请注意安全性(如果您不完全了解 iptables,显式可能更安全)和性能(使用 conntrack 会有一点开销)
    猜你喜欢
    • 1970-01-01
    • 2017-01-29
    • 2017-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-17
    • 2019-11-17
    • 2020-02-29
    相关资源
    最近更新 更多