繁体   English   中英

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

[英]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。 这是我的测试

环境:

  • 新安装的 Ubuntu 14.04(内核:3.13.0-53)
  • Docker 1.6.2
  • ufw转发已启用。([启用UFW转发] 2
  • --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 会带来其他问题。

首先回滚更改

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

  • 启用 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 ,在文件/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

更新:2018-09-10

选择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命令来管理容器的防火墙规则。


更新: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也是从这个项目自动构建的。

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

使用--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/更全面地描述了问题和解决方案

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

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

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

我花了两个小时来尝试上面和其他帖子中的建议。 唯一有效的解决方案来自这个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。 这是我的测试

环境:

  • 新安装的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

没运气。 第一个命令很好,但是第二个命令会抛出错误

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 的帖子:

告诉 docker 远离我的防火墙

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

echo "DOCKER_OPTS=\"--iptables=false\"" >>  /etc/default/docker
service docker restart

更改 ufw 转发策略

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

添加针对容器的 nat 规则

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM