需要解决的问题

需要解决的问题

我的机器上运行着两个 docker 容器,其中非常严格的 nftables 配置处于活动状态。我想保持这种方式,但将外部对 docker 容器的访问列入白名单。

容器打开端口 80 和 6200。docker 服务在禁用 iptables 的情况下启动。

以下是当前的防火墙配置,包括我的尝试。icmpsshhttphttps已经开放。对于 docker,只需要 http 端口 80 和应用程序特定端口 6200。我尝试只允许访问 docker,以192.168.0.0/16尽可能地限制。

 table inet filter {
    chain input {
            type filter hook input priority 0; policy drop;
            iif lo accept
            iif eno2 icmp type echo-request accept
            iif eno2 ip 192.168.0.0/16 tcp dport 22 accept
            iif eno2 ip 192.168.0.0/16 tcp dport { http, https, 6200 } accept
    }

    chain forward {
            type filter hook forward priority 0; policy drop;
    }

    chain output {
            type filter hook output priority 0; policy drop;
    }
}

我尝试为docker0接口添加额外的规则,但没有成功。我怀疑我必须修改chain forward

答案1

虽然问题可能看起来很简单,但其实很简单。 Docker 的存在总是会给系统中处理网络的其他部分带来一些挑战。一次nftables未来会得到更广泛的采用,并被 Docker 直接使用,尤其是一旦 Docker 停止使用br_netfilter,事情可能会变得更简单。

如果您认为仍然值得使用nftables沿着 Docker,我在下面提出了一种方法,旨在让 Docker 处理其部分,并且不需要在其他防火墙规则中重复 Docker 设置,只要完成简单的更改(例如使用新的公开端口启动新容器)。

需要解决的问题

iptables仍然需要

目前(2021年)Docker仍在使用iptables并且只有iptables(也可以使用防火墙但仅与防火墙iptables后端。无论如何,我不会考虑这种情况)。因此目前还没有办法获得纯粹的nftables使用 Docker 时的系统。事实是iptables可以是iptables-legacyiptables-nft并不重要。

以下是一些相关摘录Docker 和 iptables对于这种情况有用:

Docker 安装两个名为和 的自定义iptables链,以及DOCKER-USERDOCKER它确保传入的数据包始终首先由这两个链检查。

Docker 的所有 iptables 规则都添加到DOCKER链中。请勿手动操作该链条。如果您需要添加在 Docker 规则之前加载的规则,请将它们添加到DOCKER-USER链中。这些规则在 Docker 自动创建任何规则之前应用。

吹毛求疵:实际上 Docker 是这样做的-A DOCKER-USER -j RETURN,所以应该在启动 docker 之前添加规则,或者更好:插入在所有情况下都有效。

规则添加到FORWARD链中-- 手动或通过另一个基于 iptables 的防火墙 --被评估这些链条。

Docker 还将链的策略设置FORWARDDROP.如果您的 Docker 主机还充当路由器,这将导致该路由器不再转发任何流量。

Docker 启用 IP 转发,但默认情况下会使用防火墙将其用于除自身以外的其他用途。

可以iptables在 Docker 引擎的配置文件中将密钥设置为 false /etc/docker/daemon.json,但此选项不适合大多数用户。不可能完全阻止 Docker 创建 iptables 规则,事后创建它们非常复杂,超出了这些说明的范围。 将 iptables 设置为 false 很可能会破坏 Docker 引擎的容器网络

无法避免拥有iptables

br_netfilter

而且,Docker还加载内核模块br_netfilter为了设置这个属性:

# sysctl net.bridge.bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1

所以桥接的帧(此处为 IPv4 类型框架暂时转换为 IPv4数据包) 被过滤iptables 并且经过nftables(即使没有明确记录,nftables就像iptables挂钩到 Netfilter 并且 Netfilter 将调用这些挂钩,无论它们来自iptables或者nftables)。

这个特性是与 Docker 交互时导致问题的主要原因。如果不了解这一点,人们会想知道为什么容器位于同一个内部桥接 LAN 中彼此之间无法沟通它们不再是由 Docker 处理,还是由 Docker 运行的其他东西(LXC、libvirt/QEMU...)处理。

这是Netfilter 和通用网络中的数据包流:

Netfilter 和通用网络中的数据包流

单链来自iptables或者nftables因此,可以通过两种不同的方式遍历 ip/inet 系列:从通常的路由路径(绿色网络层字段内的绿色框)以及桥接路径(蓝色链路层字段内的绿色框)。这文档还告诉:

桥接数据包绝不会进入第 1 层(链路层)之上的任何网络代码。因此,桥接的 IP 数据包/帧永远不会输入 IP 代码。

这样就可以保证数据包不会穿越两次同一条链子,让人松了口气。

之间的相互作用iptablesnftables

由于目标是使用nftables人们必须知道如何一起使用它们。

以下是我对此问题的回答:

总结一下:

  • iptablesnftables可以一起使用
  • nftables可以将其优先级调整为具有确定性的评估顺序iptablesnftables(对于本例:nftablesiptables
  • 无论何时何地发生这种情况,丢弃的数据包都会保持明确的丢弃状态
  • 一个已接受的数据包(将由iptables)在同一个钩子的下一个链中继续评估(这将是nftables' 链)。
  • 数据包标记可用于在之间传送消息iptablesnftables

以通用方式解决此问题的方法

处理桥梁路径

nftablesip/inet 系列中的规则应避免这样做任何事物在桥梁路径中。如果没有 Docker 激活,br_netfilter就根本不需要考虑这个问题。检测是否处于 ip/inet 系列的桥接路径中应该留给iptables以避免有nftables来处理这个问题并保持通用,无论是否安装了 Docker。这样做也更容易iptables比与nftables来自 ip/inet 系列,因为有特定的iptables -m physdev --physdev-is-bridged测试:

[!] --physdev-is-bridged

如果数据包正在桥接则匹配因此不会被路由。这仅在 FORWARD 和 POSTROUTING 链中有用。

br_netfilter请注意,如果 Docker 尚未完成此操作,则此匹配取决于并加载:需要解决由br_netfilter,引起的问题!br_netfilter

使用标记进行链接iptablesnftables

这个想法是使用一个标记来获取消息iptables传递给nftables,区分情况:

  • 规则评估发生在桥接路径而不是路由路径中

    总是接受这样的情况。

  • 数据包已被 Docker 接受

    可以添加更多限制,但大多数情况下接受这样的情况。

  • 数据包被 Docker 忽略

    使用正常nftables不必考虑 Docker 存在的规则。

  • 数据包因任何原因被 DROP-ediptables

    这是一个无意义的案例nftables不会看到这个数据包,并且对此没有任何必要或可以做的事情。

iptables

如果在 Docker 启动之前完成,请创建过滤器链DOCKER-USER

iptables -N DOCKER-USER

如果之后完成,Docker 将创建它。

添加一条规则,在链中的 Docker 评估之前标记数据包,该数据DOCKER包被桥接路径检测案例覆盖,并使用不同的标记(如前所述将它们插入此处,但对它们进行编号以保留自然顺序,这在这里很重要):

iptables -I DOCKER-USER 1 -j MARK --set-mark 0xd0cca5e
iptables -I DOCKER-USER 2 -m physdev --physdev-is-bridged -j MARK --set-mark 0x10ca1

0x10ca1 和 0xd0cca5e 是任意选择的值。

追加(在 Docker 运行之前或之后,效果是相同的,因为 Docker 总是插入其DOCKER在之前插入其链)一条最终规则,仅当它是暂定的 Docker 评估标记时才重置数据包的标记,添加最后一个ACCEPT添加要覆盖的Docker 的默认DROP策略设置FORWARD在链:想法是推迟进一步评估nftables用于与 Docker 无关的数据包。

iptables -A FORWARD -m mark --mark 0xd0cca5e -j MARK --set-mark 0
iptables -A FORWARD -j ACCEPT

nftables

将优先级值更改inet filter forward为略大于NF_IP_PRI_FILTER(0),例如10以确保nftables的前向链发生在iptables filter/FORWARD为了尊重这个年表。 OP 规则集中的基础链线应更改为:

    chain forward {
            type filter hook forward priority 0; policy drop;

到:

      chain forward {
              type filter hook forward priority 10; policy drop;

前面描述的 4 种情况可以在以下位置检测到:nftables通过检查包装上的标记。添加counter表达式以帮助调试。

  • 标记0x10ca1:桥接路径

    添加桥接路径透传规则:

    nft add rule inet filter forward meta mark 0x10ca1 counter accept
    
  • 标记 0xd0cca5e:Docker 案例

    • 创建一个常规/用户链来处理 Docker 情况并添加一条调用它的规则:

      nft add chain inet filter dockercase
      nft add rule inet filter forward meta mark 0xd0cca5e counter jump dockercase
      
    • 添加有关 Docker 的附加限制,但默认接受

      例如,限制来自以下地址的传入数据包:埃诺2仅当来自 192.168.0.0/16 内的私有地址时才接受接口:

      nft add rule inet filter dockercase iif eno2 ip saddr != 192.168.0.0/16 counter drop
      nft add rule inet filter dockercase counter accept
      
  • 无标记:与 Docker 无关的一般情况

    添加无需考虑 Docker 的存在即可完成的任何操作,包括什么都不包含并且具有默认值降低政策,否则可能会从平常开始ct state related,established accept

  • (无数据包:掉入iptables,非案例)

上面的例子变成:

...
    chain forward {
        type filter hook forward priority 10; policy drop;
        meta mark 0x10ca1 counter accept
        meta mark 0xd0cca5e counter jump dockercase
    }

    chain dockercase {
        iif eno2 ip saddr != 192.168.0.0/16 counter drop
        counter accept
    }
...

实现通用处理

端口 80 和 6200 不必出现在nftables不再有规则了。如果使用 Docker 命令添加需要公开新端口的新容器,则无需执行任何操作nftables: 由于标记,它已经得到处理。

添加更多链条

还是因为br_netfilter的影响,是否应该有任何其他基础nftables与属性链接hook forwardhook postrouting包含删除规则或更有用的是,更改规则(自然...)而不使用中描述的技巧上一个链接位于图 7b 下方,那么必须进行同样的安排:

  • 它的优先级值应该高于iptables' 等效链优先级

  • 这样的iptables等效链(除非filter/FORWARD已经在 中完成DOCKER-USER)应该接收:

    iptables -t foo -I BAR -m physdev --physdev-is-bridged -j MARK --set-mark 0x10ca1

    foo之间raw, mangle, 或nat以及BAR之间PREROUTINGPOSTROUTING视情况而定

  • 以及第一条规则nftables链应该再次:

    meta mark 0x10ca1 accept
    
  • 如果链的策略再次是,drop它可能应该再次包含使用 0xd0cca5e 标记的规则的用户/常规链跳转,如之前所做的那样。

对于hook prerouting,有关的文档--physdev-is-bridged告诉这可能不适用于PREROUTING:永远不要在那里使用默认的删除策略。无论如何,对于hook prerouting情况,还不能0xd0cca5e继承任何标记filter/FORWARD,但仅使用时也是如此iptables: PREROUTING 无法预见以后会发生什么。

如果你真的想在桥接级别做一些事情,只需使用nftables在网桥系列中,不要依赖从网桥路径调用的 ip/inet 系列的这种特殊情况,因为br_netfilter.

警告

现在使用标记来处理这个问题,同时使用标记来处理其他事情变得更加困难,但只要小心一点也不是不可能。例如,通过使用按位运算和带有这些标记的掩码。这可以在iptablesnftablesip rule当使用标记作为选择器时甚至接受掩码。


重要的额外必要调整

Docker 添加纳特使用 iptables 进行端口转发的规则'DNAT目标。最后所有暴露/发布的端口都是路由的到容器而不是被主机接收。这意味着他们将使用如上所示的iptablesfilter/FORWARD链以及(使用OP的规则集)nftables inet filter forward链并且不会使用INPUT/ input

还有缺少阻止正确连接的规则主持人

inet filter input

输入路径根本不会用于 Docker 的容器,除了docker-代理这种情况通常用于本地主机的访问,但 OP 已经接受 with iif lo accept,因此不必在此答案中进一步处理。关于 Docker 的任何内容都不应该出现在这里:对容器端口 80 和 6200 的引用变得毫无用处,应该删除。

然后,与 Docker 无关,输入链错过了状态规则。如果没有它,则从主机的输出返回流量(DNS 查询回复, 平回复,下载升级...)将失败。用这个:

    chain input {
            type filter hook input priority 0; policy drop;
            ct state related,established accept
            iif lo accept
            iif eno2 icmp type echo-request accept
            iif eno2 ip 192.168.0.0/16 tcp dport 22 accept
            iif eno2 ip 192.168.0.0/16 tcp dport 443 accept
    }

输入路径仍然需要额外的规则Docker 本身(而不是它的容器):可能需要规则来允许远程访问 Docker API(如果安全考虑允许)或各种功能,例如使用的 VxLAN码头工人群

inet filter output

同样,OPinet filter output链的丢弃策略会终止主机连接(DNS查询, 平要求或者无法启动下载等)。应该有policy accept,或者应该添加来自主机本身的所需传出流量的例外。链条应包括至少像这样的东西:

  chain output {
          type filter hook output priority 0; policy drop;
          ct state related,established accept
          oif lo accept
          udp dport { 53, 123 } accept
          tcp dport { 53, 80, 443 } accept
          icmp type echo-request accept
  }

容器的数据包不是由这些链评估的,而是由forward链评估的,并且不会受到限制。

IPv6

使用内网如果未正确启用 ICMPv6,则 IPv6 系列会阻止任何 IPv6 连接,因为 IPv6 不依赖(几乎从不防火墙)ARP,而是依赖 ICMPv6 来实现链路本地连接。要么使用ip家庭(并使用除筛选为了避免与桌子发生任何冲突iptables-nft)或正确处理 ICMPv6:全部接受或检查正确方向input所需的内容outputSLAAC新民主党:RS、RA、NS、NA, ...), ping ...处理。

相关内容