数据包过滤和防火墙
在 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::2
和2001: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
,并且不得提供主机端口。
如果在网络中设置了路由,则在nat
或routed
模式下,映射的容器端口可从任何远程地址访问,除非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
可以在守护进程配置中将iptables
或ip6tables
键设置为false
,但这对于大多数用户来说并不适用。这可能会破坏Docker Engine的容器网络。
所有容器的所有端口都将可从网络访问,并且没有任何端口会从Docker主机IP地址映射。
无法完全阻止Docker创建iptables
规则,并且事后创建规则极其复杂,超出了这些说明的范围。
与 firewalld 集成
如果使用iptables
选项设置为true
运行Docker,并且启用了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
链之前就被转移。在应用防火墙规则之前路由数据包,有效地忽略了您的防火墙配置。