Docker VPN 将内部流量路由到可从外部访问的容器

Docker VPN 将内部流量路由到可从外部访问的容器

我有一个运行多个 docker 容器的 VPS。我有一个 nextcloud 实例,它通过 nginx 代理(来自 Let'sEncrypt 的证书)获得 SSL/TLS 终止。我有一个 openvpn 容器。在它的 docker 网络中,我还托管了其他服务(自己的绑定 dns 服务器和 git 服务器),我可以通过 VPN 访问这些服务。

现在我还想通过 VPN 访问我的 nextcloud 实例。最初我认为这不会是个问题,因为可以通过互联网访问 nextcloud 实例,并且 VPN 也可以连接到互联网。但不幸的是我无法访问它。如果我通过 VPN curl 我的服务器(http 或 https),我会得到“端口 80/443:没有到主机的路由”。如果不连接 VPN,连接就可以正常工作。

如果我使用 traceroute,我可以看到它正确地到达了我的 VPS 的公共 IP。所以我得出结论,这是路由问题。针对我的 VPS 公共 IP 上的端口 80/443 的流量不会被转发/路由到 nginx 代理容器(它公开了上述端口)。

据我了解,docker 使用firewalld/iptables 来路由容器之间的流量。因此,对 VPN 流量应用的规则与来自互联网的流量不同。我需要如何配置,以便到我的公共 IP 地址的 VPN 流量(服务器内部)正确转发到相应的容器?我希望在 VPN 和 No-VPN 状态之间保持恒定/不变的连接,以便我的 Nextcloud 应用程序不会混淆。

我已尝试过: 我尝试了各种解决方法。我可以在我的 VPN DNS 服务器中为我的 nextcloud 实例添加自己的 DNS 条目,该条目指向 nextcloud 应用容器(我将在其中丢失 SSL/TLS 终止)或 nginx 代理的 IP。在最后一种情况下,nginx 代理不会将流量转发到 nextcloud 容器,因为它使用了不同的主机名。如果可能的话,我想保持代理配置不变,因为它会在容器启动时/从 letsencrypt 配套容器中自动填充。此外,证书不会与使用的 FQDN 匹配。如果我尝试使用我的真实/公共 DNS 名称添加主区域(以便我可以使用与外部相同的 FQDN),则来自该 TLD 的所有其他域都不会再转发(是否可以为此配置绑定?)。

总结:从 Docker 容器到公共 VPS IP 地址的流量不会像来自外部的流量那样被转发到正确的 Docker 容器。

如果您还需要有关所用容器的更多信息,我将添加链接和我的 docker-compose 文件。

编辑:

[root@XXXXXXXX ~]# iptables -S FORWARD
-P FORWARD ACCEPT
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -o br-7e5cecc96f4a -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-7e5cecc96f4a -j DOCKER
-A FORWARD -i br-7e5cecc96f4a ! -o br-7e5cecc96f4a -j ACCEPT
-A FORWARD -i br-7e5cecc96f4a -o br-7e5cecc96f4a -j ACCEPT
-A FORWARD -o br-fd56ce52983e -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-fd56ce52983e -j DOCKER
-A FORWARD -i br-fd56ce52983e ! -o br-fd56ce52983e -j ACCEPT
-A FORWARD -i br-fd56ce52983e -o br-fd56ce52983e -j ACCEPT
-A FORWARD -o br-f1ef60d84b48 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-f1ef60d84b48 -j DOCKER
-A FORWARD -i br-f1ef60d84b48 ! -o br-f1ef60d84b48 -j ACCEPT
-A FORWARD -i br-f1ef60d84b48 -o br-f1ef60d84b48 -j ACCEPT
-A FORWARD -o br-b396aa5a2d35 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-b396aa5a2d35 -j DOCKER
-A FORWARD -i br-b396aa5a2d35 ! -o br-b396aa5a2d35 -j ACCEPT
-A FORWARD -i br-b396aa5a2d35 -o br-b396aa5a2d35 -j ACCEPT
-A FORWARD -o br-83ac9a15401e -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-83ac9a15401e -j DOCKER
-A FORWARD -i br-83ac9a15401e ! -o br-83ac9a15401e -j ACCEPT
-A FORWARD -i br-83ac9a15401e -o br-83ac9a15401e -j ACCEPT
-A FORWARD -d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT
-A FORWARD -i virbr0 -o virbr0 -j ACCEPT
-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i lo -j ACCEPT
-A FORWARD -j FORWARD_direct
-A FORWARD -j FORWARD_IN_ZONES_SOURCE
-A FORWARD -j FORWARD_IN_ZONES
-A FORWARD -j FORWARD_OUT_ZONES_SOURCE
-A FORWARD -j FORWARD_OUT_ZONES
-A FORWARD -m conntrack --ctstate INVALID -j DROP
-A FORWARD -j REJECT --reject-with icmp-host-prohibited

答案1

默认情况下,Docker 不允许任何两个连接到不同网桥的容器之间的通信。此外,它还不允许从容器到 docker 本身已映射到外部的端口的通信。这一切都通过以下方式实现:iptables

首先,端口到外部的映射也发生在iptables。它使用DNAT规则纳特表。对于这些规则,Docker 会创建一个单独的DOCKER链,以便从PREROUTINGOUTPUT纳特表。DNAT规则前面有RETURN跳转,可以过滤掉来自 Docker 桥的所有流量。所以这是第一个障碍。

它看起来有点像这样:

-A DOCKER -i br-one -j RETURN
-A DOCKER -i br-two -j RETURN
-A DOCKER ! -i br-one -p tcp -m tcp --dport EXPOSEDPORT -j DNAT --to-destination 172.17.0.2:INTERNALPORT

如果您仅将端口暴露给该本地地址,则规则也可以具有。由于之前的规则,任何 Docker 网桥的流量都无法命中规则DNAT。此外,规则不允许通过流量来自的同一网桥返回。这无论如何都没有必要,因为从同一网桥您可以直接到达-d addressDNATRETURNDNATDNAT内部端口已经。

不同桥梁上的集装箱通行限制在筛选iptables。链的开头有两个自定义链FORWARD,该链的默认策略是DROP。一个用于具有用户定义桥的容器,另一个用于具有 Docker 桥的容器:DOCKER-ISOLATION-STAGE-1。该链再次使用DOCKER-ISOLATION-STAGE-2。两者的组合基本上表明,如果流量树叶Docker 桥接器进入另一个 Docker 桥,然后DROP它(没有 ICMP 信令,所以连接就挂起了.....)

它看起来像这样:

-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A DOCKER-ISOLATION-STAGE-1 -i br-one ! -o br-one -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i br-two ! -o br-two -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-2 -o br-one -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-two -j DROP

所以如果你想要从桥上获取流量,击中DNAT桥上集装箱暴露在外面的端口并且您希望流量返回以建立完整的连接,那么您必须做以下几件事:

  • 删除阻止来自以下链的RETURN流量的规则:DNATDOCKER纳特表。您必须删除RETURN源网桥的。RETURN如果您不想允许来自该网桥的容器访问DNAT暴露端口,您可以保留目标网桥的。

    • iptables -t nat -D DOCKER -i br-one -j RETURN
    • iptables -t nat -D DOCKER -i br-two -j RETURN #如果 br-one -> br-two,则为可选
  • 从链中删除DROP两座桥梁的规则DOCKER-ISOLATION-STAGE-2筛选桌子。

    • iptables -t filter -D DOCKER-ISOLATION-STAGE-2 -o br-one -j DROP
    • iptables -t filter -D DOCKER-ISOLATION-STAGE-2 -o br-two -j DROP

现在线条是开放的。

Docker 并不经常刷新其规则(至少在我测试的 19.03 版本中不是这样)。它似乎只在 docker 守护进程重新启动时重建规则集,而不是在您停止、启动或创建容器时重建规则集。您可以尝试将所有更改添加到服务重新启动中以保持其持久性。

相关内容