具有 docker 端口映射的 iptables
iptables with docker port mapping
Iptables 规则是 notoriously difficult to set up when Docker is running on the host, and I thought I had a definitive solution in this fantastic blog post: https://unrouted.io/2017/08/15/docker-firewall/
这篇博客 post 中描述的配置已经为我服务了很长时间,但我现在遇到了一个以前从未遇到过的问题。
我正在 运行ning 一个 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
但是,有趣的部分来了,如果主机上的端口映射到相同[=44=,我确实设法访问了该服务] 容器中的端口。换句话说,当我 运行 在主机上时:
$ 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
感谢您在 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
表之前 INPUT
链运行。
所以我想如果你允许流量到端口 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
看起来有点难看,但清楚地说明了正在发生的事情。如果您尝试这个并且它有效,请告诉我,我会考虑将其写下来/更新该博客 post。
Iptables 规则是 notoriously difficult to set up when Docker is running on the host, and I thought I had a definitive solution in this fantastic blog post: https://unrouted.io/2017/08/15/docker-firewall/
这篇博客 post 中描述的配置已经为我服务了很长时间,但我现在遇到了一个以前从未遇到过的问题。
我正在 运行ning 一个 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
但是,有趣的部分来了,如果主机上的端口映射到相同[=44=,我确实设法访问了该服务] 容器中的端口。换句话说,当我 运行 在主机上时:
$ 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
感谢您在 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
表之前 INPUT
链运行。
所以我想如果你允许流量到端口 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
看起来有点难看,但清楚地说明了正在发生的事情。如果您尝试这个并且它有效,请告诉我,我会考虑将其写下来/更新该博客 post。