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

问题描述 投票:22回答:4

我刚尝试了Docker。它真棒,但似乎与ufw不太合作。默认情况下,docker会稍微操纵iptables。结果不是错误,而是我所期望的。有关更多详细信息,请阅读The dangers of 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
                       -> .......

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

环境:

  • 一个新安装的Ubuntu 14.04(内核:3.13.0-53)
  • Docker 1.6.2
  • ufw转发已启用。(Enable UFW forwarding
  • --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

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

Error response from daemon: Cannot start container

第二次尝试

然后我发现了这个:unable to link containers with --iptables=false #12701

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

sudo iptables -N DOCKER

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

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:/# 

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

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

谢谢。

巴特。

ubuntu docker containers firewall iptables
4个回答
20
投票

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

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

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

{
  "iptables": false
}

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

完成后,再做两件事:

$ sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ ufw reload

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

$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE

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

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

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


36
投票

问题

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

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

首先回滚更改

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

  • 启用Docker的iptables功能。删除所有更改,如--iptables=false,包括配置文件/etc/docker/daemon.json
  • UFW的默认FORWARD规则更改回默认的DROP而不是ACCEPT
  • 在UFW配置文件/etc/ufw/after.rules中删除与Docker网络相关的规则。
  • 如果您已修改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

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

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

ufw route allow proto tcp from any to any port 80

此命令允许公共网络访问其容器端口为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

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

ufw route allow proto udp from any to any port 53

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

ufw route allow proto udp from any to 172.17.0.2 port 53

这个怎么运作?

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

-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

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

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

以下规则阻止所有公共网络发起的连接请求,但允许内部网络访问外部网络。对于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

更多

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

Usage

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

更新:2018-09-10

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

using ufw-user-input

优点:

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

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

ufw allow 8080

有:

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

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

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

ufw allow proto tcp from any to 172.16.0.3 port 8080

using ufw-user-forward

优点:

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

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

ufw route allow 8080

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

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

ufw allow 8080

有:

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

Conclusion

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

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


更新:2018年10月6日

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

Install for Docker Swarm mode

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

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

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


0
投票

不太确定你的要求,但从我能收集的内容中你想更好地控制谁可以访问你在Docker中运行的应用程序?我在这里回答了类似的问题,通过前端代理而不是IP表Block external access to docker containers来控制流量

希望这可以帮助

迪伦

编辑

使用上述方法,您可以使用UFW仅允许到端口80(即代理)的传入连接。这样可以将任何端口暴露降至最低,并且可以通过代理配置和DNS控制流量


0
投票

对于@mkubaczyk's answer的附录,这是值得的,因为在整个设置中涉及更多的桥接网络。这些可能由Docker-Compose项目提供,这里是如何生成适当的规则,因为这些项目由systemd控制。

/etc/systemd/system/[email protected]

[Unit]
Description=Docker-Compose project: %I
After=docker.service
BindsTo=docker.service
AssertPathIsDirectory=/<projects_path>/%I
AssertFileNotEmpty=/<projects_path>/%I/docker-compose.yml

[Service]
Type=simple
Restart=always
WorkingDirectory=/<projects_path>/%I
ExecStartPre=/usr/bin/docker-compose up --no-start --remove-orphans
ExecStartPre=+/usr/local/bin/update-iptables-for-docker-bridges
ExecStart=/usr/bin/docker-compose up
ExecStop=/usr/bin/docker-compose stop --timeout 30
TimeoutStopSec=30
User=<…>
StandardOutput=null

[Install]
WantedBy=multi-user.target

/usr/local/bin/update-iptables-for-docker-bridges

#!/bin/sh

for network in $(docker network ls --filter 'driver=bridge' --quiet); do
  iface=$(docker network inspect --format '{{index .Options "com.docker.network.bridge.name"}}' ${network})
  [ -z $iface ] && iface="br-${network}"
  subnet=$(docker network inspect --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' ${network})
  rule="! --out-interface ${iface} --source ${subnet} --jump MASQUERADE"
  iptables --table nat --check POSTROUTING ${rule} || iptables --table nat --append POSTROUTING ${rule}
done

显然,这不会很好地扩展。

值得注意的是,整个基本概念将伪装在容器中运行的应用程序的任何连接的来源。

© www.soinside.com 2019 - 2024. All rights reserved.