在Ubuntu下,docker + ufw的最佳实践是什么?

Yi-*_*ang 36 ubuntu containers firewall iptables docker

我刚尝试了Docker.它真棒,但似乎与ufw不太合作.默认情况下,docker会稍微操纵iptables.结果不是错误,而是我所期望的.有关详细信息,请阅读UFW + Docker的危险

我的目标是建立一个类似的系统

    Host (running ufw) -> docker container 1 - nginx (as a reverse proxy)
                       -> docker container 2 - node web 1
                       -> docker container 3 - node web 2
                       -> .......
Run Code Online (Sandbox Code Playgroud)

我想通过ufw管理传入流量(例如限制访问),因此我不希望docker触摸我的iptables.这是我的测试

环境:

  • 一个新安装的Ubuntu 14.04(内核:3.13.0-53)
  • Docker 1.6.2
  • ufw转发已启用.(启用UFW转发)
  • --iptables=false 已添加到Docker守护程序中.

第一次尝试

docker run --name ghost -v /home/xxxx/ghost_content:/var/lib/ghost -d ghost
docker run --name nginx -p 80:80 -v /home/xxxx/nginx_site_enable:/etc/nginx/conf.d:ro --link ghost:ghost -d nginx
Run Code Online (Sandbox Code Playgroud)

没运气.第一个命令没问题,但第二个命令会抛出错误

Error response from daemon: Cannot start container
Run Code Online (Sandbox Code Playgroud)

第二次尝试

然后我发现了这个:无法用--iptables = false#12701链接容器

运行以下命令后,一切看起来都正常.

sudo iptables -N DOCKER
Run Code Online (Sandbox Code Playgroud)

但是,我注意到我无法在容器内建立任何出站连接.例如:

xxxxg@ubuntu:~$ sudo docker exec -t -i nginx /bin/bash
root@b0d33f22d3f4:/# ping 74.125.21.147
PING 74.125.21.147 (74.125.21.147): 56 data bytes
^C--- 74.125.21.147 ping statistics ---
35 packets transmitted, 0 packets received, 100% packet loss
root@b0d33f22d3f4:/# 
Run Code Online (Sandbox Code Playgroud)

如果我--iptables=false从Docker守护进程中删除,那么容器的互联网连接将恢复正常,但是ufw将无法正常工作(嗯......按照我的定义).

那么,docker + ufw的最佳实践是什么?有谁可以提供一些帮助?

谢谢.

巴特.

Fen*_*eng 49

问题

这个问题已存在很长时间了.

在Docker中禁用iptables会带来其他问题.

首先回滚更改

如果您根据我们在互联网上找到的当前解决方案修改了您的服务器,请首先回滚这些更改,包括:

  • 启用Docker的iptables功能.删除所有更改--iptables=false,包括配置文件/etc/docker/daemon.json.
  • UFW的默认FORWARD规则更改回默认值DROP而不是ACCEPT.
  • 删除UFW配置文件中与Docker网络相关的规则/etc/ufw/after.rules.
  • 如果您已修改Docker配置文件,请先重新启动Docker.稍后我们将修改UFW配置,然后我们可以重新启动它.

解决UFW和Docker问题

此解决方案只需要修改一个UFW配置文件,所有Docker配置和选项仍然是默认设置.不需要禁用docker iptables函数.

修改UFW配置文件,/etc/ufw/after.rules并在文件末尾添加以下规则:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
Run Code Online (Sandbox Code Playgroud)

使用该命令sudo systemctl restart ufw在更改文件后重新启动UFW.现在公共网络无法访问任何已发布的docker端口,容器和专用网络可以定期互访,容器也可以从内部访问外部网络.

如果要允许公共网络访问Docker容器提供的服务,例如,容器的服务端口是80.运行以下命令以允许公共网络访问此服务:

ufw route allow proto tcp from any to any port 80
Run Code Online (Sandbox Code Playgroud)

此命令允许公共网络访问其容器端口为80的所有已发布端口.

注意:如果我们使用选项发布端口-p 8080:80,我们应该使用容器端口80,而不是主机端口8080.

如果有多个容器的服务端口为80,但我们只希望外部网络访问特定容器.例如,如果容器的私有地址是172.17.0.2,请使用以下命令:

ufw route allow proto tcp from any to 172.17.0.2 port 80
Run Code Online (Sandbox Code Playgroud)

如果服务的网络协议是UDP(例如,DNS服务),则可以使用以下命令允许外部网络访问所有已发布的DNS服务:

ufw route allow proto udp from any to any port 53
Run Code Online (Sandbox Code Playgroud)

同样,如果仅用于特定容器,例如IP地址172.17.0.2:

ufw route allow proto udp from any to 172.17.0.2 port 53
Run Code Online (Sandbox Code Playgroud)

这个怎么运作?

以下规则允许专用网络能够相互访问.通常,专用网络比公共网络更受信任.

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
Run Code Online (Sandbox Code Playgroud)

以下规则允许UFW管理是否允许公共网络访问Docker容器提供的服务.这样我们就可以在一个地方管理所有防火墙规则.

-A DOCKER-USER -j ufw-user-forward
Run Code Online (Sandbox Code Playgroud)

以下规则阻止所有公共网络发起的连接请求,但允许内部网络访问外部网络.对于TCP协议,它阻止主动建立来自公共网络的TCP连接.对于UDP协议,阻止对小于32767的所有端口的访问.为什么这个港口?由于UDP协议是无状态的,因此不可能像TCP那样阻止发起连接请求的握手信号.对于GNU/Linux,我们可以在文件中找到本地端口范围/proc/sys/net/ipv4/ip_local_port_range.默认范围是32768 60999.从正在运行的容器访问UDP协议服务时,将从端口范围中随机选择一个本地端口,服务器将数据返回到此随机端口.因此,我们可以假设所有容器内的UDP协议的侦听端口小于32768.这就是我们不希望公共网络访问少于32768的UDP端口的原因.

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
Run Code Online (Sandbox Code Playgroud)

更多

https://github.com/chaifeng/ufw-docker

sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
chmod +x /usr/local/bin/ufw-docker
Run Code Online (Sandbox Code Playgroud)

用法

ufw-docker help
ufw-docker install
ufw-docker status
ufw-docker allow webapp
ufw-docker allow webapp 80
ufw-docker allow webapp 53/udp
ufw-docker list webapp
ufw-docker delete allow webapp 80/tcp
ufw-docker delete allow webapp
Run Code Online (Sandbox Code Playgroud)

更新:2018-09-10

选择的原因ufw-user-forward,而不是ufw-user-input

运用 ufw-user-input

优点:

易于使用和理解,支持旧版本的Ubuntu.

例如,要允许公众访问其容器端口所在的已发布端口8080,请使用以下命令:

ufw allow 8080
Run Code Online (Sandbox Code Playgroud)

缺点:

它不仅暴露容器的端口,还暴露主机的端口.

例如,如果服务正在主机上运行,​​并且端口是8080.该命令ufw allow 8080允许公共网络访问服务以及容器端口所在的所有已发布端口8080.但是我们只想公开在主机上运行的服务,或者只是在容器内运行的服务,而不是两者.

为避免此问题,我们可能需要对所有容器使用类似于以下的命令:

ufw allow proto tcp from any to 172.16.0.3 port 8080
Run Code Online (Sandbox Code Playgroud)

运用 ufw-user-forward

优点:

无法通过同一命令同时公开在主机和容器上运行的服务.

例如,如果我们要发布8080容器的端口,请使用以下命令:

ufw route allow 8080
Run Code Online (Sandbox Code Playgroud)

公共网络可以访问其容器端口所在的所有已发布端口8080.

8080公共网络仍然无法访问主机的端口.如果我们想这样做,请执行以下命令以允许公共访问主机上的端口:

ufw allow 8080
Run Code Online (Sandbox Code Playgroud)

缺点:

不支持旧版本的Ubuntu,命令有点复杂.但是你可以使用我的脚本https://github.com/chaifeng/ufw-docker.

结论

如果我们使用旧版本的Ubuntu,我们可以使用ufw-user-input链.但要小心避免暴露不应暴露的服务.

如果我们使用的是支持ufw route子命令的更新版本的Ubuntu ,我们最好使用ufw-user-forwardchain,并使用ufw route命令来管理容器的防火墙规则.


更新:2018年10月6日

脚本ufw- docker现在支持Docker Swarm.请参阅最新代码,https://github.com/chaifeng/ufw-docker

安装Docker Swarm模式

在Swarm模式下使用时,我们只能在管理器节点上使用此脚本来管理防火墙规则.

  • 修改所有after.rules节点上的所有文件,包括管理员和工作人员
  • 在管理器节点上部署此脚本

在Docker Swarm模式下运行,此脚本将添加全局服务ufw-docker-agent.图像chaifeng/ufw-docker-agent也是从该项目自动构建的.

  • 完美的!以为我必须用 `172.17.0.0` 替换 `172.16.0.0` (3认同)
  • @Feng 您的解决方案看起来很有趣。还有另一个稍微简单的提议 [here](https://github.com/moby/moby/issues/4737#issuecomment-419705925)。我还没有时间分析这些差异,但也许您可以对此发表评论?另一件事:我认为您可以将“内部端口”重命名为“主机端口”。我很困惑“内部端口”是什么意思。在 docker 手册中,它总是主机或容器端口,我觉得这更清楚。 (3认同)
  • 我在你的 github repo 上提交了一个问题,在那里解决会更容易:https://github.com/chaifeng/ufw-docker/issues/11 (2认同)
  • 我不明白为什么这没有被标记为最佳答案...我在第一个解决方案上浪费了一整天的时间,直到找到这个。 (2认同)

mku*_*zyk 32

几个月前我遇到过这样的问题,最近决定在我的博客上描述问题和解决方案.这是捷径.

使用--iptables=false对你描述的情况没有多大帮助.这里还不够.默认情况下,您的容器都不能执行任何传出连接.

在这里有一个小步骤,你想要在UFW后面放置容器.您可以按如下方式使用--iptables=false或创建/etc/docker/daemon.json包含内容的文件

{
  "iptables": false
}
Run Code Online (Sandbox Code Playgroud)

结果将是相同的,但后一个选项要求您重启整个docker服务,service docker restart甚至重新启动,如果docker有机会在禁用此功能之前添加iptables规则.

完成后,再做两件事:

$ sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ ufw reload
Run Code Online (Sandbox Code Playgroud)

所以你在UFW中设置默认转发策略以进行接受,并使用:

$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE
Run Code Online (Sandbox Code Playgroud)

这样你所实现的是在你的iptables规则中禁用docker messy行为,同时docker提供了必要的路由,因此容器可以很好地完成传出连接.不过,UFW规则仍将受到限制.

希望这能为您解决问题以及任何在此寻找答案的问题.

我在https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/上更全面地描述了问题和解决方案

  • 要持久保留 iptables 规则,请根据您的服务器发行版安装 linux 软件包 iptables-persistent,在我的情况下 (Debian) 是 sudo apt install iptables-persistent,并且软件包安装会将 NAT 规则添加到启动时执行的持久文件中。 (3认同)

小智 21

并不是说这里的解决方案是错误的,但对于寻求快速一步指令的人来说,它们看起来有点“可怕”和错误修剪。我最近也遇到了这个问题,在网上阅读了所有类似的答案,但在撰写本文时没有找到任何快速而清晰的内容。令人惊讶的是,我的替代解决方案很容易理解和管理,而且很有效:只需在主机之外实施防火墙

  • Digital Ocean 拥有令人惊叹的防火墙,零额外成本,所见即所得风格。
  • AWS 提供安全组
  • 等等。

将防火墙视为一等公民似乎有很多好处。

  • 你刚刚救了我! (2认同)

小智 14

我花了两个小时来尝试上面和其他帖子中的建议。唯一有效的解决方案来自这个Github 线程中Tsuna 的帖子:

在末尾附加以下内容/etc/ufw/after.rules(将 eth0 替换为面向外部的接口):

# Put Docker behind UFW
*filter
:DOCKER-USER - [0:0]
:ufw-user-input - [0:0]

-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-USER -m conntrack --ctstate INVALID -j DROP
-A DOCKER-USER -i eth0 -j ufw-user-input
-A DOCKER-USER -i eth0 -j DROP
COMMIT
Run Code Online (Sandbox Code Playgroud)

并撤消任何和所有:

  • 从 /etc/docker/daemon.json 中删除 "iptables": "false"
  • 恢复到 /etc/default/ufw 中的 DEFAULT_FORWARD_POLICY="DROP"
  • 删除对 /etc/ufw/before.rules 的任何与 docker 相关的更改
  • 请务必测试重启后一切正常。我仍然相信 Docker 的开箱即用行为是危险的,由于 Docker 在其他安全的 iptables 配置中打孔,更多的人将继续无意中将内部服务暴露给外部世界。

  • ```sudo ufw reload``` 输出错误:无法加载日志记录规则 (4认同)

小智 7

我也遇到了这个问题。就我而言,我决定为整个 docker0 接口打开 ufw 防火墙。

sudo ufw allow in docker0
Run Code Online (Sandbox Code Playgroud)

或者,您也可以允许 docker 使用子网。例如:

sudo ufw allow from 172.0.0.0/8
Run Code Online (Sandbox Code Playgroud)

这两条规则都对我有用。


ATo*_*ras 6

免责声明:此响应适用于 ufw(即 Ubuntu) 由于默认/标准 Docker 桥接网络在 172.17.0.0/16 上工作(请参阅docker inspect bridge子网),最简单的恕我直言是:

ufw allow from 172.17.0.0/16
Run Code Online (Sandbox Code Playgroud)


J. *_*ron 5

总结@mkubaczyk 的帖子:

告诉 docker 远离我的防火墙

cat << EOF >> /etc/docker/daemon.json
{
     "iptables": false
}
EOF

echo "DOCKER_OPTS=\"--iptables=false\"" >>  /etc/default/docker
service docker restart
Run Code Online (Sandbox Code Playgroud)

更改 UFW 远期政策

sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw 
Run Code Online (Sandbox Code Playgroud)

添加针对容器的 nat 规则

cat << EOF >> /etc/ufw/before.rules
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]

# Forward traffic through eth0 - Change to match your out-interface
-A POSTROUTING -s 10.66.66.0/24 -o ens0 -j MASQUERADE

# don't delete the 'COMMIT' line or these nat table rules won't
# be processed
COMMIT

EOF
ufw reload
Run Code Online (Sandbox Code Playgroud)