Moby: Docker 网络绕过防火墙,没有禁用选项

创建于 2016-04-14  ·  114评论  ·  资料来源: moby/moby

docker version

Client:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.5.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 15:54:52 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.5.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 15:54:52 2016
 OS/Arch:      linux/amd64

docker info

Containers: 14
 Running: 5
 Paused: 0
 Stopped: 9
Images: 152
Server Version: 1.10.3
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 204
 Dirperm1 Supported: false
Execution Driver: native-0.2
Logging Driver: json-file
Plugins: 
 Volume: local
 Network: bridge null host
Kernel Version: 3.13.0-58-generic
Operating System: Ubuntu 14.04.4 LTS
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 7.793 GiB
Name: brm-pheonix-dev
ID: Y6Z4:6D53:RFOL:Z3CM:P7ZK:H6HL:RLV5:JT73:LZMC:DTBD:7ILK:2RS5
Username: benjamenmeyer
Registry: https://index.docker.io/v1/

其他环境详细信息(AWS、VirtualBox、物理等):
Rackspace 云服务器,Ubuntu 14.04,但这并不重要

重现问题的步骤:

  1. 使用锁定的防火墙设置系统
  2. 创建一组带有暴露端口的 docker 容器
  3. 检查防火墙; docker 将使用“任何地方”作为源,从而所有容器都向公众公开。

描述您收到的结果:
root@brm-pheonix-dev :~/rse# iptables --list DOCKER
链式 DOCKER(1 个参考)
目标 prot opt 源目标
接受 tcp -- 任何地方 172.17.0.2 tcp dpt:6379

描述您期望的结果:
root@brm-pheonix-dev :~/rse# iptables --list DOCKER
链式 DOCKER(1 个参考)
目标 prot opt 源目标
接受 tcp -- 127.0.0.0/24 172.17.0.2 tcp dpt:6379
接受 tcp -- 172.16.0.0/16 172.17.0.2 tcp dpt:6379

您认为重要的其他信息(例如,问题仅偶尔发生):

默认情况下,docker 正在以一种破坏安全的方式修改防火墙 - 它允许来自所有网络设备的所有流量访问容器上的暴露端口。 考虑一个有 2 个容器的站点:容器 A 暴露 443 运行 Nginx,容器 B 在端口 8000 上运行 API。希望将容器 A 向公众开放以供使用,但将容器 B 完全隐藏,以便它只能与 localhost 通信(用于用户测试)和 docker 网络(用于与容器 A 对话)。 出于测试目的,也可能希望容器 C 成为容器 B 使用的具有相同类型限制的数据库。

我发现这是因为我 _thought_ 未向公众开放的服务上的监控日志。 从试图闯入的源中找到日志条目后,我检查了防火墙规则,发现源地址或接口没有限制。 我使用 UFW 并且只允许 SSH 连接到这个特定的盒子,并且更愿意保持这种方式。 如果人们不小心,这会极大地影响使用 Docker 容器部署服务并导致潜在的安全问题。

最好的安全实践是默认限制网络像上面期望的效果示例一样工作,然后允许用户添加适当的防火墙等规则来覆盖此类行为,或者可以选择恢复到当前行为。 我知道由于遗留原因,这不太可能,因为它会在更新时破坏很多东西; 所以至少有一个选项来启用现在可以打开的上述选项将是一个很好的第一步,也许稍后在多次警告后将其设为默认行为。 假设默认行为是安全的,在 docker-compose yml 中具有管理此功能的功能(防火墙-> 启用公共端口,ip)将是一个很好的方式来明显地让它知道发生了什么。

我确实找到了 --iptables=false 选项,但是,我不想自己设置所有规则。 我唯一反对的是规则的源设置。

虽然我没有验证过,但我怀疑 docker 支持的所有防火墙都会有同样的问题。

arenetworking versio1.10

最有用的评论

Ben 揭露的问题是真实而令人惊讶的(一个糟糕的组合)。 许多管理员,像我一样,正在使用久经考验的 ufw 防火墙。 Docker 正在围绕 ufw 进行并最终运行,并以这样一种方式改变 iptables 规则:1) 导致 ufw 误报包过滤规则的当前状态,以及 2) 将看似私有的服务暴露给公共网络。 为了让 docker 继续受到系统管理员社区的青睐,必须设计另一种方法。 现在有很多管理员,就像 Ben 和我自己一样,无意中打开了更广泛的 Internet 端口。 与本和我不同的是,他们还没有弄清楚。

所有114条评论

注意:我在https://github.com/docker/docker/blob/master/vendor/src/github.com/docker/libnetwork/iptables/iptables.go中注意到,甚至没有设置源的现有选项,所以它只是使用源 ip/设备的 iptables 默认值。

这不完全是#14041,因为这个问题是在谈论暴露的端口。 公开端口旨在使它们可公开访问,因为这是您向外界公开服务的方式。 如果您在开发环境中工作,您可以使用主机防火墙禁止从计算机外部访问端口,或者干脆不公开端口并直接访问服务,或者从同一网络上的其他容器访问。

我建议您使用较新的 docker 网络功能为您根本不想公开的服务设置专用网络,请参阅https://docs.docker.com/engine/userguide/networking/

这就是我首先想到的; 但有点困惑,因为 _exposing_ a port ( EXPOSE ) 实际上并没有做任何事情,但是 _publishing_ a port ( -p / -P ) 实际上将它暴露在主持人。

如果您实际上是在谈论 _publishing_,那么这就是设计的;

在您的示例中,容器 B 和 C 不应发布它们的端口,容器 A 可以通过 Docker 网络与它们通信,例如

docker network create mynet

docker run -d --net=mynet --name=api api-image
docker run -d --net=mynet --name=db database-image
docker run -d --net=mynet --name=web -p 443:443 nginx

这只会将“web”容器发布到宿主机,web容器可以通过它们的名字访问“API”和“数据库”容器(即http://api :80/,以及db:3306(假设为MySQL))

@justincormack所以我认为使用私有网络不能解决问题。 就我而言,我在容器之间使用专用网络,它们仍然公开暴露,因为主机防火墙未配置为限制对专用网络的暴露。

@thaJeztah问题仍然归结为防火墙支持 -

现在我通过 docker-compose 运行它; 然而,这并不完全是一个 docker-compose 问题,因为 libnetwork 功能_no_ 能够在防火墙规则中限制网络 - iptables 规则没有源规范,因此无论人们如何配置网络,只要依赖 docker 创建防火墙规则(应该这样做,因为它更有可能使它们正确)然后这成为一个问题。 在 docker-compose.yml 文件中考虑以下内容:

nginx:
    build: ./docker/nginx/.
    ports:
        - "127.0.0.1:8080:80"
        - "127.0.0.1:443:443"
    environment:
        DESTINATION_HOST: repose
    links:
        - repose
repose:
    build: ./docker/repose/.
    ports:
        - "127.0.0.1:80:8080"
    environment:
        DESTINATION_HOST: phoenix
        DESTINATION_PORT: 8888
    links:
        - phoenix
curryproxy:
    build: ./docker/curryproxy/.
    ports:
        - "127.0.0.1:8081:8081"
    external_links:
        - rse_rse_1
        - rse_rse_2
        - rse_rse_3
phoenix:
    build: .
    ports:
        - '127.0.0.1:88:8888'
    links:
        - curryproxy:curry
    external_links:
        - rse_rse_1:rse
        - rse_rse_2
        - rse_rse_3
        - rse_cache_1:cache
    volumes:
        - .:/home/phoenix

以上是我的一个项目的摘录。 虽然我希望能够从我的主机本地测试所有这些,但我不希望其他任何人能够访问除 nginx 实例之外的任何内容。

我不确定这如何转化为您的命名法……这可能是“发布”方面的一部分,需要扩展发布功能才能做到我所说的。

如果这是设计使然,那么它是一个糟糕的安全模型,因为您现在在不熟悉的网络(例如旅行)中将所有开发人员暴露在极端风险中。

正如我所说,我不希望默认值会立即更改,但拥有该选项将是一个很好的第一步。

那么我有点困惑,你能举一些外部例子来说明你可以连接到什么吗? 后端服务将(默认情况下)在172.17.0.0/16网络上,您将无法从外部访问我首先不会想到的,因为您没有从外部主机定义的路由。

如果您的外部 IP 也是私有 IP,则存在一个潜在问题,即不会丢弃为内部网络路由的流量(而它应该从公共网络到私有网络) - 这是问题吗?

@justincormack所以我主要是设置适当的代理,以便某些服务只能通过代理(nginx - ssl 终止)访问,然后通过身份验证代理进行过滤(repose),最后转到另一个服务(凤凰)。 如果所有这些都绑定到 0.0.0.0 接口,我就不在乎了; 但我只希望 nginx 可以从外部访问(或者如果我这里没有 nginx 的话,至少可以访问休息部分)。 例如,一个简单的解决方案是不必在配置中设置“127.0.0.1”,而是有一个防火墙部分,可以很容易地指定允许通过防火墙的基本配​​置只有 docker 网络和本地主机(环回)接口启用通话 - 类似于:

firewall:
    external:
        ports:
            - 80
            - 443

现在可以通过将 _host_ 上的网络映射限制为 127.0.0.1 而不是默认的 0.0.0.0 映射来稍微缓解这种情况。 请注意,这才是真正缓解它的原因,否则桥接会将主机端口转发到 docker 网络。

是的,我确实验证了这种限制是否有效; 但是,它仍然存在潜在的漏洞,并且防火墙规则与实际执行的操作不符。

再举一个例子,不久前有一个 Linux 内核漏洞(目前无法找到它),它与在 IPtables 中标记为可供应用程序使用的端口有关,但实际上并未连接到应用程序- 例如,位于本地主机端口而不是公共 IP 端口。 这可能会设置它,并且更好的做法是将 IPtables 规则限制为预期的网络,而不是让它们开放以从任何地方连接。 正如我所说,至少可以选择指定。 他们可能已经解决了那个特定的问题,但为什么要保留这种可能性呢?

IOW,一切都与安全有关。

@BenjamenMeyer如果您不想访问其他服务,为什么要发布它们的端口? 即"127.0.0.1:8081:8081"如果仅通过 docker 网络访问则不需要(其他服务直接通过 docker 网络连接)

我遇到的一个与此相关的问题是我想发布端口,但只允许某些 IP 地址访问它们。

例如,我在几个容器中运行 Jenkins 环境。 主节点已“发布”,但我必须制定一些非常复杂的 iptables 规则来锁定它,以便只有我们拥有的 2 个办公室可以访问它。

有没有办法解决当前内置在 Docker 中的问题? 或者至少是推荐的做法? 我在文档中看到了如何限制对 1 个 IP 地址的访问; 但不是几个。 另一个问题是,如果您的服务器已经有 iptables 配置,您可能会在应用规则之前重置所有规则(因此我必须设置复杂的规则)。

我有一个类似于@SeerUK 所述的问题。 当预先存在的防火墙规则不适用于已发布的容器端口时,会出现严重的违反预期的情况。 所需的行为如下(至少对我而言)

  1. 根据 INPUT 配置等过滤用户连接尝试
  2. 然后根据 docker 添加的 FORWARD 规则照常进行流量转发

在 iptables 中是否有一种简洁的方法来实现这一点,或者它是否不容易允许这样的构造。 我对 iptables 的了解特别有限,所以请耐心等待。 我最近刚刚在尝试了解 docker 与它的交互时获得了有关它的知识。

我目前实际使用的是什么,因为我实际上是在一个非常强大的专用服务器上运行这些容器,我已经设置了一个运行 Docker 的 KVM 虚拟机,然后使用一些更标准的 iptables 规则来限制来自主机的访问. VM 有自己的网络接口,只能从服务器访问,因此我必须添加规则以明确允许访问主机上 iptables 中的端口。 我失去了一点性能,但并不多。

@thaJeztah我希望能够从本地系统访问它,并轻松对其进行测试。 例如,设置一个具有 Health 端点的 RESTful HTTP API 并能够通过使用 localhost 可靠地运行curl (我必须为其他人记录这一点,并且 IP 地址发生变化是不可靠的) )。 在我的开发环境的大多数情况下,我只希望容器相互通信,但我也希望能够从主机访问它。

对于@SeerUK的情况,能够设置 IP 块(5.5.0.0/16 - iptables 规则中源地址的有效参数)将是一件非常好的事情。 IPtables 已经有能力做限制,但是 docker 没有利用它。

@thaJeztah我明确设置了"127.0.0.1:8081:8081"以使其远离外部网络; 我在我的 docker 容器中发现了试图通过暴露的端口破解容器的人的日志。

我现在的工作是在我离开当天之前关闭 docker 容器,因为我无法确保我想要外部的环境实际上是 _is_ external,或者出于安全目的对环境进行了适当的限制。

@BenjamenMeyer一种方法是在容器中运行这些测试,例如

docker run --net -it --rm --net=mynetwork healthchecker 

Ben 揭露的问题是真实而令人惊讶的(一个糟糕的组合)。 许多管理员,像我一样,正在使用久经考验的 ufw 防火墙。 Docker 正在围绕 ufw 进行并最终运行,并以这样一种方式改变 iptables 规则:1) 导致 ufw 误报包过滤规则的当前状态,以及 2) 将看似私有的服务暴露给公共网络。 为了让 docker 继续受到系统管理员社区的青睐,必须设计另一种方法。 现在有很多管理员,就像 Ben 和我自己一样,无意中打开了更广泛的 Internet 端口。 与本和我不同的是,他们还没有弄清楚。

@thaJeztah假设我是通过命令行执行此操作,而不是使用我只需要设置 IP 地址的其他工具。

例如,我正在开发一个 API。 我有一个工具可以用来在生产中使用该 API 来支持它; 对于工具和 API 的开发,我只想将该工具指向 dockerized API。 该工具对 docker 一无所知,也不应该。 而且我不一定想将该工具放入 docker 中只是为了使用它 - 将它指向仅对本地主机公开的端口就足够了。

@jcheroske我同意,但我不知道 _that_ 方面有一个很好的解决方案。 为此, ufw可能需要变得更智能,以便能够查找和报告它未参与创建的规则。 有很多软件可以以ufw (或 AFAIK firewalld 等)不知道的方式调整iptables规则。 也没有真正简单的解决方案来解决这个问题。

也就是说,如果 Docker 可以与这些工具集成以转储适当的配置文件以便能够启用/禁用它们,或者与这些工具集成以便它被挂钩并适当地转储信息,那就太好了,但是,给定有更好的解决方案,我认为_那个_方面不会真正得到解决。 在这里,更多的是限制正在生成的iptables规则的范围,以通过允许源规范(lo、eth0、127.0.0.0/24 等)至少将潜在影响降至最低。

如果您愿意这样做,使用 iptables 确实使这完全成为可能。

这是一个关于如何使用它的精简示例: https :

您可以在底部看到,1.2.3.4 明确授予访问端口 8000(由 Docker 公开)的访问权限,然后删除该端口的任何其他内容。 PRE_DOCKER 链被插入到 DOCKER 链之前,以便它首先被命中,这意味着 DROP 会阻止被阻止的请求到达 DOCKER 链。

Docker 没有内置此功能,这有点令人讨厌,但现在可以解决它。

另一种选择是使用外部防火墙。 像 AWS 和 Scaleway 这样的地方提供安全组之类的东西,您可以在其中管理从外部对您的设备的访问,从那里每个端口的行为方式都相同。

我从来没有真正想过如何用 UFW 来完成这项工作。 虽然现在,我很高兴使用 iptables 作为解决方案。 到目前为止,它似乎对我来说效果很好。

显然,如果您已经围绕 UFW 构建了一组相当复杂的防火墙规则,这并不是一个很好的解决方案。 尽管如此,它确实使使用诸如 iptables-persistent 之类的东西变得非常容易。 您还可以使用其他方式来允许访问这种在 iptables 中看起来更“正常”的方式。

@BenjamenMeyer你有没有想过使用用户定义的docker network和一个子网和 ip-range 选项,并为容器分配一个静态 ip 地址并将它们用于本地开发,这样你就不必依赖虚拟静态 ip,例如 127.0.0.1 ? 这将避免需要为主机私有的那些容器一起进行端口映射。

docker network create --subnet=30.1.0.0/16 --ip-range=30.1.0.0/24 mynetwork
docker run --net=mynetwork --ip=30.1.1.1 --name=myservice1 xxxx
docker run --net=mynetwork --ip=30.1.1.2 --name=myservice2 yyyy

通过这种设置,myservice2 可以通过名称myservice1访问 myservice1,甚至不需要依赖静态 ip。 主机也可以自由访问静态IP,无需端口映射。

同样在 compose 1.7 中,您可以为容器指定静态 IP 地址并指定网络子网和范围。

我确实想出了一个简单的解决方法。

1) 编辑 /etc/default/docker: DOCKER_OPTS="--iptables=false"

2)添加ufw规则: ufw allow to <private_ip> port <port>

如此简单,真让我想知道为什么--iptables=false选项不是默认选项。 当所有 docker 必须做的就是说:“嘿,如果你正在运行防火墙,你将不得不在它上面打一个洞!”为什么要创造这种情况! 我错过了什么?

https://fralef.me/docker-and-iptables.html
http://blog.viktorpetersson.com/post/101707677489/the-dangers-of-ufw-docker

我无法让 docker 停止修改 iptables 来挽救我的生命。 尝试在 Ubuntu 16.04 上更新 /etc/default/docker 无济于事

@enzeart试试/lib/systemd/system/docker.service

@SeerUK祝福你的灵魂

@enzeart要配置在使用 systemd 的主机上运行的守护程序,最好不要编辑 docker.unit 文件本身,而是使用“插入”文件。 这样,您在升级 docker 时就不会遇到问题(以防有更新的 docker.unit 文件)。 有关更多信息,请参阅https://docs.docker.com/engine/admin/systemd/#custom -docker-daemon-options。

您也可以使用 daemon.json 配置文件,请参阅https://docs.docker.com/engine/reference/commandline/daemon/#daemon -configuration-file

@mavenugo已经有一个

@jcheroske有效,但正如我所指出的,这意味着 _end-user_(我)必须确保所有iptables规则都是正确的,这不是最佳的,并且几乎不可能发生让docker自动执行,因此这个问题。

你好,请上。 我也觉得是它的问题。 Iptables 中的容器链需要遵循主要规则,默认情况下不对外公开。

我真的很希望 Docker(和 docker-compose)能够将可以访问该端口的 IP 列入白名单或黑名单。

举个例子:

nginx:
    ports:
      - "8000:8000"
    whitelist:
      - 10.6.20.2

这意味着只有 10.6.20.2 的源 IP 才能访问此主机上的端口 8000。

@StefanPanait我真的很喜欢这个主意。 它还可以使用与卷和访问/拒绝列表类似的语法,例如:

nginx:
  access:
  - "10.0.1.6:allow"
  - "deny"

当然,它仍然必须允许诸如容器间通信之类的事情。

@SeerUK容器间通信应该是默认设置,但是为什么您不能禁止一个容器与之通信呢? 这对于调试非常有用......

虽然我想这样做的正确方法是将 docker 网络分开……不过,我认为能够做如下事情:

nginx: access: - "10.0.1.6:allow" - "webapi:allow" - "database:deny" - "deny"
可能有用......问题是,它是否足够有用以证明实施到那个程度? 我不知道。

目前,我希望看到原始问题得到解决,然后可以添加这样的功能,如果它们在设计开始时没有意义(它们可能)。

为什么你不能禁止一个容器与它交谈? 这对调试非常有用

那是docker network disconnect是为了什么? 您可以断开容器与网络的连接以进行调试,然后使用docker network attach重新连接它

对于那些刚刚发现在其 Internet 公开服务器上打开了大量端口的人,在使用 UFW 后,我不断挖掘并发现以下内容:

带有 UFW 和 Docker 的 Ubuntu 16.04 提出了新的挑战。 我完成了如下所示的所有步骤: https : 我无法让 docker plus UFW 在 16.04 上工作。 换句话说,无论我做什么,所有 docker 端口都在全球范围内暴露在互联网上。 直到我发现这个: http :
我必须创建文件:/etc/docker/daemon.json 并将以下内容放入:

{
“iptables”:假
}

然后我发出sudo service docker stop然后sudo service docker start最终 docker 只是遵循 UFW 中的适当规则。

补充资料: https :

@thaJeztah

为什么你不能禁止一个容器与它交谈? 这对调试非常有用
那是 docker network disconnect 是为了什么? 您可以断开容器与网络的连接以进行调试,然后使用 docker network attach 重新附加它

如果仍然需要网络连接怎么办? 示例:从容器 B 中的服务器测试容器 A 的健康检查失败,同时仍然具有容器 C、D 和 E 提供的服务。比关闭整个网络更容易禁止容器 B 访问容器 - 容器 C 也可能依赖于对容器 B 的访问以使其健康检查通过。

尽管如此,这并不能承受“让我们解决最初的问题”。

@gts24有趣的发现。

恕我直言,整个问题在于 Docker 就像所有其他程序一样,根本不应该触及防火墙(iptables 或其他)。 当我安装(例如)apache 并告诉它监听0.0.0.0:80决定是否在防火墙上打开端口 80,可以在其中指定希望的任何规则。

与其在 docker(和/或 compose)配置文件上重新发明防火墙规则,不如弃用整个PUBLISH功能,并创建一个新的LISTEN功能,使其像所有其他程序一样工作。 充其量,docker 可以在使用它的系统上为每个端口/容器创建默认禁用的firewalld 服务。

@gcscaglia在守护进程上设置--iptables=false ,你应该得到它

@thaJeztah该功能没用,因为 Docker 没有提供替代方案来从守护程序获取必要的防火墙规则(或更具体的带有相关参数的触发脚本挂钩)。 如果您有静态容器并且从不更改它们的任何内容(例如已发布的端口),则它几乎不可用,但对于所有其他情况,您可以忘记它。

@thaJeztah正是 taladar 所说的。

--iptables=false应该重命名为--networking=false因为即使是内部容器到容器网络也不能在禁用它的情况下工作。 让容器侦听某些端口/接口组合而不在入站防火墙规则上打孔的选项(即LISTEN )将解决所有这些问题,向后兼容并允许使用--iptables=true .

有了这样的监听模式,那些希望它“正常工作”的人可以继续使用PUBLISH ,而那些想要控制的人可以使用LISTEN

@gcscaglia好的,所以如果您希望--iptables启用,但_不要_使用-p / --publish 。 您应该能够手动设置 IPTables 规则以将端口转发到容器。 容器已经侦听了它自己的私有 IP 地址,以及容器中的服务侦听的端口。

@thaJeztah不,你不能。 因为你不知道甚至有一个容器刚刚启动并且需要防火墙规则。 您也无法告诉使用 API 的人在它正在侦听的主机端口上启动它。

简单的例子。 我们使用 Docker 容器来运行 Jenkins 作业。 他们公开了他们的 SSH 端口,以便 Docker 可以联系他们。 一旦工作完成,它们就会被销毁。 Docker 没有提供任何方法让它与 --iptables=false 一起使用,因为您无法告诉 Jenkins(使用 Docker API 启动容器)主机端口,甚至无法触发脚本进行设置必要的防火墙规则。

您的想法仅适用于手动启动永久、永不更改的容器的极其简单的用例。 除非容器具有静态 IP,否则即使容器中的简单 --restart=always 也会在此设置中中断。

@taladar我正在回复@gcscaglia的用例,他要求提供一个功能,其中--iptables=false ,但要保持启用状态,但不要使用-p / --publish功能(它告诉 docker那些可通过 IPTables 访问的端口)。

@thaJeztah虽然你的用例是正确的,但 AFAIK 提议的解决方案对我不起作用,原因与它不适用于 taladar 情况相同:任何东西的单次重启,突然我的手动规则需要更新。 没有任何钩子或触发器可以用来通知重启,因此我可以自动执行此类更新(更不用说我确实在重新发明轮子)。

目前,我的情况的唯一解决方案是在 docker 主机和所有外部网络之间放置另一个防火墙(docker 无法触及)。 但是如果 Docker 只是简单地做它已经做的一切,除了向世界开放端口,我可以完全取消第二个防火墙。

我想你不能拥有一切,但这肯定令人沮丧。 你认为就我的LISTEN想法提出功能请求值得吗,或者 Docker 团队不会对这样的功能感兴趣?

LISTEN做什么? 有EXPOSE ,它允许您注释容器侦听的端口。 对于触发器,您可以收听 docker events

并不是说这里没有改进的余地(我知道正在调查中),只是想知道您的期望

目前,正如您所说,容器中运行的所有服务都绑定到容器私有 IP(如果您不使用--net=host或类似的)。 这是好的和可取的,因为主机和容器之间的这种隔离正是 Docker 的卖点。

但是,目前,如果我希望在任何容器外(无论是在主机上还是在网络上的其他地方)运行的应用程序能够访问在容器内运行的服务,我需要使该服务侦听主机的网络接口之一。 为了在不向容器暴露任何主机接口的情况下解决这个问题,Docker 创建了-p / --publish特性,它:

  1. 创建 iptables 规则以将选定主机接口上的选定端口转发到容器私有 IP 上的选定端口(这是我们都期望的,因为这是我们要求的)
  2. 告诉 iptables 允许世界上任何地方的任何人访问所选主机接口上的该端口(这对于转发工作是不必要的,因此让我们中的许多人感到惊讶)

我提议的是一个功能(命名为LISTEN或其他名称),它只执行“1”并让用户自行决定“2”,就像所有其他服务/程序通常所做的那样。

至于EXPOSE ,AFAIK 只是 Docker 映像中的元数据,因此守护程序知道当用户指定-P (发布所有内容)时要做什么。 也许我错了,“暴露的”端口可以以抗重启的方式转发(没有全球访问权限)?

- 题外话 -

恕我直言,这个问题的 OP 正在询问为什么-p执行“2”以及如何阻止 Docker 这样做。 虽然守护程序级别的设置(除了禁用网络)可以解决它,但为了保持向后兼容,最好的方法是添加新功能(即LISTEN或其他名称)。

虽然许多用户喜欢这种“开箱即用”的效果,但没有系统管理员希望除 iptables/firewalld 之外的程序在防火墙上打开端口,更不用说他们的防火墙管理软件不报告的方式。

我认为存在三个主要问题:

  1. Docker 发布/公开端口的方式绕过了常见的防火墙规则,如 UFW。
  2. 上述事实似乎没有得到很好的记录(这是完全出乎意料的,因为我知道没有其他 Linux 服务本身绕过防火墙规则)。
  3. 没有简单的方法可以让 iptables 适应 Docker 发布/公开端口的方式,或者人们知道的方式不容易自动化,也没有记录。

也许在 FILTER 表上实现端口公开在技术上是不可行的,因此修复 1 是不可能的。 至少这应该在某处得到一个很大的警告(修复 2),但理想情况下 3 可以用新选项解决,就像这里的人建议的那样,例如allowdeny这将添加额外的防火墙规则自动允许或拒绝公开/发布端口的特定 IP。

例如allow: "tcp:{your_trusted_ip}"用于名为“elasticsearch”的容器发布端口 9200 可能会执行以下操作:

iptables -t mangle -N DOCKER-elasticsearch
iptables -t mangle -A DOCKER-elasticsearch -s {your_trusted_ip} -j RETURN
iptables -t mangle -A DOCKER-elasticsearch -j DROP
iptables -t mangle -I PREROUTING -p tcp --dport 9200 -j DOCKER-elasticsearch

我从 Docker 文档中发现这非常有用: https: //docs.docker.com/engine/userguide/networking/default_network/container-communication/#communicating -to-the-outside-world

Docker 的转发规则默认允许所有外部源 IP。 要仅允许特定 IP 或网络访问容器,请在 DOCKER 过滤器链的顶部插入否定规则。 例如,为了限制外部访问,只有源 IP 8.8.8.8 可以访问容器,可以添加以下规则:

$ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j 删除

@jmimico是的,我以前遇到过。 您将如何限制来自 2 个或更多 IP 的访问?

Docket 只是添加一个选项来在任何地方运行 shell 脚本,它现在创建 iptables 规则,并将所有 Docker 内部信息作为参数传递给脚本,这有什么困难? 这将允许每个人准确地创建他们需要的规则。 添加一些方法来触发人们在执行 iptables-restore 或由于其他原因刷新链后可以调用的活动容器的脚本的重新执行,您就完成了。

Docker 不需要像这里的一些人所建议的那样重新创建各种预先构建的防火墙场景,这可以通过分发钩脚本集构建在这样的系统之上。 最多只在本地主机上公开并在全球公开(就像 Docker 现在所做的那样)可能是有意义的。 IPTables 对 Docker 来说太灵活了,无法直接在设置中对所有场景进行建模。

这张票似乎永远存在,当前的行为使 Docker 边界无法使用(似乎是在这个项目中实现特性的标准方法,例如缺乏适当的内置 GC 或几乎没有一个可用的、无内核错误的、高性能的存储后端,...),并且有一个简单的解决方案可以让人们实施适合他们环境的自己的解决方案。

@StefanPanait好问题。 我敢打赌,您需要利用对象组的使用。 使用列入白名单的 IP 填充对象组,然后在 DOCKER 链的第一行中使用该对象组。

例子:
iptables -N docker-allow
iptables -A docker-allow -s 1.1.1.1 -j 接受
iptables -A docker-allow -s 2.2.2.2 -j 接受
iptables -A docker-allow -s 3.3.3.3 -j 接受
iptables -A docker-allow -j DROP

iptables -I DOCKER -i ext_if -j docker-allow

添加到 DOCKER 链的规则是否曾经被守护进程重启之类的东西破坏过? 我认为手动解决方案的问题在于它们很难做,因此有一个强有力的案例可以更直接地更好地支持常见情况(阻止/允许单个 IP)。

也许插件是支持这一点的合适地方? 例如,也许“ufw”插件可以以与 ufw 兼容的方式添加规则,以便用户可以使用他们的常规防火墙工具链有效地管理防火墙,而 docker 服务的行为将更像普通的主机服务。

添加到 DOCKER 链的规则是否曾经被守护进程重启之类的东西破坏过? 我认为手动解决方案的问题在于它们很难做对,因此有一个强有力的案例可以更直接地更好地支持常见情况(阻止/允许单个 IP)。

将删除规则添加到 DOCKER-USER 链似乎可以更好地使其在 docker 重新启动时保持不变。

Docker v.17.06 有一个名为 DOCKER-USER 的新 iptables 链。 这是您的自定义规则,请参阅我对 serverfault 的回答: https :

当我评论 SF 时,我想念为什么这个 DOCKER-USER 链与任何其他用户添加的链有任何不同。您仍然需要自己指定接口名称,而且非 iptables 专家仍然容易犯严重错误。

另一方面,它仍然伴随着“Docker 是唯一的 iptables 用户”的心态,这对于那些想将 iptables 用于不仅仅是 Docker 的人来说很糟糕。 因此,除了可能将整个主机专用于 Docker 的人之外,这对所有潜在用户都是不利的。

好的,所以使用 DOCKER-USER 解决了插入顺序的问题,确保它总是在其他与 Docker 相关的规则之前出现在链中。 但是,它并没有使通过端口号将到容器的流量列入白名单变得容易得多,因为此时--dport是 docker 容器服务的端口,而不是暴露的端口。 例子:

发布端口 9900 以公开在 9000 上内部侦听的 docker 服务。

$ sudo iptables -A DOCKER-USER -m limit --limit 20/min -j LOG --log-prefix "IPTables: "
$ docker run --rm -it -p '192.168.56.101:9900:9000' alpine nc -l 9000

从网络上的另一台机器:

$ telnet 192.168.56.101 9900

记录的内容:

IPTables: IN=enp0s8 OUT=docker0 MAC=08:00:27:b6:8d:d6:0a:00:27:00:00:04:08:00 SRC=192.168.56.1 DST=172.17.0.2 LEN=52 TOS=0x00 PREC=0x00 TTL=127 ID=14127 DF PROTO=TCP SPT=51208 DPT=9000 WINDOW=64240 RES=0x00 SYN URGP=0
IPTables: IN=docker0 OUT=enp0s8 PHYSIN=veth05ba007 MAC=02:42:0f:f9:76:4c:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.56.1 LEN=40 TOS=0x00 PREC=0x00 TTL=63 ID=23041 DF PROTO=TCP SPT=9000 DPT=51208 WINDOW=0 RES=0x00 ACK RST URGP=0

因此,如您所见,此时没有机会过滤到端口 9900 的流量。当然,我可以过滤到 9000 的流量,但这是一个问题,因为内部端口可能会无意中在多个容器甚至主机上运行的服务之间重叠。 这是 Docker 的一大卖点,您可以在一台主机上运行多个服务,而不必担心端口冲突。 因此,许多容器被设计为只监听一个端口,用户可以使用--publish选项来更改哪个端口暴露在哪个接口上:

$ docker run -d -p 7777:6379 --name data1 redis
$ docker run -d -p 8888:6379 --name data2 redis

但是,我不能使用 DOCKER-USER(如果我错了,请纠正我)来影响到 data1 的流量而不影响到 data2 的流量,除非我使用某种内省来发现目标容器 IP 是暂时的,这让你回到寻找一种简单、可靠的方法来防火墙发布的服务,无需编写脚本和内省。

需要明确的是,这不起作用:

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 7777 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 8888 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 7777 -j DROP
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 8888 -j DROP

这确实有效,但结果是这两个服务都暴露给了两个列入白名单的 CIDR:

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 6379 -j DROP

所以看起来 DOCKER-USER 仅用于将所有端口暴露给特定 IP 而不会将特定端口暴露给特定 IP 除非您不介意编写 iptables 规则来引用内部端口号并且没有多个容器使用相同的内部端口号。 每个人似乎都错过了这些要点,并使用 DOCKER-USER 作为解决方案运行,我认为这值得一个新的、更好的解决方案。

@SeerUK
https://gist.github.com/SeerUK/b583cc6f048270e0ddc0105e4b36e480 中发布的解决方案对我不起作用。 能否请你帮忙?

docker 错误是使用 iptables(系统防火墙)进行应用程序路由。 它将永远制造问题和混乱。 改iptables是很危险的。 添加这个嵌入在 docker 中的函数并不是那么昂贵。 此外,如果您以并发方式执行 docker,它也可能具有并发不一致的状态。

如果在 docker 运行时更改 iptables,它会发现 iptables 有奇怪的行为。 如果您停止 docker set iptables 并重新启动 docker,一切都将按预期工作。 我担心的是……如果我必须在生产中更改 iptables?

如果在 docker 运行时更改 iptables,它会发现 iptables 有奇怪的行为。

iptables 就是 iptables,它不在乎 docker 是否正在运行。

如果你之前做很多测试会更好......告诉iptables是iptables只是一个重言式。 这是我的建议:)

我认为他的观点是当 Docker 像您的评论所建议的那样运行时,iptables 的行为并没有什么不同。 这显然是 Docker 使用 iptables 的方式的问题,就好像没有其他人在同一系统上需要它一样。

我不明白为什么DOCKER-USER不能解决这个问题。
您可以使用 iptables 随意过滤:源端口、目标端口、源地址、非本地流量等。

DOCKER-USER的全部意义在于,它在运行任何 docker 规则之前运行用户想要运行的任何规则,这允许您在流量到达 docker 之前对流量做任何你想做的事情。

@cpuguy83核心问题是

DOCKER-USER 无法解决这个问题,因为它也没有将 docker 网络绑定到特定的网络接口或特定的 IP (fe 127.0.0.1)。

根据我的原始要求:
1.Docker 应该将自己绑定到一个特定的 IP——基于 Docker 网络配置——然后允许用户添加规则以在系统外公开容器(使用他们选择的工具); 默认情况下,容器不应在系统外公开。

  1. 由于至少有些人依赖于历史行为,因此没有过渡期就无法做到这一点。

恕我直言,整个问题在于 Docker 就像所有其他程序一样,根本不应该触及防火墙(iptables 或其他)。 当我安装(例如)apache 并告诉它在 0.0.0.0:80 上侦听时,我决定是否在防火墙上打开端口 80,在那里我可以为它指定我希望的任何规则。
与其在 docker(和/或 compose)配置文件上重新设计防火墙规则,不如弃用整个 PUBLISH 功能,并创建一个新的 LISTEN 功能,以便像所有其他程序一样工作。 充其量,docker 可以在使用它的系统上为每个端口/容器创建默认禁用的 firewalld 服务。

得好@gcscaglia ! 更令人困惑的是,在https://docs.docker.com/engine/reference/run/#expose -incoming-ports 中很少或根本没有提到所有这些,我认为这是大多数人关心的地方一些补充示例学习的文档最终出现在。 某处应该有一个亮红色的框来解释 Docker 覆盖预先存在的 iptables 规则的风险。

如果不想让docker管理iptables,那就设置--iptables-=false
如果您只希望 docker 在特定接口上打开端口,那么您也可以在守护程序配置中进行设置。

@BenjamenMeyer
您可以阻止DOCKER-USER所有流量,只让您想要的流量通过。

您可以阻止 DOCKER-USER 中的所有流量,只让您想要的流量通过。

@cpuguy83如果我在这里说的有什么不正确,请告诉我:

$ docker run -d -p 7777:6379 --name data1 redis
$ docker run -d -p 8888:6379 --name data2 redis

但是,我不能使用 DOCKER-USER(如果我错了,请纠正我)来影响到 data1 的流量而不影响到 data2 的流量,除非我使用某种内省来发现目标容器 IP 是暂时的,这让你回到寻找一种简单、可靠的方法来防火墙发布的服务,无需编写脚本和内省。

需要明确的是,这不起作用:

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 7777 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 8888 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 7777 -j DROP
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 8888 -j DROP

这确实有效,但结果是这两个服务都暴露给了两个列入白名单的 CIDR:

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 6379 -j DROP

因此,无法使用DOCKER-USER独立于 data2 容器控制进出 data1 容器的流量。 对我来说,这使得DOCKER-USER不是一个很好的解决方案。

@colinmollenhour

如果您在DOCKER-USER有一个通用的 drop 规则,这与 docker appended vs prepended 它的主要跳转规则有什么不同?

@ cpuguy83我的观点并不反对覆盖本身。 我的意思是让 Docker 覆盖预先存在的 iptables 规则绝对应该是一个选择加入的功能,而不是一个选择退出的功能。

即使作为选择退出 - 它不应该是 - 它应该非常详细地记录,因为它是完全出乎意料的并且相当违反直觉的调试。 特别是考虑到有多少人使用ufw 。 我知道没有其他软件会在没有非常明确的警告的情况下做这样的事情,我个人会远离此类软件。

此外,由于大多数 Docker 用户(至少以我的经验)开始在其他网络基础设施掩盖问题的环境中使用它,强化了机器配置为他们认为的假设,这一事实加剧了这种情况。

我希望 Docker 将转向只收听的方法,如

如果您在 DOCKER-USER 中有一个通用的 drop 规则,这与 docker appended vs prepended 它的主要跳转规则有什么不同?

我不认为我理解你的问题......请忽略我 2017 年 4 月 22 日的评论,因为那只是关于 DOCKER-USER 确实解决的持久性问题。 我指出的问题不在于持久性。

@taladar你到底在拇指什么?

@jacoscaz

首先,我认为所有的维护者都同意现有的行为并不理想。 不幸的是,这种行为已经永远存在了。 为数百万人使用的东西更改默认值并不是我们在这里真正可以做的事情。 这就是我们添加DOCKER-USER的原因之一,这样至少人们可以注入他们需要的任何规则。

然而,我们无法绕过需要使用 iptables(或 ebpf,或其他一些 nating 解决方案)并提供-p提供的功能......另一方面,如果你想防止人们在防火墙中戳洞,禁止他们使用-p

回顾一下,您可以:

  1. 告诉 docker(默认情况下)绑定到特定地址(默认地址当然是0.0.0.0或所有接口)...但是这不会阻止某人在-p手动指定地址-p 1.2.3.4:80:80
  2. 将自定义规则注入DOCKER-USER ,包括拒绝所有
  3. 使用--iptables=false禁用 iptables 管理

您还有什么建议可以不包括破坏现有用户吗?

@ cpuguy83我明白。 我不主张从一个版本到另一个版本发生巨大的变化。 我确实认为在多个版本的过程中进行计划会是一个很好的改变。 我也不认为 Docker 应该完全停止使用 iptables。 转发允许不同,据我所知,Docker 应该能够默认转发,默认情况下不允许任何人访问地方

就现在可以做的事情而言,这方面的额外文档应该是首要的。 哪个是向 docker.com 的维护者提出这个问题的最合适的渠道?

在他们不感兴趣的情况下,保持这种对话的活跃可能是最大限度地提高这种行为广为人知的机会的最佳方式。

我同意为此功能添加额外文档的呼吁。 据我所知,它仅在https://docs.docker.com/network/iptables/ 上提及

我知道有一些变通方法,但我相信这应该被视为对 Docker 功能的长期改变。 我喜欢在端口上将其设置为 LISTEN,并允许用户定义的规则在其上构建的想法。

正如建议的那样,我向 DOCKER-USER 添加了一条 DROP 规则,以防止容器意外暴露在外面(令人震惊的是,确实发生了这种情况)。

但是,我现在有一项我希望公开的服务。 但正如@colinmollenhour所解释的那样,因为 NAT 发生在过滤之前,我只能过滤 docker ip(不固定)和内部端口号(对于多个容器可能相同)。

那么我怎样才能公开这一项服务呢?

@SystemParadox这是 DOCKER-USER 不能真正解决问题的众多原因之一。

@cpuguy83
我确实喜欢提议的 LISTEN 解决方案,并且从不提倡从一个版本到另一个版本进行重大更改; 而是提倡对一系列版本进行更改,并发出适当的通知 b/c 我确实意识到很多人使用 Docker,并且从一个版本到另一个版本的重大更改对所有人都是有害的。

我也同意更新有关-p和 EXPOSE 等的 Docker 文档应该是优先事项,应该立即完成,以至少引起对该问题的认识。 根据我的经验,大多数使用 Docker 的人都不是防火墙专家,因此他们相信 Docker 会做他们期望的事情,这在当前的设计中并不存在。

此外, https: //github.com/moby/moby/issues/22054#issuecomment -425580301 中的概括解决方案也不起作用。 为什么? 我不直接运行 Docker,我运行 Docker Compose——基于 YAML; IP 地址是动态的(由 Docker 控制),我经常在同一个 Docker 网络中部署多个需要相互交互的服务。 因此, -p用法和地址绑定用法(回顾中的选项 1)都不是解决方案。 DOCKER-USER 并没有像其他人指出的那样真正解决任何问题(回顾中的选项 2)并且完全禁用 IP 表(回顾中的选项 3)也没有任何帮助 b/c 现在一切都坏了(IP 是动态的,所以很难编写解决方案;容器间网络被破坏 b/c Docker 依赖 IPTables 在容器之间移动;等等)。

同样,此线程中没有要求在两个版本之间进行重大更改; 但呼吁采取有计划的分阶段方法,使人们能够适当地迁移。

作为解决方法,您可以使用-m conntrack --ctorigdstport访问原始目标端口。

所以就我而言,我有以下几点:

-A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow docker out
-A DOCKER-USER -s 172.17.0.0/16 -j ACCEPT
# Allow access to docker service mapped to host 8702 (the service is actually listening on port 8088 in the container)
-A DOCKER-USER -p tcp -m conntrack --ctorigdstport 8702 -j ACCEPT
# Prevent access to docker from outside
-A DOCKER-USER -j DROP

@系统悖论

为正确的 iptables 解决方案万岁! 🍻

我从未听说过--ctorigdstport但我想那是因为我没有阅读并尝试过所有可能的 iptables 扩展名,据我所知,您是第一个在这种情况下提到它的人。

我测试过,这确实有效:

$ docker run -d -p 7777:6379 --name data1 redis
$ docker run -d -p 8888:6379 --name data2 redis
$ sudo iptables -N DOCKER-USER-redis1
$ sudo iptables -A DOCKER-USER-redis1 -s 192.168.56.0/24 -p tcp -m tcp -j RETURN
$ sudo iptables -A DOCKER-USER-redis1 -j REJECT --reject-with icmp-port-unreachable
$ sudo iptables -N DOCKER-USER-redis2
$ sudo iptables -A DOCKER-USER-redis2 -s 10.0.24.0/24 -p tcp -m tcp -j RETURN
$ sudo iptables -A DOCKER-USER-redis2 -j REJECT --reject-with icmp-port-unreachable
$ sudo iptables -A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 7777 -j DOCKER-USER-redis1
$ sudo iptables -A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8888 -j DOCKER-USER-redis2

我认为这样的示例属于文档中,因为它可能涵盖 99% 的用户正在寻找的内容:使用-p公开端口的能力,但仍然能够使用-s等常用过滤器控制流量

我创建了一个请求来更新有关 iptables 的 Docker 文档。

https://github.com/docker/docker.github.io/issues/8087

https://unrouted.io/2017/08/15/docker-firewall/ 中列出的解决方案
似乎是类似的东西,创建了一个名为 FILTERS 的额外 iptables 链
到链 INPUT 和 DOCKER-USER 跳转到的地方。

@SystemParadox @colinmollenhour测试--ctorigdstport我可以确认它有效,但有一个小警告。

就我而言,我在 Apache 上有一个 dockerized PHP 应用程序,监听端口 80。我的规则只允许 1.2.3.4 如下:

-A DOCKER-USER -s 1.2.3.4/32 -i eth0 -p tcp -m conntrack --ctorigdstport 80 -j ACCEPT
-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 80 -j DROP

所以我的丢弃规则比你的更具体,只丢弃到达我的网络服务器的数据包——或者我是这么认为的。 事实上,它丢弃了定向到我的网络服务器的数据包,以及从 PHP 应用程序向第三方服务器发出的请求的响应返回的数据包。

这是因为--ctorigdstport不匹配被过滤数据包上的目标端口,而是匹配发起连接的数据包。 因此,对从 Docker 发出到其他服务器的请求的响应将具有SPT=80并且也将匹配--ctorigdstport 80

如果有人想对 DROP 规则进行更严格的控制,还必须添加--ctdir

-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j DROP

事实上,所有允许连接的规则也应该添加--ctdir以准确表达它们的含义:

-A DOCKER-USER -s 1.2.3.4/32 -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j ACCEPT

@jest哇,知道这一点真的很重要! 我没有意识到它可以匹配另一个方向的数据包。 当您考虑它时,它是有道理的,因为它与整个连接的状态相匹配,但是在阅读文档时很容易错过。

@SystemParadox是的,我没有机会通过文档通知自己,并且被 Docker 等待响应的请求所震惊。 :)

我一直在绕圈子,原因是需要--ctdir ORIGINAL 。 一方面, @jest的解释非常有道理,另一方面,我通常不必处理回复数据包,那么为什么这里会有什么不同呢?

我认为不同之处在于我将-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT作为第一条规则,所以我的其余规则从未看到任何回复数据包。 在这种情况下,我认为--ctdir ORIGINAL不是严格要求的,尽管无论如何包含它可能更安全。

@jest ,你同意吗? 大概您没有早期的ESTABLISHED,RELATED -j ACCEPT规则,这就是为什么这对您有所不同?

@jest你的帖子

您的方法是仅适用于由 docker 管理的内容,还是适用于所有内容? 例如,我的 ssh 端口(22)与 docker 无关。 我是否需要像往常一样使用-m tcp --dport 22 ,还是必须使用-m conntrack --ctorigdstport 22 --ctdir ORIGINAL

我假设您的方法仅适用于 docker 管理的流量,因为这些数据包在过滤器表中到达我之前会进行修改/natting。 但是,我是 iptables 的新手,所以我想从比我更了解的人那里确定!

@lonix1仅当您公开端口时,

@SystemParadox已经有一段时间了,我无法访问该服务器进行检查,但如果我没记错的话,有ESTABLISHED,RELATED规则(由 UFW 管理,在ufw-before-input链中)。 但是,在我的情况下,它们不会匹配从 docker 到 Internet 主机在端口 80 上的连接的第一个数据包 (SYN)。并且当没有--ctdir时会被DOCKER-USER的适当规则丢弃--ctdir

@Ionix1 ,主机上服务的数据包仅通过 INPUT,而 docker 服务的数据包仅通过 FORWARD 和 DOCKER-USER。

例如,给定一个 10.0.0.1 的外部 IP 和两个带有-p 4000:80-p 4001:80容器,您将看到具有以下属性的数据包:

INPUT:
dst 10.0.0.1 dport 80 ctorigdst 10.0.0.1 ctorigdstport 80
FORWARD/DOCKER-USER:
dst 172.17.0.5 dport 80 ctorigdst 10.0.0.1 ctorigdstport 4000
dst 172.17.0.6 dport 80 ctorigdst 10.0.0.1 ctorigdstport 4001

因此,您可以安全地将--dport 80用于 INPUT 规则,因为它们位于完全独立的链中。 如您所见, --ctorigdstport 80仍会匹配,但除非您也出于某种原因修改输入,否则我可能不会这样做。

您可能还注意到,您实际上可以使用--dport 80--dst 172.17.0.5来过滤特定 docker 容器的数据包,但该 IP 是不可预测的,这就是我们使用--ctorigdstport

最终,您需要了解任何给定规则可能匹配的数据包,这取决于您所在的链、目的地是什么以及是否有任何修改。

@jest谢谢,这似乎证实了我的想法。

所以,我需要一点方向……从 UFW 切换到 iptables ……
-我是否只是通过“ufw disable”关闭UFW?
- 我是创建自己的 .sh 文件,还是有一个现有的文件(我是 DigitalOcean 上的 Ubuntu)?
-我只需要告诉 Docker “--iptables=false”? (这就是Docker?)
-DOCKER-USER 已经由 Docker 创建和链接?

@fredjohnston如果需要,您可以继续使用 UFW。 配置文件存储在/etc/ufw 。 这里的问题是 Docker 不会显示 b/c, /etc/ufw/applications.d没有列出任何应用程序配置文件(任何其他防火墙工具及其配置都相同)。

在 Docker 中禁用 IPTables 意味着您在 Docker 中不会有太多的网络,只是 IP 地址和容器将无法相互通信。 DOCKER_USER是一个给你一些控制权的黑客,但实际上并没有解决问题——这实际上是关于默认情况下不将 Docker 容器在网络上公开,而是锁定到容器的 IP 地址。

目前,我确实建议您继续使用您最熟悉的任何防火墙工具(ufw 等),但请注意 Docker Containers 将在您的网络上公开。

由于我以任何方式在这里 - 我现在实际上遇到了一个相关问题,这也是 Linux 以外平台上的问题。 考虑以下:

  • 项目 A 有两个容器,一个用于数据库,一个用于应用程序。
  • 项目 B 有两个容器,一个用于数据库,一个用于应用程序。
  • 两个项目彼此隔离(单独的源存储库、配置等)
  • 这两个项目都在 Docker Compose 下管理
  • 两个项目都为本地开发目的公开了它们的数据库端口
  • 两个项目使用相同的数据库服务器(postgres、mysql 等)

现在假设您想在本地运行这两个项目 - 例如在两个项目使用的共享库上工作,以便您可以轻松地将其拉入他们的代码进行测试。

在当前的防火墙交互设计下——以及部分导致上述关于在用户不知情的情况下将容器暴露给公共网络的问题——两个项目的数据库 docker 不能同时运行,因为它们将争夺数据库的暴露端口。 如果您想公开它们的应用程序端口并且它们都为应用程序服务器使用相同的端口,则相同 - 很常见,因为基于 HTTP 的 API 和应用程序现在非常普遍,尤其是在面向云的应用程序中。

当然,您可以通过自己的方式在一个数据库容器下设置两者; 但是您并没有根据您的项目设计隔离它们,并且必须更加小心配置等。

在适当的解决方案中,这里有两个方面:

  1. 容器将只绑定到它们的 IP 并且它们暴露的端口将在它们各自的 Docker 网络中单独绑定到它们的 IP,而不是在系统上匹配所有 IP( 0.0.0.0:: )。
  2. 默认情况下,Docker 也不会在系统外公开向 Docker 网络公开路由。 Docker 网络可用于建立当前设计的网络间(docker 网络到 docker 网络)连接,然后默认情况下还允许本地主机连接。
  3. 然后,用户将需要添加适当的防火墙规则,以便在需要时将容器暴露给外界——例如,通过端口转发端口 443 到他们选择的容器的端口 443。

同样,这可以逐步完成:

  • 第 1 版:实施第 1 步和第 2 步; 但也(临时)添加非本地主机路由并发出警告。 当前获取端口的先来先服务行为保持不变; 并发出有关此行为消失的警告。
  • 版本 1+N:删除警告,并删除非本地主机路由。 对于希望 Docker 将端口暴露在系统外的用户,需要执行第 3 步,并确保这有据可查。

DOCKER_USER是一个给你一些控制权的黑客,但实际上并没有解决这个问题——这实际上是关于默认情况下不将 Docker 容器在网络上公开,而是锁定到容器的 IP 地址。

目前,我确实建议您继续使用您最熟悉的任何防火墙工具(ufw 等),但请注意 Docker Containers 将在您的网络上公开。

这里有一个文档更新请求https://github.com/docker/docker.github.io/pull/8357来创建一个附加的 iptables 静态配置,但我不确定它的状态。

编辑:我注意到您误解了 DOCKER-USER 的含义。 它不用于“使容器锁定到容器的 IP 地址”,而是用于使用 iptables 规则过滤对容器的访问。

@aki-k 每个 DOCKER_USER 我知道DOCKER_USER不会针对 IP 地址锁定容器,并且从未声称它确实如此。 DOCKER_USER只是将安全问题交给用户来管理——这意味着用户必须知道如何操纵他们的防火墙以确保他们实际上拥有一个安全的环境。 将其作为用户问题同样是不可接受的,因为大多数用户知道如何管理他们的防火墙 - 防火墙规则很难,即使我们这些对编写防火墙规则非常了解的人仍然经常会出错。

我的要求 - 以及整个问题 - Docker 需要在默认情况下是安全的,并且不会在用户不知情或没有明确干预的情况下将容器暴露给外部世界。 根据我的最后一条评论 (https://github.com/moby/moby/issues/22054#issuecomment-552951146) 这样做也会有其他一些技术优势。

我的要求 - 以及整个问题 - Docker 需要在默认情况下是安全的,并且不会在用户不知情或没有明确干预的情况下将容器暴露给外部世界。

我发现的最早的问题报告来自 2014 年 3 月 18 日:

https://github.com/moby/moby/issues/4737

这可能是他们不想更改并尝试使用 DOCKER-USER iptables 链修复的设计决策。 您可以使用 docker run 的 -p 选项仅将端口发布到 docker 主机(-p 127.0.0.1:port:port),但这也不能解决问题。

让我们明确一点,Docker 默认安全的。 您必须告诉 Docker 进行端口转发。

对于需要相互通信的服务,您应该使用网络( docker network )来限制访问,而不是端口转发。

让我们明确一点,Docker _Is_ 默认是安全的。 您必须告诉 Docker 进行端口转发。

请不要忽视 docker 运行在您已建立的 iptables 保护上的事实问题。 我相信你已经看到无数的问题报告讨论这个问题。

让我们明确一点,Docker _Is_ 默认是安全的。 您必须告诉 Docker 进行端口转发。

对于需要相互通信的服务,您应该使用网络( docker network )来限制访问,而不是端口转发。

让我们明确一点 - Docker 是安全的,直到您想在 Docker 网络之外访问它。 一旦你想从本地主机 (127.0.0.1) 访问它,默认情况下 Docker并不安全,因为它绑定到 0.0.0.0 并将容器暴露在系统之外 - 绕过用户为保护他们的系统所做的任何事情,而不会出现在工具中,而不是直接使用iptables 。 DOCKER_USER 不是也永远不会是一个合适的解决方案,因为它要求用户对底层防火墙系统(Linux 上的 iptables,无论 Mac 使用什么,以及 Windows 上的 Windows 防火墙)了解太多。 默认情况下,这仍然需要是安全的。 正如我之前在https://github.com/moby/moby/issues/22054#issuecomment -552951146 中概述的那样,有一种非常简单的方法可以做到这一点,并且在整个问题中多次提到(尽管可能没有那么清楚)评论)。

还要注意,公开端口的文档也没有提到这个安全问题,没有提到 Docker 在公开端口时如何与系统防火墙交互 - 从而降低了在使用 Docker 之前设计为安全的系统的安全性.

让我们明确一点,Docker _Is_ 默认是安全的。 您必须告诉 Docker 进行端口转发。
对于需要相互通信的服务,您应该使用网络( docker network )来限制访问,而不是端口转发。

让我们明确一点 - Docker 是安全的,直到您想在 Docker 网络之外访问它。 一旦你想从本地主机 (127.0.0.1) 访问它,那么默认情况下 Docker _not_ 安全,因为它绑定到 0.0.0.0 [...]

准确地说,Docker 不绑定到 0.0.0.0。 这是用户的(合理的)期望:在 CLI 上指定--publish ,因此在用户空间中运行,启动某种代理守护进程,侦听指定端口,接受传入连接并来回推送Docker 和外部世界之间的所有数据包。

但相反,Docker 将神奇的 DNAT/masquarading 规则注入防火墙以重写数据包上的地址,从而随机破坏任何预先安装的规则系统。

在我看来,这里混乱的抽象级别是最大的麻烦并让用户感到困惑。 我不知道 Docker 团队在设计我们在这里看到的--publish机器时考虑了哪些场景,我认为没有一个可以证明该决定的合理性(也许除了性能原因)。

让我们明确一点,Docker 默认是安全的。 您必须告诉 Docker 进行端口转发。

...这恰好是 Docker 最常用的功能之一。 实际上,您刚刚说过,在 Docker 最常用的功能之一中对预先存在的防火墙规则的未记录覆盖使 Docker _默认_安全_。

好吧,我想无论对你有用。 由于它对我不起作用,我将开始寻找替代容器平台。 安全问题可以解决,但公然无视合理的安全期望是另一种野兽。

让我们评估一下今天的情况:

默认情况下,不公开任何端口。 您必须告诉 Docker 公开端口。
过去 Docker 会设置 iptables,这样任何知道如何路由到桥接网络的东西都可以访问容器 IP(通过将转发策略设置为“接受”),但现在不再如此。

我看到有些人说他们正在使用-p向彼此公开服务,这不应该是必需的。
在默认网络上,您可以使用--link将服务连接在一起,容器可通过 DNS 使用。
在非默认网络(用户定义的网络)上,容器也可以通过 DNS 相互访问,包括通过--link设置别名,甚至是具有指定别名的网络。

在许多情况下,您似乎只想从客户端连接到服务,在这种情况下,建议使用另一个容器来访问您要连接的服务,而不是公开端口。

-p专为 ingress 设计,让外部事物访问此服务。
-p的默认值确实允许来自任何地方的流量。 您可以通过手动指定每个-p或作为守护程序范围设置允许的地址来更改此设置。
由于-p确实使用了 iptables,因此创建了DOCKER-USER链,以便用户可以在它到达容器之前添加自己的过滤规则。

由于-p是为入口设计的,我认为这样暴露流量是合理的。 我确实觉得 Docker 规则被插入到过滤器表的顶部是不幸的,但是改变这一点对于想要这种行为的一大群用户来说将是一个重大的改变。

-p有几个其他的替代方案:

  1. 不要使用-p ,直接连接容器IP。 这需要一些额外的工作,因为您必须查找 IP,但这些数据可从 API 获得。 您还需要确保防火墙上的转发策略允许这样做(假设您从不同的主机连接,相同的主机应该没问题)
  2. 将 macvlan 或 ipvlan 网络用于您希望从主机网络(即入口)访问的服务。 这些网络选项直接从主机的网络接口(您选择它绑定到哪个接口)为容器提供 IP。
  3. 使用--net=host ,这会在主机网络命名空间上运行服务,从而使服务可以访问主机上已经存在的网络基础设施。

您说“默认情况下使其安全”,但根据定义,公开端口是一种潜在的不安全操作。 似乎也有一些想法只向 localhost 公开是安全的,但这并不是因为主机上运行的任何东西都可以访问 localhost(如果是桌面,则包括浏览器中的 javascript)。

您想通过使用-p解决什么用例?
您对您希望看到的实际变化有什么想法吗?

我很好地改变了一些东西以使其更适合您的工作流程,但这里有很多不同的用例,一种尺寸永远不会适合所有(请参阅有关-p投诉)。

@cpuguy83一切都很好,但在此请求中没有任何改变。

人们使用 docker 并希望从他们的本地系统连接到在 docker 下运行的应用程序 - 这对于开发人员来说是一个非常常见的用例,尤其是在尝试诊断某些东西或编写不在 docker 下但需要连接到托管在 docker 中的服务的服务时(填写开发环境)。 在 Docker 下开发并不总是一个好的、有趣或有用的体验,或者说:

建议使用另一个容器来访问您要连接的服务,而不是暴露端口。

简直是无路可走。 并非所有工具都对 Docker 友好,也不应该如此。 不应该要求用户完全在 Docker 下使用在 Docker 下运行的服务。 即便如此,如果使用 Docker 来操作服务器,管理员也无法通过防火墙配置和公开端口功能轻松控制它们,但是它是编排的(命令行、Dockerfile、Docker Compose 配置)完全被破坏。

此外,人们使用 Docker Compose 来管理大部分环境,并通过docker-compose.ymlDockerfile指定需要公开端口以便他们可以在本地访问它。 因此,说use the -p参数是不正确的,因为它们从不以可行的方式直接与 docker 命令交互。

暴露端口并不意味着必须破坏安全性。 我已经概述了如何在不破坏安全性的情况下向本地系统公开端口 (https://github.com/moby/moby/issues/22054#issuecomment-552951146) 并且它将使外部暴露的管理(关闭系统)以一种他们可以使用现有工具轻松控制的方式掌握在用户手中。

解决方案:

  • 使用 Docker 网络
  • 单独在 Docker 网络和本地主机上公开端口
  • 停止在 0.0.0.0 上绑定端口 - 或者有效地这样做
  • 要求用户使用自己的防火墙工具来暴露系统外的端口(ufw、firewalld 等)
  • 为通用防火墙提供集成,使这一切变得容易

换句话说,不是通过127.0.0.1:<port>访问 docker 容器中的服务,甚至从本地主机也需要<docker container ip>:<service port> 。 如果人们想在系统外公开服务,他们可以通过他们的工具(ufw 等)添加防火墙规则,将给定端口的端口转发到<docker container ip>:<service port>

另外,遵循他们的代理设计的Kubernetes办法,切实做好为@jest中建议https://github.com/moby/moby/issues/22054#issuecomment -554665865。 同样,这仍然需要是本地系统,直到用户有意将其暴露给外部网络。

这里最大的问题是 Docker 正在损害防火墙的完整性以提供其服务,这样做根本没有告诉用户,也没有集成到用户的工具中,因此用户既不知道也无法控制它。

我意识到市场上有大量的防火墙接口——即使使用 iptables,也有 firewalld、ufw 和其他十几个。 我真的不希望 Docker 与它们集成(尽管那会很好),但我确实希望 Docker 以一种不会绕过它们或破坏用户设置的任何安全性的方式工作。

作为一个基本的测试用例:

  • 设置基于 Debian 的服务器(Debian、Ubuntu)
  • 安装 ufw、OpenSSH 服务器
  • 运行ufw allow OpenSSH
  • 运行ufw enable

此时,您有一个非常安全的服务器; 唯一允许的流量是 (a) 与传出流量或 (b) SSH 服务器流量有关。

现在,启动一个带有暴露端口的 docker 容器。

可接受的解决方案应满足以下条件:

  • 该端口不应从另一台计算机访问; 防火墙应该继续阻止它来自外部系统。
  • 该端口应该可以从本地计算机 (localhost) 访问。
  • 多个 docker 容器应该能够暴露同一个端口并且一切正常; 所有具有暴露端口的容器只能从本地系统访问。
  • 用户不应该知道他们的防火墙配置细节来实现这一点。

使用firewalld对基于 CentOS/Fedora/RHEL 的系统进行相同的尝试。

@cpuguy83 IMO 人希望-p在应用程序级别上工作。 也就是说,如果我-p 80:80 ,我期望的行为就像绑定到容器中端口 80 的应用程序在主机上运行一样。

VirtualBox 或 SSH 模型端口以这种方式转发,因此人们假设在 Docker 情况下也是如此。

使用更广泛的期望并行:从用户的角度来看,它应该作为主机绑定卷。 您只需指向主机目录,并且“神奇地”它在容器内可见; 从另一端对文件系统所做的任何修改都是一样的,包括权限、配额等。

-p问题缩小到防火墙案例:在正常世界中,用户希望绑定到 0.0.0.0:80 的应用程序对外界可见。 如果用户想要限制访问,他会在许多指南中被指导使用防火墙并在INPUT链中设置规则:

-P INPUT DROP
-A INPUT -s 1.2.3.4/32 -p tcp --dst-port 80 -j ACCEPT

或使用 UFW 之类的工具:

ufw enable
ufw allow http

但是对于 docker,用户必须能够突然构建这样的规则:

-A DOCKER-USER -s 1.2.3.4/32 -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j ACCEPT
-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j DROP

请向我展示一份针对普通用户如何使用 Docker 解决此类常见场景的综合指南。

例如对于一个普通的开发人员——你一直在向他们推销整个“应用容器化”概念——暴露端口的后果简直是不可预测的。

这些天,由于我缺乏知识,我根本不公开端口,而是使用 Traefik 之类的服务。 也许--expose也不应该被提议作为 CLI 文档中的通用用途,因为 IMO 它对普通用户没有任何好处。

或者,向任何没有深入了解这些工具的人提供 Linux 笔记本电脑,例如戴尔或联想,并见证他们在正确设置防火墙方面的真诚努力如何在有人设法访问他们的本地数据库时完全没有区别在星巴克喝咖啡。

这个确切的问题给我们带来了一个系统漏洞,这是我在周末发现的。 根据@jacoscaz ,我只能访问另一个开发人员的本地数据库,因为它有一个已发布的端口。 如果你们都可以将其包含在介绍文档中,这样其他人就不会这样做,那就太好了。 我们需要一个非容器化的服务来连接到一个容器,而网络上的其他人都无法访问,因此 Docker 网络是不可能的。 听起来现在最好的选择是连接到本地容器 IP,除非有人有更好的主意。

@dentonmwood这听起来就像你应该做的。 正如我上面提到的,您甚至可以将服务设置为使用 macvlan 或 ipvlan 运行,这将直接在您的正常网络上为其提供 IP。

@BenjamenMeyer @jest @jacoscaz

感谢您的额外反馈。

我同意我们要求用户在这方面提出的知识并不多。 目前,我们让用户(无论是系统管理员还是开发人员)有责任了解-p作用,并采取适当的预防措施以确保服务仅暴露给他们认为暴露给的人。 我们甚至希望那些通常没有任何理由了解 iptables 的开发人员介入并针对他们自己的环境修复它(通过DOCKER-USER ),从而更进一步。
由于担心破坏兼容性,我们基本上处于这种情况。

我有一些我还没有时间充分考虑的想法,但它基本上会根据客户端的 API 版本改变-p的行为并单独处理入口。 仍然是一个让我担心的重大变化,但至少旧的 API 版本保留了旧的行为。

我确实有一个想法,我们可以为本地访问的用例做一个本地代理(ala kubectl proxy ),但是这再次使开发人员有责任知道和理解比他们真正需要的更多.

想法?

我认为引入代理是朝着好的方向迈出的一步。

我承认“端口转发”是容器和编排器之间的共同责任,在精神上类似于 Swarm 模式。 但是始终需要最少的主机集成,并且当向(非专家)用户提供-p类的选项时,建议的解决方案应该是这样的人可以理解的。

由于未指定-p对环境的影响(我认为至少在 CLI 级别),因此有可能引入用户友好的工作方式配置。

例如,目前iptables中有/etc/docker/daemon.json ,决定是否操作DOCKER 。 可以使用另一个条目扩展该配置,例如像iptables==true && expose_with_network_proxy==true这样的组合将开启类似代理的行为。

这对于新安装通常应该是安全的,因为前段时间更喜欢overlay2不是aufs (如果我没记错的话)。 并且易于部署,因为升级不涉及现有配置文件,但新安装可以自由进行。

由于担心破坏兼容性,我们基本上处于这种情况。

我真的想强调一个事实,至少在我看来,这个问题的严重性更多地与缺乏文档有关,而不是与-p当前的技术基础有关。 是的,我不相信当前的机制可以说是安全的,但是安全漏洞是可以修复的,我没有资格批评导致当前情况的原因。 毕竟,事后看来总是 20/20,我钦佩对向后兼容性的承诺以及面对这种承诺带来的技术挑战的意愿。

我不明白的是,为什么 Docker 的文档中仍然没有明亮的、大的、红色的框明确警告使用-p副作用。 如果我遇到这样的警告,我想我一开始就不会在这里。

想法?

代理选项听起来很合理。 另外,我喜欢能够在需要时公开和取消公开端口的想法,而不是在启动docker run时必须这样做。

@BenjamenMeyer @jest @jacoscaz

感谢您的额外反馈。

感谢您最终更仔细地研究了这一点。

我同意我们要求用户在这方面提出的知识并不多。 目前,我们让用户(无论是系统管理员还是开发人员)有责任了解-p作用,并采取适当的预防措施以确保服务仅暴露给他们认为暴露给的人。 我们甚至希望那些通常没有任何理由了解 iptables 的开发人员介入并针对他们自己的环境修复它(通过DOCKER-USER ),从而更进一步。
由于担心破坏兼容性,我们基本上处于这种情况。

我有一些我还没有时间充分考虑的想法,但它基本上会根据客户端的 API 版本改变-p的行为并单独处理入口。 仍然是一个让我担心的重大变化,但至少旧的 API 版本保留了旧的行为。

想法?

  1. 从更新文档开始,以便用户通过所有方法(-p、Dockerfile、docker-compose.yml)了解 EXPOSE 的效果。 可以很快完成这么多。 如果社区也希望向后兼容,这也可能有助于让更多人在寻找好的解决方案方面提供建议/专业知识,并帮助回答这个问题。
  2. 计划一种方法,从 Docker 现在的位置到 Docker 需要的位置。 这可能包括在某个时候发生的重大变化。

我认为情况的严重性当然允许进行重大更改来修复它; 只是不要在没有通知人们的情况下这样做。 设定一个时间表(6 个月?12 个月?),在哪些地方对变更进行了大量讨论,等等,并为社区做好准备; 然后在“主要”版本中进行更改。 根据您的版本架构,第一组似乎是年份 (19); 鉴于我们已到 2019 年底,请使用 2019 年剩余时间和 2020 年制定解决方案并进行宣传; 在 2020 年的某个时候将其作为可选功能引入,然后在 2021 年将其提升为第一名/默认使用。

就设置默认行为而言,Dockerfile 版本和 Docker Compose Schema 版本也可能是很好的工具,但我不会阻止旧版本利用它,并且会发出关于需要更新的强烈警告在这种情况下也是如此。

不管它是如何推出的,我认为你会发现整个社区更支持这种改变,如果他们理解为什么和正在发生的事情并且不觉得他们被它蒙蔽了双眼。

我确实有一个想法,我们可以为本地访问的用例做一个本地代理(ala kubectl proxy ),但是这再次使开发人员有责任知道和理解比他们真正需要的更多.

我确实喜欢代理的想法, @jest关于如何启用它的建议也很可能是进行迁移的好方法。

老实说,大多数希望在系统外公开它的人都会或应该在某种程度上熟悉防火墙,即使这只是如何配置 UFW 或 firewalld 来这样做。 因此,如果该解决方案仅可用于本地主机,那么我认为人们学习如何使用他们的防火墙工具通过防火墙进行端口转发是可以接受的。

我确实认为通过用户决定使用的防火墙工具完成此类功能很重要,因为这将使他们可以看到所有内容。 这样的功能不应该绕过他们的工具。 我也意识到有太多的防火墙工具,Docker 与它们集成是不合理的。 所以我建议采取文档路线,并强调如何从一些流行的开始,让社区添加更多并更新它们。

我确实喜欢代理的想法, @jest关于如何启用它的建议也很可能是进行迁移的好方法。

老实说,大多数希望在系统外公开它的人都会或应该在某种程度上熟悉防火墙,即使这只是如何配置 UFW 或 firewalld 来这样做。 因此,如果该解决方案仅可用于本地主机,那么我认为人们学习如何使用他们的防火墙工具通过防火墙进行端口转发是可以接受的。

我附和这个。 使用代理解决方案,我认为提出一些允许用户将代理绑定到他们想要的任何接口和端口组合的东西是可行的。 但是,如果以向后兼容性(我支持!)或任何其他原因的名义被迫妥协,则应优先考虑匹配合理的安全期望,即使以将系统外暴露委托给用户为代价。

根据我的评论,代理实际上可以根据可用的防火墙工具以不同的方式运行。 如果检测到受支持的防火墙配置工具,代理可以使用它来设置适当的转发规则。 可以添加此类工具作为生产环境的要求。 如果这样的工具不可用,代理将默认生成应用程序级代理服务器。

Docker 绕过 macOS 防火墙,就像在 Linux 上一样。

在我的开发机器(Mac 版 Docker 桌面)上,我将"ip": "127.0.0.1"到 Docker 守护程序配置中。 这样,默认情况下,任何开发数据库等都只能从本地主机访问。 对于那些需要让其他人看到应用程序的罕见情况,我可以使用-p 0.0.0.0:8080:8080显式发布它。

编辑:似乎 Docker Compose 仍然默认绑定到 0.0.0.0,无论 Docker 守护进程配置如何。 所以这个技巧只有在从命令行运行 Docker 时才有效。 无论如何,由于 Compose 文件经常与可能具有不同系统配置的队友共享,因此最好将 127.0.0.1 明确添加到 Compose 文件中。

@luontola多么优雅的解决方案。

编辑:我还在想的可能是一种允许/阻止地址列表的方法,这些地址可以在例如 Docker compose 中定义,然后会自动添加到 DOCKER-USER iptables 链中。

一个小 oopsie 雏菊,突出了这个问题的重要性: https :

@danhallin像您

嗯有趣的阅读!

我在 Mac OS X 上运行,还有一个名为 LittleSnitch 的本地防火墙。 它只是弹出一个对话框,询问 185.156.177.252 是否可以连接到 com.docker.backend 进程。 我点了拒绝。

我想知道它如何在我的路由器中打开一个端口,但我刚刚意识到这当然是通过 UPnP 完成的! 所有路由器都支持这一点。

我现在想知道的是为什么。 为什么来自 185.156.177.252 的东西试图从外部连接? 如果我的本地 Docker 进程需要一些东西,它应该从内部调用 home,而不是在外部打开端口。

@cpuguy83一切都很好,但在此请求中没有任何改变。

人们使用 docker 并希望从他们的本地系统连接到在 docker 下运行的应用程序 - 这对于开发人员来说是一个非常常见的用例,尤其是在尝试诊断某些东西或编写不在 docker 下但需要连接到托管在 docker 中的服务的服务时(填写开发环境)。 在 Docker 下开发并不总是一个好的、有趣或有用的体验,或者说:

建议使用另一个容器来访问您要连接的服务,而不是暴露端口。

简直是无路可走。 并非所有工具都对 Docker 友好,也不应该如此。 不应该要求用户完全在 Docker 下使用在 Docker 下运行的服务。 即便如此,如果使用 Docker 来操作服务器,管理员也无法通过防火墙配置和公开端口功能轻松控制它们,但是它是编排的(命令行、Dockerfile、Docker Compose 配置)完全被破坏。

此外,人们使用 Docker Compose 来管理大部分环境,并通过docker-compose.ymlDockerfile指定需要公开端口以便他们可以在本地访问它。 因此,说use the -p参数是不正确的,因为它们从不以可行的方式直接与 docker 命令交互。

暴露端口并不意味着必须破坏安全性。 我已经概述了如何在不破坏安全性的情况下向本地系统公开端口( #22054(评论) ),并且它将以一种他们可以轻松的方式将外部暴露(系统外)的管理交到用户手中使用他们现有的工具进行控制。

解决方案:

* Use Docker Network

* Expose the Ports on the Docker Network alone and local host

* Stop binding the port on 0.0.0.0 - or effectively doing so

* Require users to use their own firewall tooling to expose the port off system (ufw, firewalld, etc)

* Provide integrations for common firewalls to make this easy

换句话说,不是通过127.0.0.1:<port>访问 docker 容器中的服务,甚至从本地主机也需要<docker container ip>:<service port> 。 如果人们想在系统外公开服务,他们可以通过他们的工具(ufw 等)添加防火墙规则,将给定端口的端口转发到<docker container ip>:<service port>

或者,遵循 Kubernetes 方法及其代理设计,有效地按照#22054(评论)中的@jest建议进行操作。 同样,这仍然需要是本地系统,直到用户有意将其暴露给外部网络。

这里最大的问题是 Docker 正在损害防火墙的完整性以提供其服务,这样做根本没有告诉用户,也没有集成到用户的工具中,因此用户既不知道也无法控制它。

我意识到市场上有大量的防火墙接口——即使使用 iptables,也有 firewalld、ufw 和其他十几个。 我真的不希望 Docker 与它们集成(尽管那会很好),但我确实希望 Docker 以一种不会绕过它们或破坏用户设置的任何安全性的方式工作。

作为一个基本的测试用例:

* Setup a Debian-based server (Debian, Ubuntu)

* Install ufw, OpenSSH Server

* run `ufw allow OpenSSH`

* run `ufw enable`

此时,您有一个非常安全的服务器; 唯一允许的流量是 (a) 与传出流量或 (b) SSH 服务器流量有关。

现在,启动一个带有暴露端口的 docker 容器。

可接受的解决方案应满足以下条件:

* The port should not be accessible from another computer; the firewall should continue to block it from outside systems.

* The port should be accessible from the local computer (localhost).

* Multiple docker containers should be able to expose the same port and have everything work; all containers with an exposed port should be accessible from the local system only.

* The user should not have to know about their firewall configuration details to make this happen.

使用firewalld对基于 CentOS/Fedora/RHEL 的系统进行相同的尝试。

当我试图弄清楚是否有一种方法可以将 docker 配置为在每次发布端口时都不会绕过我的防火墙输入规则时,我遇到了这个问题。 上面的评论很好地解释了整个事情,也是一种更可取的 IMO 方法。

昨天,我偶然发现了这个问题。 我花了数周时间为我的 VPS 精心定制我的 iptables 防火墙规则。 我试图保持谨慎和限制。 默认情况下,过滤器输入和输出设置为下降。 我明确允许某些流量,并阻止其他所有流量。 流量被记录下来,我一直在测试和监控。

我知道 Docker 对前向链和 NAT 表进行了自己的更改..所以我一直_极其_小心地在我的整个过程中尊重这些更改。 此外,我所有的容器都连接到用户定义的网络。 从不使用默认主机网络。 官方文档告诉我们不要这样做。

我的第一个惊喜是我的容器化 Nginx 代理可以被整个互联网访问。 我的防火墙规则中有一个选项可以允许来自世界各地的入站 Web 流量。 但我还没有打开那个功能。 我的 iptables 中没有任何地方明显允许 HTTP 80/443。

我的一些 Web 应用程序依赖于像 MySQL 这样的数据库。 最初我_不_使用 Docker Compose 中的-p选项。 我知道这没有必要,因为我的 Web 应用程序和数据库服务器共享同一个用户定义的 Docker 网络。 但作为一名前 DBA,我一直在考虑备份。 因此,我激活了-p选项以允许我主机的cron 作业和备份工具进行及时备份。 我还编写了另一个防火墙 _option_ 以允许外部 SQL 访问。 但同样,还没有启用。

在我对 Nginx 感到惊讶之后,我明智地决定再次检查 MySQL 是否也没有暴露。 我试图(从我的笔记本电脑)连接到远程 VPS 上的 MySQL 数据库。 当我立即成功连接时再次震惊。

我说的所有内容都没有在之前的帖子中详细讨论过。 非常感谢@BenjamenMeyer @jest @jacoscaz的有用调查和建议。 但是对于那些要求现代用例和体验的人呢? 给你。 在这个帖子开始 4 年多之后,像我这样的人仍然遇到这种行为。 仍然感到震惊。

是的,有很多解决方法。 我会立即追求一些。 但是要实现这些,您首先必须真正知道这个问题存在。 这是最让我失望的。 并不是说 Docker 开发人员做出了某些设计决策,无论好坏。

但是这种“防火墙绕过行为”并没有被清楚地指出、大声警告和更广泛地记录。 在网络安全方面,惊喜从来都不是一件好事。

想分享我对此问题的解决方法,以防其他人觉得它有用。 使用 iptables 和 ipset。 在 Ubuntu、CentOS 7 和 RHEL 7 上测试。使用 ipset 是因为“受信任的”IP 并不总是在相同的 IP 范围内(修复 iptables 的限制)。
它适用于普通 Docker 和 Docker Swarm(又名 SwarmKit)。
它确保您在默认情况下是安全的,只允许您指定的 IP 能够连接到您指定应该打开的操作系统端口和 Docker 端口(使用 iptables 和 ipset)。 还可以选择使 OS 端口或 Docker 端口“公开”(对所有 IP 开放)

Ansible 不是必需的,但使它更容易.. Ansible 角色: https :
CentOS/RHEL 的手动步骤:
https://github.com/ryandaniels/ansible-role-iptables-docker#manual -commands-centosrhel
和 Ubuntu 18.04/20.04:
https://github.com/ryandaniels/ansible-role-iptables-docker#manual -commands-ubuntu-2004

除了 iptables 之外,您还需要安装/配置 ipset(如果遇到问题,请参阅上面的链接)。
iptables 配置示例(ssh 端口 22 对所有人开放):

*filter
:DOCKER-USER - [0:0]
:FILTERS - [0:0]
#Can't flush INPUT. wipes out docker swarm encrypted overlay rules
#-F INPUT
#Use ansible or run manually once instead to add -I INPUT -j FILTERS
#-I INPUT -j FILTERS
-A DOCKER-USER -m state --state RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -i docker_gwbridge -j RETURN
-A DOCKER-USER -s 172.18.0.0/16 -j RETURN
-A DOCKER-USER -i docker0 -j RETURN
-A DOCKER-USER -s 172.17.0.0/16 -j RETURN
#Below Docker ports open to everyone if uncommented
#-A DOCKER-USER -p tcp -m tcp -m multiport --dports 8000,8001 -j RETURN
#-A DOCKER-USER -p udp -m udp -m multiport --dports 9000,9001 -j RETURN
-A DOCKER-USER -m set ! --match-set ip_allow src -j DROP
-A DOCKER-USER -j RETURN
-F FILTERS
#Because Docker Swarm encrypted overlay network just appends rules to INPUT
-A FILTERS -p udp -m policy --dir in --pol ipsec -m udp --dport 4789 -m set --match-set ip_allow src -j RETURN
-A FILTERS -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FILTERS -p icmp -j ACCEPT
-A FILTERS -i lo -j ACCEPT
#Below OS ports open to everyone if uncommented
-A FILTERS -p tcp -m state --state NEW -m tcp -m multiport --dports 22 -j ACCEPT
#-A FILTERS -p udp -m udp -m multiport --dports 53,123 -j ACCEPT
-A FILTERS -m set ! --match-set ip_allow src -j DROP
-A FILTERS -j RETURN
COMMIT
此页面是否有帮助?
0 / 5 - 0 等级