数据包过滤和防火墙

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

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

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

注意

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

对于 ipvlanmacvlanhost 网络,不会创建 iptables 规则。

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过滤链的顶部插入一个否定规则。例如,以下规则丢弃所有来自IP地址192.0.2.2以外的数据包:

$ 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=com.docker.network.bridge.gateway_mode_ipv4=

默认是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,禁用IPv4访问容器的端口80,请使用未指定的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段内的主机(例如,连接到同一网络交换机的主机)可以访问发布到本地主机的端口。 有关更多信息,请参阅 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 会自动创建一个名为 dockerfirewalld 区域,目标为 ACCEPT

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

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

Docker 和 ufw

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

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