数据包过滤和防火墙

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

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

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

注意

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

没有为 iptablesipvlanmacvlan 网络创建 host 规则。

Docker 和 iptables 链

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

  • DOCKER-USER
    • 一个用于用户自定义规则的占位符,这些规则将在 DOCKER 链中的规则之前处理。
  • DOCKER
    • 根据正在运行的容器的端口转发配置,确定是否应接受不属于已建立连接的数据包的规则。
  • DOCKER-ISOLATION-STAGE-1DOCKER-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

最后,您可以指定要接受的IP地址范围,使用 --src-range (请记住在使用 --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 模式时,端口 80 将在容器的 IP 地址上打开。端口 8080 不会在主机的 IP 地址上打开,且发出的数据包将使用容器的 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 引擎的容器网络。

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

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

与 firewalld 集成

如果您运行 Docker 时将 iptables 选项设置为 true,并且 firewalld 在您的系统上已启用,Docker 会自动创建一个目标为 ACCEPTfirewalld 区域,名为 docker

由 Docker 创建的所有网络接口(例如,docker0)都会被插入到 docker 区域中。

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

Docker 和 ufw

简单防火墙 (ufw) 是随 Debian 和 Ubuntu 提供的前端工具, 它允许您管理防火墙规则。Docker 和 ufw 使用 iptables 的方式 使它们彼此不兼容。

当您使用Docker发布容器的端口时,进出该容器的流量在经过ufw防火墙设置之前就会被分流。 Docker在nat表中路由容器流量,这意味着数据包在到达ufw使用的INPUTOUTPUT链之前就被分流了。 数据包在防火墙规则可以应用之前就被路由,实际上忽略了您的防火墙配置。