数据包过滤和防火墙

在 Linux 上,Docker 创建 `iptables` 和 `ip6tables` 规则来实现网络隔离、端口发布和过滤。

由于这些规则对于 Docker 桥接网络的正确运行是必需的,因此您不应修改 Docker 创建的规则。

但是,如果您在暴露于互联网的主机上运行 Docker,您可能希望添加 iptables 策略来防止未经授权访问容器或主机上运行的其他服务。此页面描述了如何实现这一目标以及需要注意的注意事项。

注意

Docker 为桥接网络创建 `iptables` 规则。

不会为 `ipvlan`、`macvlan` 或 `host` 网络创建 `iptables` 规则。

Docker 和 iptables 链

在 `filter` 表中,Docker 将默认策略设置为 `DROP`,并创建以下自定义 `iptables` 链

  • DOCKER-USER
    • 用户定义规则的占位符,这些规则将在 `DOCKER` 链中的规则之前处理。
  • DOCKER
    • 确定是否应接受不是已建立连接一部分的数据包的规则,基于正在运行的容器的端口转发配置。
  • `DOCKER-ISOLATION-STAGE-1` 和 `DOCKER-ISOLATION-STAGE-2`
    • 隔离 Docker 网络彼此的规则。

在 `FORWARD` 链中,Docker 添加规则,这些规则将与已建立连接无关的数据包传递到这些自定义链,以及接受属于已建立连接一部分的数据包的规则。

在 `nat` 表中,Docker 创建链 `DOCKER` 并添加规则以实现伪装和端口映射。

在 Docker 的规则之前添加 iptables 策略

这些自定义链中的规则接受或拒绝的数据包将不会被附加到 `FORWARD` 链的用户定义规则看到。因此,要添加其他规则来过滤这些数据包,请使用 `DOCKER-USER` 链。

匹配请求的原始 IP 和端口

当数据包到达 `DOCKER-USER` 链时,它们已经通过了目标网络地址转换 (DNAT) 过滤器。这意味着您使用的 `iptables` 标志只能匹配容器的内部 IP 地址和端口。

如果您想根据网络请求中的原始 IP 和端口匹配流量,则必须使用 `conntrack` iptables 扩展。例如

$ sudo iptables -I DOCKER-USER -p tcp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$ sudo iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdst 198.51.100.2 --ctorigdstport 80 -j ACCEPT

重要

使用conntrack扩展可能会导致性能下降。

端口发布和映射

默认情况下,对于IPv4和IPv6,守护进程都会阻止对未发布端口的访问。已发布的容器端口会映射到主机IP地址。为此,它使用iptables执行网络地址转换(NAT)、端口地址转换(PAT)和伪装。

例如,docker run -p 8080:80 [...]会在Docker主机的任何地址上的8080端口与容器的80端口之间创建映射。来自容器的出站连接将使用Docker主机的IP地址进行伪装。

限制对容器的外部连接

默认情况下,允许所有外部源IP连接到已发布到Docker主机地址的端口。

要仅允许特定的IP或网络访问容器,请在DOCKER-USER过滤器链的顶部插入一条否定规则。例如,以下规则会丢弃来自除192.0.2.2以外所有IP地址的数据包

$ iptables -I DOCKER-USER -i ext_if ! -s 192.0.2.2 -j DROP

您需要更改ext_if以与您主机的实际外部接口相对应。或者,您可以允许来自源子网的连接。以下规则仅允许来自子网192.0.2.0/24的访问

$ iptables -I DOCKER-USER -i ext_if ! -s 192.0.2.0/24 -j DROP

最后,您可以使用--src-range指定要接受的IP地址范围(使用--src-range--dst-range时,请记住也要添加-m iprange

$ iptables -I DOCKER-USER -m iprange -i ext_if ! --src-range 192.0.2.1-192.0.2.3 -j DROP

您可以将-s--src-range-d--dst-range组合使用,以同时控制源和目标。例如,如果Docker主机具有地址2001:db8:1111::22001:db8:2222::2,您可以针对2001:db8:1111::2制定规则,并保持2001:db8:2222::2开放。

iptables很复杂。更多信息请访问Netfilter.org HOWTO

直接路由

端口映射确保已发布的端口可在主机的网络地址上访问,这些地址很可能对于任何外部客户端都是可路由的。通常不会为主机网络中存在的容器地址设置路由。

但是,尤其是在IPv6中,您可能更倾向于避免使用NAT,而是安排对容器地址的外部路由。

要从Docker主机外部访问桥接网络上的容器,必须通过Docker主机上的地址设置到桥接网络的路由。这可以通过静态路由、边界网关协议(BGP)或任何适合您网络的其他方法来实现。

桥接网络驱动程序具有选项com.docker.network.bridge.gateway_mode_ipv6=<nat|routed>com.docker.network.bridge.gateway_mode_ipv4=<nat|routed>

默认值为nat,为每个已发布的容器端口设置NAT和伪装规则。在routed模式下,不会设置NAT或伪装规则,但仍然会设置iptables,以便只有已发布的容器端口可访问。

routed模式下,不会使用-p--publish端口映射中的主机端口,主机地址仅用于确定是否将映射应用于IPv4或IPv6。因此,当映射仅应用于routed模式时,仅允许地址0.0.0.0::1,并且不得提供主机端口。

如果在网络中设置了路由,则在natrouted模式下,映射的容器端口可从任何远程地址访问,除非Docker主机的防火墙有其他限制。

示例

创建一个适合IPv6直接路由的网络,并为IPv4启用NAT

$ docker network create --ipv6 --subnet 2001:db8::/64 -o com.docker.network.bridge.gateway_mode_ipv6=routed mynet

创建一个具有已发布端口的容器

$ docker run --network=mynet -p 8080:80 myimage

然后

  • 仅容器的80端口将对IPv4和IPv6开放。如果存在到容器地址的路由,并且主机防火墙未阻止访问,则可以从任何地方访问它。
  • 对于IPv6,使用routed模式,容器IP地址上的80端口将开放。主机IP地址上将不会打开8080端口,出站数据包将使用容器的IP地址。
  • 对于IPv4,使用默认的nat模式,容器的80端口可以通过主机IP地址上的8080端口以及直接访问。源自容器的连接将使用主机的IP地址进行伪装。

docker inspect中,此端口映射将显示如下。请注意,IPv6没有HostPort,因为它使用的是routed模式

$ docker container inspect <id> --format "{{json .NetworkSettings.Ports}}"
{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"},{"HostIp":"::","HostPort":""}]}

或者,要使映射仅限于IPv6,禁用对容器80端口的IPv4访问,请使用未指定的IPv6地址[::],并且不要包含主机端口号

$ docker run --network mynet -p '[::]::80'

设置容器的默认绑定地址

默认情况下,当映射容器端口时未指定任何特定主机地址,Docker守护进程会将已发布的容器端口绑定到所有主机地址(0.0.0.0[::])。

例如,以下命令会将8080端口发布到主机上的所有网络接口,包括IPv4和IPv6地址,这可能会使它们对外部世界可用。

docker run -p 8080:80 nginx

您可以更改已发布容器端口的默认绑定地址,以便默认情况下它们仅对Docker主机可访问。为此,您可以将守护进程配置为使用环回地址(127.0.0.1)。

警告

位于同一L2段中的主机(例如,连接到同一网络交换机的主机)可以访问发布到localhost的端口。更多信息,请参见moby/moby#45610

要为用户定义的桥接网络配置此设置,请在创建网络时使用com.docker.network.bridge.host_binding_ipv4 驱动程序选项

$ docker network create mybridge \
  -o "com.docker.network.bridge.host_binding_ipv4=127.0.0.1"

注意

  • 将默认绑定地址设置为::意味着未指定主机地址的端口绑定将适用于主机上的任何IPv6地址。但是,0.0.0.0表示任何IPv4或IPv6地址。
  • 更改默认绑定地址对Swarm服务没有任何影响。Swarm服务始终在0.0.0.0网络接口上公开。

默认桥接

要设置默认桥接网络的默认绑定,请在daemon.json配置文件中配置"ip"

{
  "ip": "127.0.0.1"
}

这会将默认桥接网络上已发布容器端口的默认绑定地址更改为127.0.0.1。重新启动守护进程才能使此更改生效。或者,您可以在启动守护进程时使用dockerd --ip标志。

路由器上的 Docker

Docker将FORWARD链的策略设置为DROP。这将阻止您的Docker主机充当路由器。

如果希望您的系统充当路由器,则必须向DOCKER-USER链添加显式的ACCEPT规则。例如

$ iptables -I DOCKER-USER -i src_if -o dst_if -j ACCEPT

阻止 Docker 操作 iptables

可以在守护进程配置中将iptablesip6tables键设置为false,但这对于大多数用户来说并不适用。这可能会破坏Docker Engine的容器网络。

所有容器的所有端口都将可从网络访问,并且没有任何端口会从Docker主机IP地址映射。

无法完全阻止Docker创建iptables规则,并且事后创建规则极其复杂,超出了这些说明的范围。

与 firewalld 集成

如果使用iptables选项设置为true运行Docker,并且启用了firewalld,Docker会自动创建一个名为dockerfirewalld区域,目标为ACCEPT

Docker创建的所有网络接口(例如,docker0)都将插入docker区域。

Docker还会创建一个名为docker-forwarding的转发策略,允许从ANY区域到docker区域的转发。

Docker 和 ufw

Uncomplicated Firewall (ufw) 是Debian和Ubuntu附带的前端,它允许您管理防火墙规则。Docker和ufw使用iptables的方式使其彼此不兼容。

使用Docker发布容器端口时,进出该容器的流量会在通过ufw防火墙设置之前被转移。Docker在nat表中路由容器流量,这意味着数据包在到达ufw使用的INPUTOUTPUT链之前就被转移。在应用防火墙规则之前路由数据包,有效地忽略了您的防火墙配置。