数据包过滤和防火墙
在 Linux 上,Docker 创建 iptables 和 ip6tables 规则来实现网络隔离、端口发布和过滤。
由于这些规则是 Docker 桥接网络正常运行所必需的,因此您不应修改 Docker 创建的规则。
但是,如果您在暴露于互联网的主机上运行 Docker,您可能需要添加 iptables 策略,以防止未经授权的访问容器或主机上运行的其他服务。本页描述了如何实现这一点,以及您需要注意的警告。
注意Docker 为桥接网络创建
iptables规则。不会为
ipvlan、macvlan或host网络创建iptables规则。
Docker 和 iptables 链
在 filter 表中,Docker 将默认策略设置为 DROP,并创建以下自定义 iptables 链:
DOCKER-USER- 一个占位符,用于用户定义的规则,这些规则将在
DOCKER-FORWARD和DOCKER链中的规则之前处理。
- 一个占位符,用于用户定义的规则,这些规则将在
DOCKER-FORWARD- Docker 网络的处理第一阶段。规则将与已建立连接无关的数据包传递到其他 Docker 链,以及接受属于已建立连接的数据包的规则。
DOCKER- 根据运行中容器的端口转发配置,确定是否应接受不属于已建立连接的数据包的规则。
DOCKER-ISOLATION-STAGE-1和DOCKER-ISOLATION-STAGE-2- 用于隔离 Docker 网络的规则。
DOCKER-INGRESS- 与 Swarm 网络相关的规则。
在 FORWARD 链中,Docker 添加了无条件跳转到 DOCKER-USER、DOCKER-FORWARD 和 DOCKER-INGRESS 链的规则。
在 nat 表中,Docker 创建 DOCKER 链并添加规则以实现伪装和端口映射。
在 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,守护进程会阻止对未发布端口的访问。发布的容器端口会映射到主机 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::2 和 2001:db8:2222::2 地址,您可以制定特定于 2001:db8:1111::2 的规则,并保持 2001:db8:2222::2 开放。
您可能需要允许来自允许的外部地址范围之外的服务器的响应。例如,容器可能会向不允许访问容器服务的主机发送 DNS 或 HTTP 请求。以下规则接受任何属于已被其他规则接受的流的入站或出站数据包。它必须放在限制来自外部地址范围访问的 DROP 规则之前。
$ iptables -I DOCKER-USER -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables 很复杂。更多信息请参见 Netfilter.org HOWTO。
直接路由
端口映射确保已发布的端口可以在主机的网络地址上访问,这些地址很可能对任何外部客户端都是可路由的。通常在主机的网络中不会为存在于主机内的容器地址设置路由。
但是,特别是在 IPv6 中,您可能更喜欢避免使用 NAT,而是安排外部路由到容器地址(“直接路由”)。
要从 Docker 主机外部访问桥接网络中的容器,您必须首先通过 Docker 主机上的一个地址设置到桥接网络的路由。这可以通过静态路由、边界网关协议 (BGP) 或任何其他适合您网络的方式来实现。例如,在本地第 2 层网络中,远程主机可以通过 Docker 守护进程主机在本地网络上的地址设置到容器网络的静态路由。
直接路由到桥接网络中的容器
默认情况下,远程主机不允许直接访问 Docker Linux 桥接网络中的容器 IP 地址。它们只能访问发布到主机 IP 地址的端口。
要允许直接访问任何 Linux 桥接网络中任何容器上的任何已发布端口,请在 /etc/docker/daemon.json 中使用守护进程选项 "allow-direct-routing": true 或等效的 --allow-direct-routing。
要允许从任何地方直接路由到特定桥接网络中的容器,请参阅 网关模式。
或者,要允许通过特定主机接口直接路由到特定桥接网络,请在创建网络时使用以下选项:
com.docker.network.bridge.trusted_host_interfaces
示例
创建一个网络,其中容器 IP 地址上发布的端口可以直接从接口 vxlan.1 和 eth3 访问:
$ docker network create --subnet 192.0.2.0/24 --ip-range 192.0.2.0/29 -o com.docker.network.bridge.trusted_host_interfaces="vxlan.1:eth3" mynet
在该网络中运行一个容器,将其 80 端口发布到主机的环回接口上的 8080 端口:
$ docker run -d --ip 192.0.2.100 -p 127.0.0.1:8080:80 nginx
现在可以从 Docker 主机通过 http://127.0.0.1:8080 访问容器 80 端口上运行的 Web 服务器,或者直接通过 http://192.0.2.100:80 访问。如果连接到接口 vxlan.1 和 eth3 的网络上的远程主机有到 Docker 主机内 192.0.2.0/24 网络的路由,它们也可以通过 http://192.0.2.100:80 访问 Web 服务器。
网关模式
桥接网络驱动程序具有以下选项:
com.docker.network.bridge.gateway_mode_ipv6com.docker.network.bridge.gateway_mode_ipv4
这些选项中的每一个都可以设置为以下网关模式之一:
natnat-unprotectedroutedisolated
默认是 nat,为每个已发布的容器端口设置 NAT 和伪装规则。离开主机的数据包将使用主机地址。
使用 routed 模式,不设置 NAT 或伪装规则,但仍然设置 iptables,以便只有已发布的容器端口是可访问的。来自容器的出站数据包将使用容器的地址,而不是主机地址。
在 nat 模式下,当一个端口被发布到特定的主机地址时,该端口只能通过具有该地址的主机接口访问。因此,例如,将端口发布到环回接口上的地址意味着远程主机无法访问它。
然而,使用直接路由,已发布的容器端口总是可以从远程主机访问,除非 Docker 主机的防火墙有额外的限制。本地第 2 层网络上的主机可以设置直接路由,而无需任何额外的网络配置。本地网络之外的主机只有在网络的路由器配置为启用它时才能使用直接路由到容器。
在 nat-unprotected 模式下,未发布的容器端口也可以使用直接路由访问,不设置端口过滤规则。此模式是为了与旧的默认行为兼容而包含的。
网关模式还影响连接到同一主机上不同 Docker 网络的容器之间的通信。
- 在
nat和nat-unprotected模式下,其他桥接网络中的容器只能通过它们发布到的主机地址访问已发布的端口。不允许从其他网络进行直接路由。 - 在
routed模式下,其他网络中的容器可以使用直接路由来访问端口,而无需通过主机地址。
在 routed 模式下,-p 或 --publish 端口映射中的主机端口不被使用,主机地址仅用于决定是将映射应用于 IPv4 还是 IPv6。因此,当映射仅适用于 routed 模式时,只应使用地址 0.0.0.0 或 ::,并且不应给出主机端口。如果给出了特定的地址或端口,它将对已发布的端口没有影响,并且会记录一条警告消息。
isolated 模式只能在网络同时使用 CLI 标志 --internal 或等效标志创建时使用。通常会为 internal 网络中的桥接设备分配一个地址。因此,docker 主机上的进程可以访问该网络,并且该网络中的容器可以访问在该桥接地址上监听的主机服务(包括在“任何”主机地址 0.0.0.0 或 :: 上监听的服务)。当网络以 isolated 网关模式创建时,不会为桥接设备分配地址。
示例
创建一个适合 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 端口访问,也可以从 Docker 主机内部直接访问。但是,容器的 80 端口不能从主机外部直接访问。源自容器的连接将使用主机的 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)。
警告在 28.0.0 之前的版本中,同一 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
在 Linux 上,Docker 需要在主机上启用“IP 转发”。因此,如果 sysctl 设置 net.ipv4.ip_forward 和 net.ipv6.conf.all.forwarding 尚未启用,它会在启动时启用它们。当它这样做时,它还会将 iptables FORWARD 链的策略设置为 DROP。
如果 Docker 将 FORWARD 链的策略设置为 DROP,这将阻止您的 Docker 主机充当路由器,这是启用 IP 转发时的推荐设置。
要阻止 Docker 将 FORWARD 链的策略设置为 DROP,请在 /etc/docker/daemon.json 中包含 "ip-forward-no-drop": true,或在 dockerd 命令行中添加选项 --ip-forward-no-drop。
或者,您可以为您想要转发的数据包向 DOCKER-USER 链添加 ACCEPT 规则。例如:
$ iptables -I DOCKER-USER -i src_if -o dst_if -j ACCEPT
警告在 28.0.0 之前的版本中,Docker 总是将 IPv6
FORWARD链的默认策略设置为DROP。在 28.0.0 及更新版本中,它只会在自己启用 IPv6 转发时才设置该策略。这一直是 IPv4 转发的行为。如果在 Docker 启动前您的主机上启用了 IPv6 转发,请检查您的主机配置以确保其仍然安全。
阻止 Docker 操作 iptables
可以在守护进程配置中将 iptables 或 ip6tables 键设置为 false,但此选项不适合大多数用户。这很可能会破坏 Docker 引擎的容器网络。
所有容器的所有端口都将可以从网络访问,并且没有一个会从 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 使用的 INPUT 和 OUTPUT 链之前就被转移了。数据包在防火墙规则可以应用之前就被路由了,实际上忽略了您的防火墙配置。