数据包过滤和防火墙
在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过滤链的顶部插入一个否定规则。例如,以下规则丢弃所有来自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::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= 和
com.docker.network.bridge.gateway_mode_ipv4=。
默认是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模式,端口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
可以在守护进程配置中将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
简单防火墙 (ufw) 是一个随 Debian 和 Ubuntu 一起提供的前端工具, 它允许你管理防火墙规则。Docker 和 ufw 使用 iptables 的方式 使得它们彼此不兼容。
当你使用Docker发布容器的端口时,进出该容器的流量在通过ufw防火墙设置之前会被重定向。Docker在nat表中路由容器流量,这意味着数据包在到达ufw使用的INPUT和OUTPUT链之前被重定向。数据包在防火墙规则应用之前被路由,实际上忽略了你的防火墙配置。