nftables 白名单 docker

flo*_*wit 6 docker nftables

我的机器上运行着两个 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

A.B*_*A.B 32

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

\n

如果您认为仍然值得与 Docker 一起使用nftables,我在下面介绍了一种方法,旨在让 Docker 处理其部分,并且不需要在其他防火墙规则发生变化时复制 Docker 设置,就像使用新容器启动新容器一样简单暴露端口,完成。

\n

需要解决的问题

\n

iptables仍然需要

\n

目前(2021)Docker 仍然使用iptables并且仅使用iptables(它也可以使用firewalld,但只能使用带有iptables后端的firewalld。无论如何,我不考虑这种情况)。因此,目前使用 Docker 时无法拥有纯粹的nftables系统。iptables可能重要也可能不重要iptables-legacyiptables-nft

\n

以下是Docker 和 iptables中的一些相关摘录,对本例有用:

\n
\n

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

\n
\n
\n

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

\n
\n

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

\n
\n

FORWARD添加到链中的规则(手动或通过另一个基于 iptables 的防火墙)在这些链之后进行评估。

\n
\n
\n

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

\n
\n

Docker 启用 IP 转发,但默认情况下会对其进行防火墙以用于其他用途。

\n
\n

可以iptables在位于 的 Docker 引擎\xe2\x80\x99s\n配置文件中将密钥设置为 false /etc/docker/daemon.json,但此选项\n不适合大多数用户。完全阻止\nDocker 创建 iptables 规则是不可能的,事后创建它们\n极其复杂,超出了这些说明的范围。\n将 iptables 设置为 false 很可能会破坏容器\n网络连接码头工人引擎

\n
\n

无法避免iptables

\n

br_netfilter

\n

此外,Docker 还加载内核模块br_netfilter以设置此属性:

\n
# sysctl net.bridge.bridge-nf-call-iptables\nnet.bridge.bridge-nf-call-iptables = 1\n
Run Code Online (Sandbox Code Playgroud)\n

因此,桥接帧(这里的 IPv4 类型暂时转换为 IPv4数据包)由iptables nftables过滤(即使没有明确记录,nftables就像iptables挂钩到 Netfilter 一样,Netfilter 会调用这些挂钩,无论它们是来自iptablesnftables)。

\n

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

\n

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

\n

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

\n

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

\n
\n

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

\n
\n

因此,可以保证数据包不会在同一条链上遍历两次,这让人松了口气。

\n

iptablesnftables之间的交互

\n

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

\n

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

\n\n

总结一下:

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

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

\n

处理桥梁路径

\n

ip/inet 系列中的nftables规则应避免在桥接路径中执行任何操作。如果没有 Docker 激活,br_netfilter就根本不需要考虑这个问题。检测是否处于 ip/inet 系列的桥接路径中应留给iptables,以避免让nftables处理此问题并保持通用性,无论是否安装 Docker。使用iptables比使用ip/inet 系列中的nftables更容易做到这一点,因为有特定的iptables -m physdev --physdev-is-bridged测试:

\n
\n

[!] --physdev-is-bridged

\n

如果数据包正在桥接,因此未进行路由,则匹配。这仅在 FORWARD 和 POSTROUTING 链中有用。

\n
\n

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

\n

使用标记链接iptablesnftables

\n

这个想法是使用标记将来自iptables 的消息传递到nftables,以区分情况:

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

    \n

    总是接受这样的情况。

    \n
  • \n
  • 数据包已被 Docker 接受

    \n

    可以添加进一步的限制,但大多数都接受这种情况。

    \n
  • \n
  • 数据包被 Docker 忽略

    \n

    使用普通的nftables规则,不必考虑 Docker 的存在。

    \n
  • \n
  • 数据包因任何原因在iptables中被 DROP-ed

    \n

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

    \n
  • \n
\n

iptables

\n

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

\n
iptables -N DOCKER-USER\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
iptables -I DOCKER-USER 1 -j MARK --set-mark 0xd0cca5e\niptables -I DOCKER-USER 2 -m physdev --physdev-is-bridged -j MARK --set-mark 0x10ca1\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
iptables -A FORWARD -m mark --mark 0xd0cca5e -j MARK --set-mark 0\niptables -A FORWARD -j ACCEPT\n
Run Code Online (Sandbox Code Playgroud)\n

nftables

\n

将优先级值更改inet filter forward为略大于NF_IP_PRI_FILTER(0) 的值,例如 10,以确保nftables的前向链发生在iptables filter/FORWARD之后,以遵守此时间顺序。OP 规则集中的基本链线应更改为:

\n
\n
    chain forward {\n            type filter hook forward priority 0; policy drop;\n
Run Code Online (Sandbox Code Playgroud)\n
\n

到:

\n
      chain forward {\n              type filter hook forward priority 10; policy drop;\n
Run Code Online (Sandbox Code Playgroud)\n

通过检查数据包上的标记,可以在nftables中检测到前面描述的 4 种情况。添加counter表达式以帮助调试。

\n
    \n
  • 标记0x10ca1:桥接路径

    \n

    添加桥接路径透传规则:

    \n
    nft add rule inet filter forward meta mark 0x10ca1 counter accept\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
  • 标记 0xd0cca5e:Docker 案例

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

      \n
      nft add chain inet filter dockercase\nnft add rule inet filter forward meta mark 0xd0cca5e counter jump dockercase\n
      Run Code Online (Sandbox Code Playgroud)\n
    • \n
    • 添加有关 Docker 的附加限制,但默认接受

      \n

      例如,限制来自eno2接口的传入数据包仅在来自 192.168.0.0/16 内的私有地址时才被接受:

      \n
      nft add rule inet filter dockercase iif eno2 ip saddr != 192.168.0.0/16 counter drop\nnft add rule inet filter dockercase counter accept\n
      Run Code Online (Sandbox Code Playgroud)\n
    • \n
    \n
  • \n
  • 无标记:与 Docker 无关的一般情况

    \n

    添加无需考虑 Docker 的存在即可完成的任何操作,包括什么都不包含并具有默认的删除策略,否则可能会从通常的方式开始ct state related,established accept

    \n
  • \n
  • (无数据包:在iptables中丢弃,非大小写)

    \n
  • \n
\n

上面的例子变成:

\n
...\n    chain forward {\n        type filter hook forward priority 10; policy drop;\n        meta mark 0x10ca1 counter accept\n        meta mark 0xd0cca5e counter jump dockercase\n    }\n\n    chain dockercase {\n        iif eno2 ip saddr != 192.168.0.0/16 counter drop\n        counter accept\n    }\n...\n
Run Code Online (Sandbox Code Playgroud)\n

实现通用处理

\n

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

\n

添加更多链条

\n

仍然由于br_netfilter\ 的影响,如果任何其他基础nftables链具有该属性hook forwardhook postrouting包含删除规则或更有用,更改规则(nat ...)而不使用图 7b 下面的上一个链接中描述的技巧,则相同类型的安排必须要做的:

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

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

    \n

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

    \n

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

    \n
  • \n
  • nftables链的第一条规则应该是:

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

    \n
  • \n
\n

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

\n

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

\n

警告

\n

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

\n
\n

重要的额外必要调整

\n

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

\n

还缺少阻止主机正确连接的规则

\n

inet filter input

\n

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

\n

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

\n
    chain input {\n            type filter hook input priority 0; policy drop;\n            ct state related,established accept\n            iif lo accept\n            iif eno2 icmp type echo-request accept\n            iif eno2 ip 192.168.0.0/16 tcp dport 22 accept\n            iif eno2 ip 192.168.0.0/16 tcp dport 443 accept\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

输入路径仍然需要Docker 本身(而不是其容器)的附加规则:可能需要规则来允许远程访问 Docker API(如果安全考虑允许)或Docker swarm使用的 VxLAN 等各种功能使用的 VxLAN 。

\n

inet filter output

\n

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

\n
  chain output {\n          type filter hook output priority 0; policy drop;\n          ct state related,established accept\n          oif lo accept\n          udp dport { 53, 123 } accept\n          tcp dport { 53, 80, 443 } accept\n          icmp type echo-request accept\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

IPv6

\n

在未正确启用 ICMPv6 的情况下使用inet系列会阻止任何 IPv6 连接,因为 IPv6 不依赖(几乎从不防火墙)ARP,而是依赖 ICMPv6 来实现链路本地连接。要么使用系列(并使用表的过滤器ip以外的其他名称以避免与iptables-nft发生任何冲突),要么正确处理 ICMPv6:全部接受或检查正确的SLAAC中和方向上所需的内容(NDPRS, RA、NS、NA ……)、ping……处理。inputoutput

\n

  • @lonix只要您会看到单词“iptables”(或 IPTables )而不是单词“nftables”,并且只要您还会在此文件中看到单词“br_netfilter”,这就是真的:https: //github.com/moby/libnetwork/blob/master/drivers/bridge/bridge.go(在此评论中,我指向 master 分支,而不是我的答案中的特定提交版本)。 (2认同)