[英]What is the best practice of docker + ufw under Ubuntu
我刚刚试用了 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
-> .......
我想通过 ufw 管理传入流量(例如限制访问),因此我不希望 docker 接触我的 iptables。 这是我的测试
环境:
--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
,那么容器的 inte.net 连接将恢复正常,但 ufw 将无法“正常”工作(好吧......根据我的定义)。
那么,docker + ufw 的最佳实践是什么? 谁能提供一些帮助?
这个问题已经存在很长时间了。
在 Docker 中禁用 iptables 会带来其他问题。
如果您根据我们在互联网上找到的当前解决方案修改了您的服务器,请先回滚这些更改,包括:
--iptables=false
,包括配置文件/etc/docker/daemon.json
。DROP
而不是ACCEPT
。/etc/ufw/after.rules
与Docker网络相关的规则。本方案只需要修改一个UFW配置文件,所有Docker配置和选项保持默认。 不需要禁用 docker iptables 功能。
修改UFW配置文件/etc/ufw/after.rules
,在文件/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
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
ufw-user-forward
而不是ufw-user-input
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
ufw-user-forward
亲:
无法通过同一命令同时公开在主机和容器上运行的服务。
比如我们要发布容器的8080
端口,使用如下命令:
ufw route allow 8080
公网可以访问所有容器端口为8080
已发布端口。
但是主机的8080
端口仍然无法被公网访问。 如果我们要这样做,执行以下命令,允许公共单独访问主机上的端口:
ufw allow 8080
骗局:
不支持老版本的 Ubuntu,命令有点复杂。 但是你可以使用我的脚本https://github.com/chaifeng/ufw-docker 。
如果我们使用旧版本的 Ubuntu,我们可以使用ufw-user-input
链。 但是要小心避免暴露不应该暴露的服务。
如果我们使用的是支持ufw route
子命令的较新版本的 Ubuntu,我们最好使用ufw-user-forward
链,并使用ufw route
命令来管理容器的防火墙规则。
脚本ufw- docker 现在支持 Docker Swarm。 更多请看最新代码, https://github.com/chaifeng/ufw-docker
安装 Docker Swarm 模式在 Swarm 模式下使用时,我们只能在管理器节点上使用此脚本来管理防火墙规则。
after.rules
文件,包括管理器和工作器在 Docker Swarm 模式下运行,此脚本将添加一个全局服务ufw-docker-agent
。 镜像chaifeng/ufw-docker-agent也是从这个项目自动构建的。
几个月前我就遇到过这样的问题,最近决定在我的博客上描述这个问题和解决方案。 这是捷径。
使用--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 混乱行为,同时 docker 提供了必要的路由,因此容器可以很好地进行传出连接。 不过,从现在开始,UFW 规则仍将受到限制。
希望这可以为您和任何来到这里寻找答案的人解决问题。
我在https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/更全面地描述了问题和解决方案
我花了两个小时来尝试上面和其他帖子中的建议。 唯一有效的解决方案来自这个Github 线程中Tsuna 的帖子:
在
/etc/ufw/after.rules
的末尾追加以下/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
并撤消任何和所有:
- 从 /etc/docker/daemon.json 中删除 "iptables": "false"
- 恢复到 /etc/default/ufw 中的 DEFAULT_FORWARD_POLICY="DROP"
- 删除对 /etc/ufw/before.rules 的任何与 docker 相关的更改
- 请务必测试重启后一切正常。 我仍然相信 Docker 的开箱即用行为是危险的,由于 Docker 在其他安全的 iptables 配置中打孔,更多人将继续无意中将内部服务暴露给外部世界。
我不喜欢 iptables 所需的操作开销:docker 守护进程中的 false 标志。 事实上,从我所见,如果我错了,请纠正我,所有的解决方案都太复杂了。
只需将其插入 /etc/ufw/after.rules 中的 *filter 部分之前:
*mangle
# Allow a whitelisted ip to access postgres port
-I PREROUTING 1 -s <whitelisted_ip> -p tcp --dport 5432 -j ACCEPT
# Allow everyone to access port 8080
-I PREROUTING 2 -p tcp --dport 8080 -j ACCEPT
# Drop everything else
-I PREROUTING 3 -p tcp -j DROP
COMMIT
没有必要搞乱 docker 网络或不必要的黑客攻击。
对于@mkubaczyk对整个设置中涉及更多桥接网络的情况的回答,这是值得的。 这些可能由 Docker-Compose 项目提供,这里是如何生成正确规则的,因为这些项目是由systemd
控制的。
/etc/systemd/system/compose-project@.service
[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
显然,这不会很好地扩展。
同样值得注意的是,整个基本概念将掩盖容器中运行的应用程序的任何连接的来源。
很抱歉挖掘这个旧线程。 我遇到了同样的问题,它帮助我将 ufw 限制为特定的 ip 和接口。 因为默认情况下 ufw 应用于所有网络接口,也是来自 docker 的内部接口。 这就是为什么所有这些漂亮的 docker 端口转发故事(如 -p80:8080)都不起作用的原因。 为了克服这个问题,只需指定一个特定的接口和 ip 应该应用什么 ufw。 就我而言,它是在服务器上向世界公开的那个。
ufw allow in on eth0 to ip_of_eth0 port 22 proto tcp
ufw allow in on eth0 to ip_of_eth0 port 80 proto tcp
ufw allow in on eth0 to ip_of_eth0 port 443 proto tcp
将 eth0 更改为您想要的接口。
有了这个解决方案,现在可以不用搞乱 iptables 或 /etc/docker/daemon.json 标志中的 iptables:false 只公开真正需要的端口。
从外部计算机输出 nmap:
Starting Nmap 7.91 ( https://nmap.org ) at <time>
Nmap scan report for <domain> (ip)
Host is up (0.042s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
Nmap done: 1 IP address (1 host up) scanned in 11.44 seconds
我有一个类似的情况。
我解决它的方法是创建一个 custom.network 并将其定义为外部。
docker network create my_app_net
# put this in all the project related containers' docker compose files.
networks:
- my_app_net
networks:
my_app_net:
external: true
然后我能够通过我定义的主机名连接到容器(在 docker 中)。
主机名:“my_app_db” 容器名称:“my_app_db”
然后我能够从其中一个容器连接到数据库服务器。 我还确保服务器绑定到所有 IP,例如 0.0.0.0(我为此使用自定义 my.cnf 文件)mysql -uUSER -pPASS -hDOCKER_HOST --port 3306 --protocol=tcp DB_NAME
另一个重要的细节是在创建 mysql 用户时允许它通过将 % 指定为 db host 而不是 user@localhost 从任何主机连接。
不太确定您要问什么,但从我可以收集到的信息来看,您想更好地控制谁可以访问在 Docker 中运行的应用程序? 我在这里回答了一个类似的问题,通过前端代理而不是 IP 表来控制流量阻止外部访问 docker 容器
希望这可以帮助
迪伦
通过上述方法,您可以使用 UFW 只允许传入连接到端口 80(即代理)。 这将任何端口暴露保持在最低限度,额外的好处是您可以通过代理配置和 DNS 控制流量
我刚刚试用了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
-> .......
我想通过ufw管理传入流量(例如,限制访问),因此我不希望docker触摸我的iptables。 这是我的测试
环境:
第一次尝试
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
第二次尝试
然后我发现了这一点:无法使用--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的最佳实践是什么? 谁能提供帮助?
谢谢。
巴特
免责声明:此响应适用于 ufw(即 Ubuntu)由于默认/标准 Docker 网桥网络在 172.17.0.0/16 上工作(请参阅docker inspect bridge
子网),恕我直言,最直接的做法是:
ufw allow from 172.17.0.0/16
我还发现了这个可怕的问题,并且我已经阅读了很多内容以试图了解这里发生了什么。
UFW 非常简单,如果我不是被迫的话,我不想深入研究 iptables。 关于 iptables / ufw 的 Docker 行为对我来说似乎还可以,尽管没有足够的记录。 我的观点是,在启动容器时,应该确切了解暴露的端口发生了什么。 然后docker ps
命令给出了关于正在发生的事情的很好的反馈。
让我们运行一个 MariaDb 容器:
$ docker run --detach --env MARIADB_ROOT_PASSWORD="superSecret" mariadb:10.4
$ docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES PORTS
happy_jackson 3306/tcp
此处 PORTS 列显示3306/tcp
:端口 3306 可能可用但实际上未发布,这意味着 3306 端口既不能从主机也不能从主机网络访问。
让我们运行另一个 MariaDb 容器:
$ docker run --detach --env MARIADB_ROOT_PASSWORD="superSecret" -p 3306:3306 mariadb:10.4
$ docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES PORTS
trusting_goodall 0.0.0.0:3306->3306/tcp
现在 PORTS 列显示0.0.0.0:3306->3306/tcp
:端口已发布,这意味着它的端口可从主机和主机网络获得。
让我们运行最后一个 MariaDb 容器:
$ docker run --detach --env MARIADB_ROOT_PASSWORD="superSecret" -p 127.0.0.1:3306:3306 mariadb:10.4
$ docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES PORTS
quizzical_gauss 127.0.0.1:3306->3306/tcp
现在 PORTS 列显示127.0.0.1:3306->3306/tcp
:端口 3306 是本地发布的,这意味着它只能从主机使用,不能从主机网络使用。
所以是的,Docker 必须调整 UFW,但这只是为了实现所要求的:在本地或网络上公开端口。 所以只要你知道你在做什么用端口发布,你应该是安全的。
此外,虽然我不是网络安全专家,但在我的服务器上进行了一些完整的端口扫描让我放心:我得到的结果与预期一致。
在 Ubuntu 22.04 更改了链的名称。 我在文件 /etc/ufw/after.rules 中添加了
-A ufw-after-forward -j ACCEPT -s 172.16.0.0/12
提交前
总结@mkubaczyk 的帖子:
cat << EOF >> /etc/docker/daemon.json
{
"iptables": false
}
EOF
echo "DOCKER_OPTS=\"--iptables=false\"" >> /etc/default/docker
service docker restart
sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
cat << EOF >> /etc/ufw/before.rules
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]
# Forward traffic through eth0 - Change to match you 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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.