与 Docker 的 Iptables

问题

为 Docker 容器配置 iptables 规则有点棘手。首先,你会认为经典防火墙规则应该可以解决问题。

例如,假设你已经配置了 nginx-proxy 容器+多个服务容器,以通过 HTTPS 公开一些个人 Web 服务。然后,这样的规则应该只为 IP XXX.XXX.XXX.XXX 提供对 Web 服务的访问。

$ iptables -A INPUT -i eth0 -p tcp -s XXX.XXX.XXX.XXX -j ACCEPT
$ iptables -P INPUT DROP

它不起作用,你的容器仍可供所有人使用。

实际上,Docker 容器不是主机服务。它们依赖于主机中的虚拟网络,主机充当此网络的网关。对于网关,路由流量不是由 INPUT 表处理,而是由 FORWARD 表处理,这使得规则在无效之前发布。

但并非全部。事实上,当 Docker 守护进程开始发挥其关于容器网络连接的魔力时,会创建许多 iptables 规则。特别是,创建 DOCKER 表以通过将来自 FORWARD 表的流量转发到此新表来处理有关容器的规则。

$ iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

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

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain DOCKER (2 references)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             172.18.0.4           tcp dpt:https
ACCEPT     tcp  --  anywhere             172.18.0.4           tcp dpt:http

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

解决方案

如果你查看官方文档( https://docs.docker.com/v1.5/articles/networking/) ,则会给出第一个解决方案,以限制 Docker 容器对一个特定 IP 的访问。

$ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP

实际上,在 DOCKER 表的顶部添加规则是个好主意。它不会干扰 Docker 自动配置的规则,而且很简单。但两个主要缺点是:

  • 首先,如果你需要从两个 IP 而不是一个 IP 访问该怎么办?这里只能接受一个 src IP,其他的将被删除而没有任何方法可以防止这种情况发生。
  • 第二,如果你的码头工人需要访问互联网怎么办?实际上没有请求会成功,因为只有服务器 8.8.8.8 可以响应它们。
  • 最后,如果你想添加其他逻辑怎么办?例如,允许任何用户访问你在 HTTP 协议上提供服务的 Web 服务器,但将其他所有内容限制为特定 IP。

对于第一次观察,我们可以使用 ipset 。我们不允许在上面的规则中允许一个 IP,而是允许来自预定义 ipset 的所有 IP。作为奖励,可以更新 ipset 而无需重新定义 iptable 规则。

$ iptables -I DOCKER -i ext_if -m set ! --match-set my-ipset src -j DROP

对于第二个观察,这是防火墙的规范问题:如果允许你通过防火墙联系服务器,则防火墙应授权服务器响应你的请求。这可以通过授权与已建立的连接相关的分组来完成。对于 docker 逻辑,它给出:

$ iptables -I DOCKER -i ext_if -m state --state ESTABLISHED,RELATED -j ACCEPT

最后一个观察集中在一点:iptables 规则是必不可少的。实际上,在 DROP 规则之前,必须将接受某些连接(包括与 ESTABLISHED 连接有关的连接)的附加逻辑放在 DOCKER 表的顶部,否则所有剩余连接都不匹配 ipset。

因为我们使用 iptable 的 -I 选项,它在表的顶部插入规则,所以必须按相反的顺序插入以前的 iptables 规则:

// Drop rule for non matching IPs
$ iptables -I DOCKER -i ext_if -m set ! --match-set my-ipset src -j DROP
// Then Accept rules for established connections
$ iptables -I DOCKER -i ext_if -m state --state ESTABLISHED,RELATED -j ACCEPT 
$ iptables -I DOCKER -i ext_if ... ACCEPT // Then 3rd custom accept rule
$ iptables -I DOCKER -i ext_if ... ACCEPT // Then 2nd custom accept rule
$ iptables -I DOCKER -i ext_if ... ACCEPT // Then 1st custom accept rule

考虑到所有这些,你现在可以查看说明此配置的示例。