使用Docker时,简单防火墙(UFW)没有阻止任何东西。

这是我第一次设置Ubuntu服务器(14.04 LTS),我在配置防火墙(UFW)时遇到了问题。
我只需要ssh和http,所以我正在进行以下操作:
sudo ufw disable

sudo ufw reset
sudo ufw default deny incoming
sudo ufw default allow outgoing

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp

sudo ufw enable
sudo reboot

但我仍然可以连接到此机器上其他端口的数据库。你有什么想法,我做错了什么吗?
编辑:这些数据库位于 Docker 容器上。这可能相关吗?它是否覆盖了我的 ufw 配置?
编辑2:sudo ufw status verbose 的输出结果是:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)
80/tcp (v6)                ALLOW IN    Anywhere (v6)

什么数据库?哪个端口?你确定这是同一台机器吗?ufw status的输出是什么? - solsTiCe
@solsTiCe 是的,我确定是同一台机器。数据库是在 Docker 容器中的 InfluxDB,端口为 80838086。我在问题中添加了 ufw status verbose 的输出。谢谢。 - ESala
11个回答

问题出在容器上使用了-p标志。
原来Docker直接对你的iptables进行更改,这些更改在ufw status中不会显示出来。
可能的解决方案有:
  1. 停止使用-p标志。改用Docker链接或docker networks

  2. 将容器绑定到本地,以便它们不会暴露在机器外部:

    docker run -p 127.0.0.1:8080:8080 ...

  3. 如果坚持使用-p标志,请告诉Docker不要触碰你的iptables,在/etc/docker/daemon.json中禁用它们并重新启动:

    { "iptables" : false }

我建议选择选项1或2。请注意,选项3 会产生副作用,比如容器无法连接到互联网。

15这真是太有用了 - 我之前因为无法访问我在ufw中未允许的端口而感到疯狂。虽然使用iptables=false会带来很多问题,比如容器在本地主机上变得可访问,例如用于反向代理。最好确保容器监听正确,不要使用-p 80:80,而是使用-p 127.0.0.1:80:80或不同的端口,比如127.0.0.1:3000:80,以便与nginx或apache进行反向代理而不发生端口冲突。 - Andreas Reiff
2@AndreasReiff 你说得对,但最近我发现尽量避免使用“-p”是最好的。例如,在反向代理的情况下,我不为工作容器映射任何端口,然后将nginx放在自己的容器中,使用“-p 80:80”和“--link”与其他容器连接。这样就不会出现端口冲突,唯一的访问点就是通过nginx。 - ESala
2还要检查这篇文章,以了解您必须采取的第三个预防措施!https://svenv.nl/unixandlinux/dockerufw - Henk
2重要提示:/etc/default/docker 是由 upstart 配置使用的配置文件。 如果您已从 upstart 切换到 systemd,将不会使用此文件。 - orshachar
@ESala你应该考虑将这个答案取消标记为正确,因为它已经过时了。 - ctbrown
为什么使用127.0.0.1可以工作,而使用localhost0.0.0.0却不行? - spencer.sm
1非常感谢!截至2021年,这个答案仍然有效。原本让我困扰的是,我无法在任何地方访问我的数据库,但有了这个答案,我终于解决了这个问题。 - Duco
哇!这可是个严重的安全漏洞啊... - Somebody
我想知道是否有一种方法可以通过SSH隧道访问映射为127.0.0.1:3000:3000的端口。也就是说,我有一个具有公共IP的服务器,我不想将端口3000暴露在互联网上,但仍然希望能够访问它(例如,使用SSH端口转发从我的笔记本电脑访问)。 - Volodymyr Sorokin
这是一个系统范围的变化,很可能会破坏反向代理等功能,其中Docker应该可以访问并修改iptables。如果可能的话,更好的解决方案应该只针对特定的端口进行处理。 - Peter Kionga-Kamau
@ESala 你使用NPM吗?如果是的话,你是否将其绑定到127.0.0.1并通过实际的Nginx服务器进行代理?我想要对某些国家的IP进行地理屏蔽,但由于NPM是一个暴露在0.0.0.0:80端口的容器,所以在UFW中阻止IP并不能真正阻止该IP访问80端口,它只会阻止其他端口,而我的目标是阻止所有连接。 - Pawel Cioch

16.04带来了新的挑战。我按照在ufw防火墙后运行Docker中所示的步骤进行了操作,但是在16.04上无法使docker和UFW正常工作。换句话说,无论我做了什么,所有的docker端口都对外公开。直到我找到了这个:如何设置Docker 1.12+以不干扰IPTABLES/FirewallD 我不得不创建文件/etc/docker/daemon.json并将以下内容放入其中:
{
    "iptables": false
}

我接着执行了sudo service docker stop然后sudo service docker start最终docker只是遵循UFW的适当规则。
附加数据:Docker胜过UFW!

2使用Ubuntu 16.04和Docker版本17.09,该解决方案会破坏容器的出站连接。请参阅回答https://askubuntu.com/a/833363/262702和https://askubuntu.com/a/954041/262702。 - ctbrown

如果您正在使用systemd的init系统(Ubuntu 15.10及更高版本),请编辑/etc/docker/daemon.json文件(如果不存在,可能需要创建它),确保已配置iptables键:
{   "iptables" : false }

编辑:这可能导致您在容器内失去对互联网的连接。
如果您启用了UFW,请验证您是否可以从容器内访问互联网。如果不能 - 您必须将DEFAULT_FORWARD_POLICY定义为ACCEPT,并在/etc/default/ufw上应用此技巧描述的方法:https://stackoverflow.com/a/17498195/507564

在我的情况下,我最终修改了iptables,只允许特定IP访问Docker。
根据ESala的回答
引用: 如果您在容器上使用-p标志,Docker会直接对iptables进行更改,而不考虑ufw。
Docker添加到iptables的记录示例:
路由到'DOCKER'链:
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER

将来自'DOCKER'链的数据包转发到容器中:
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 6379 -j DNAT --to-destination 172.17.0.3:6379

您可以修改iptables,仅允许来自指定源IP(例如1.1.1.1)的访问到DOCKER链:
-A PREROUTING -s 1.1.1.1 -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT -s 1.1.1.1 ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER

您可能希望使用 iptables-save > /tmp/iptables.confiptables-restore < /tmp/iptables.conf 来转储、编辑和恢复 iptables 规则。

更新于10.2021

当我们只使用一个端口号时,Docker会使用一个临时端口。请参考docker-compose ports

仅指定容器端口(主机端口将选择一个临时主机端口)。

我原本以为我的回答有效,因为我无法连接到预期的端口1234。
所以,请不要这样做 - 请遵循其他回答中的建议。
我不会删除这个回答,因为它对某些人来说可能仍然有用,可以理解为什么不要这样做。

原始回答

我使用了docker-compose启动了几个容器,也遇到了一个端口被暴露给外部而忽略了ufw规则的问题。

解决方法是在我的docker-compose.yml文件中进行以下更改:

ports:
- "1234:1234"

转换为:

ports:
- "1234"

现在其他的Docker容器仍然可以使用该端口,但我无法从外部访问它。

1根据文档,这似乎不正确,因为"为主机端口选择了一个临时主机端口"。我认为最好使用expose代替。 - YTZ

一个快速的解决方法是在运行Docker并进行端口映射时。你总是可以这样做。
docker run ...-p 127.0.0.1:<ext pot>:<internal port> ...

为了防止你的Docker被外部访问。

使用具有内容的/etc/docker/daemon.json

{
  "iptables": false
}

可能听起来像是一个解决方案,但它只在下次重启之前有效。之后,您可能会注意到您的容器都无法访问互联网,因此无法ping任何网站,这可能是不希望发生的行为。
绑定容器到特定IP也是同样的情况,您可能不想这样做。最终的选择是创建一个容器,并使其在UFW后面无论发生什么情况,无论您如何创建此容器,都可以实现这一目标,因此有一个解决方案:
在创建了/etc/docker/daemon.json文件后,执行以下操作:
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

如果你要使用docker-compose,那么上面的命令中的IP应该被替换为运行docker-compose up时docker-compose创建的网络的IP。
我在这篇文章中更详细地描述了问题和解决方案。
希望能对你有所帮助!

在启动容器时使用 --network=host,这样 Docker 将会将端口映射到隔离的主机内部网络而不是默认的桥接网络。我没有发现阻止桥接网络的合法方式。或者你可以使用自定义的用户定义网络进行隔离。


这个解决方案对我很有效 - 谢谢! - paleo13

  1. 登录到您的Docker控制台:

    sudo docker exec -i -t docker_image_name /bin/bash

  2. 然后在您的Docker控制台内执行以下命令:

    sudo apt-get update
    sudo apt-get install ufw
    sudo ufw allow 22
    
  3. 添加您的ufw规则并启用ufw:

    sudo ufw enable

    • 您的Docker镜像需要以--cap-add=NET_ADMIN参数启动
启用“NET_ADMIN” Docker选项的方法:
1. 停止容器:
docker stop yourcontainer;
2. 获取容器ID:
docker inspect yourcontainer;
3. 修改hostconfig.json文件(默认Docker路径为/var/lib/docker,您可以根据需要进行更改)。
vim /var/lib/docker/containers/containerid/hostconfig.json

4. 搜索"CapAdd",并将null修改为["NET_ADMIN"];
....,"VolumesFrom":null,"CapAdd":["NET_ADMIN"],"CapDrop":null,.... 5. 在主机上重新启动Docker;
service docker restart; 6. 启动您的容器;
docker start yourcontainer;

下面的解决方案对我来说完美无瑕,而且它也没有其他方法那样存在许多缺点,即{ "iptables" : false }和它禁用容器内部的互联网访问。
这个解决方案的功劳归于 GitHub上的tsuna。它非常简单,包含三个简单的步骤:
在编辑器中打开/etc/ufw/after.rules
sudo nano /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

最后,要使更改生效:
sudo systemctl restart ufw # or `sudo reboot` if this wasn't effective