数据包过滤和防火墙

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

由于这些规则对于 Docker bridge 网络的正常运行至关重要,您不应修改 Docker 创建的规则。

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

注意

Docker 会为 bridge 网络创建 iptables 规则。

ipvlanmacvlanhost 网络不会创建 iptables 规则。

Docker 和 iptables 链

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

  • DOCKER-USER
    • 一个用户定义规则的占位符,这些规则将在 DOCKER-FORWARDDOCKER 链中的规则之前处理。
  • DOCKER-FORWARD
    • Docker 网络处理的第一阶段。将与已建立连接无关的数据包传递到其他 Docker 链的规则,以及接受属于已建立连接的数据包的规则。
  • DOCKER
    • 根据运行容器的端口转发配置,确定是否接受不属于已建立连接的数据包的规则。
  • DOCKER-ISOLATION-STAGE-1DOCKER-ISOLATION-STAGE-2
    • 用于隔离 Docker 网络彼此之间的规则。
  • DOCKER-INGRESS
    • 与 Swarm 网络相关的规则。

FORWARD 链中,Docker 添加了无条件跳转到 DOCKER-USERDOCKER-FORWARDDOCKER-INGRESS 链的规则。

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

在 Docker 规则之前添加 iptables 策略

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

附加到 FORWARD 链的规则将在 Docker 的规则之后处理。

匹配请求的原始 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,daemon 会阻止对未发布的端口的访问。已发布的容器端口会映射到主机 IP 地址。为此,它使用 iptables 执行网络地址转换 (NAT)、端口地址转换 (PAT) 和 IP 伪装。

例如,docker run -p 8080:80 [...] 会在 Docker 主机上任意地址的端口 8080 与容器的端口 80 之间创建映射。从容器发出的连接将进行 IP 伪装,使用 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 主机外部访问 bridge 网络上的容器,您必须通过 Docker 主机上的一个地址设置到 bridge 网络的路由。这可以通过静态路由、边界网关协议 (BGP) 或适用于您网络的任何其他方式实现。

在本地的第 2 层网络中,远程主机可以使用 Docker daemon 主机在本地网络上的地址设置到容器网络的静态路由。这些主机可以直接访问容器。对于本地网络之外的远程主机,直接访问容器需要路由器配置以启用必要的路由。

网关模式

bridge 网络驱动程序有以下选项:

  • com.docker.network.bridge.gateway_mode_ipv6
  • com.docker.network.bridge.gateway_mode_ipv4

每个选项都可以设置为以下网关模式之一:

  • nat
  • nat-unprotected
  • routed
  • isolated

默认模式是 nat,会为每个已发布的容器端口设置 NAT 和 IP 伪装规则。离开主机的数据包将使用主机地址。

routed 模式下,不会设置 NAT 或 IP 伪装规则,但仍会设置 iptables 以便只有已发布的容器端口可访问。从容器发出的数据包将使用容器的地址,而不是主机地址。

nat 模式下,当端口发布到特定主机地址时,该端口只能通过具有该地址的主机接口访问。因此,例如,将端口发布到回环接口上的地址意味着远程主机无法访问它。

然而,使用直接路由,已发布的容器端口总是可以从远程主机访问,除非 Docker 主机的防火墙有额外的限制。本地第 2 层网络上的主机可以在无需任何额外网络配置的情况下设置直接路由。本地网络之外的主机只有在网络路由器配置为启用直接路由时才能使用直接路由访问容器。

nat-unprotected 模式下,未发布的容器端口也可以使用直接路由访问,不会设置端口过滤规则。包含此模式是为了兼容旧的默认行为。

网关模式也影响连接到同一主机上不同 Docker 网络的容器之间的通信。

  • natnat-unprotected 模式下,其他 bridge 网络中的容器只能通过已发布到的主机地址访问已发布的端口。不允许从其他网络进行直接路由。
  • routed 模式下,其他网络中的容器可以使用直接路由访问端口,而无需通过主机地址。

routed 模式下,-p--publish 端口映射中的主机端口不使用,主机地址仅用于决定是将映射应用于 IPv4 还是 IPv6。因此,当映射仅适用于 routed 模式时,应只使用地址 0.0.0.0::,并且不应指定主机端口。如果指定了特定地址或端口,它对已发布的端口无效,并且会记录警告消息。

isolated 模式只能在创建网络时同时使用 CLI 标志 --internal 或其等效选项时使用。通常会在 internal 网络中的 bridge 设备上分配一个地址。因此,docker 主机上的进程可以访问该网络,网络中的容器可以访问在该 bridge 地址上监听的主机服务(包括监听“任意”主机地址 0.0.0.0:: 的服务)。当以网关模式 isolated 创建网络时,不会为 bridge 分配地址。

示例

创建适用于 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 模式,可以通过主机 IP 地址上的端口 8080 以及直接访问容器的端口 80。源自容器的连接将进行 IP 伪装,使用主机的 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,禁用 IPv4 对容器端口 80 的访问,请使用未指定的 IPv6 地址 [::],并且不包含主机端口号。

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

设置容器的默认绑定地址

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

例如,以下命令会将端口 8080 发布到主机上的所有网络接口,包括 IPv4 和 IPv6 地址,可能使其可供外部访问。

docker run -p 8080:80 nginx

您可以更改已发布容器端口的默认绑定地址,使其默认情况下只能由 Docker 主机访问。为此,您可以配置 daemon 使用回环地址(127.0.0.1)代替。

警告

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

要为用户定义的 bridge 网络配置此设置,请在创建网络时使用 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 网络接口上。

默认 bridge 网络

要为默认 bridge 网络设置默认绑定,请在 daemon.json 配置文件中配置 "ip" 键:

{
  "ip": "127.0.0.1"
}

这将把默认 bridge 网络上已发布容器端口的默认绑定地址更改为 127.0.0.1。重启 daemon 以使此更改生效。或者,您可以在启动 daemon 时使用 dockerd --ip 标志。

路由器上的 Docker

在 Linux 上,Docker 需要在主机上启用“IP 转发”。因此,如果 sysctl 设置 net.ipv4.ip_forwardnet.ipv6.conf.all.forwarding 在启动时未启用,它会启用它们。执行此操作时,它还会将 iptables 的 FORWARD 链策略设置为 DROP

如果 Docker 将 FORWARD 链的策略设置为 DROP。这将阻止您的 Docker 主机充当路由器,这是启用 IP 转发时的推荐设置。

要阻止 Docker 将 FORWARD 链的策略设置为 DROP,请在 /etc/docker/daemon.json 中包含 "ip-forward-no-drop": true,或将选项 --ip-forward-no-drop 添加到 dockerd 命令行。

或者,您可以将 ACCEPT 规则添加到 DOCKER-USER 链中,以允许您希望转发的数据包。例如

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

警告

在早于 28.0.0 的版本中,Docker 总是将 IPv6 FORWARD 链的默认策略设置为 DROP。在 28.0.0 及更新版本中,仅当 Docker 自身启用 IPv6 转发时,才会设置该策略。IPv4 转发的行为一直如此。

如果在 Docker 启动之前在主机上启用了 IPv6 转发,请检查主机的配置以确保其仍然安全。

阻止 Docker 操作 iptables

可以将 守护进程配置 中的 iptablesip6tables 键设置为 false,但此选项不适合大多数用户。这可能会破坏 Docker Engine 的容器网络。

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

完全阻止 Docker 创建 iptables 规则是不可能的,事后创建规则极其复杂,并且超出了本说明的范围。

与 firewalld 集成

如果您运行 Docker 时将 iptables 选项设置为 true,并且系统上启用了 firewalld,Docker 会自动创建一个名为 docker 的 firewalld 区域,其目标为 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 链之前就被转移了。数据包在防火墙规则应用之前就被路由,从而有效地忽略了您的防火墙配置。

页面选项