运行 WireGuard 的枢纽可以在不为每个组单独设置接口或路由表的情况下隔离对等组。真正干活的是两层机制,而把这两层混为一谈,正是一条策略看起来正确却毫无作用的常见原因。一层决定谁可以主张某个源地址。另一层决定这个数据包被允许去往哪里。

两项工作,分开处理

隧道层回答一个狭窄的问题:这个对等方的密钥是否授权了这个内层源地址。转发层回答一个更宽泛的问题:这个数据包是否被允许发往那个目的地。两者都不回答对方的问题,任何一层单独也不构成完整的访问策略。

正因为这样分开,一个对等方才可能既通过认证、地址也正确,却依然到不了本不该到达的主机。反过来也一样:一条放行一切的防火墙规则,并不能证明谁可以主张哪个地址。

把两层放在一起设计,然后两层都测。在一层上正确、在另一层上未经检验的配置,会按默认值的设定开放或关闭地失败。

只给每个对等方它自己的地址

多主机的对等方会诱使你在服务端把整个组网段都授出去。那份授权就是对源地址的主张,于是一个被攻破或配置错误的客户端可以冒用网段里的任何地址,而隧道会照单全收。为每个对等方只分配一个 /32 作为它自己的内层地址。组网段只在客户端上设置,那里描述的是 该客户端可以到达哪些目的地,而不是它可以使用哪些源地址。

这样防火墙匹配的才是可信的源。为组网段写的规则仍然是有用的分组方式,只有当一个单独的对等方无法主张属于其他成员的地址时,它才真正有意义。

写好把组隔开的转发策略

这里的相关流量从同一个隧道接口进出,因此走的是转发路径而不是主机的输入路径。规则应当放进拥有主机的那个防火墙管理器的既有转发链里,而且它们只是片段,不是完整的规则集。

iifname "wg0" oifname "wg0" ip saddr 10.66.10.0/24 ip daddr 10.66.10.0/24 accept
iifname "wg0" oifname "wg0" ip saddr 10.66.20.0/24 ip daddr 10.66.20.0/24 accept
iifname "wg0" oifname "wg0" drop

前两条规则允许组内流量通过。最后一条拦下穿过该接口的其余一切,组与组之间的隔离正是由此产生。如果需要,为另一个地址族补上明确的规则,因为针对 IPv4 写的 drop 既不授权也不描述 IPv6 的行为。

顺序决定策略是否真的生效

规则的顺序比片段本身更重要。一条接受已建立连接的通用规则如果排在这些条目之前,旧的跨组流就会继续存在,隔离便只对新连接生效。重新加载之前,先想清楚你要的是这两种结果中的哪一种。

在同一条链路上跨链时,接受判定并不总是最终结果。如果后面还跑着另一条基础链,实际生效的策略是两者的组合,所以要去看计数器,而不是你改过的文件。计数器是成本最低的证据。没有包计数的规则根本没匹配上;而一条规则有匹配、后面那条链却接受了,就说明 drop 放错了位置。

要测矩阵,而不是数文件

建一张小的期望结果矩阵,逐格检查。A 组到 A 组应当通,B 组到 B 组应当通,A 组到 B 组不应当通,两个方向都要测,如果你支持两个地址族就都要测。

nft list ruleset
wg show wg0 allowed-ips
ping -c 2 10.66.10.5
conntrack -L -s 10.66.20.7 2>/dev/null | head

前两条显示配置了什么。从错误组成员发出的 ping 显示实际发生了什么。conntrack 列表显示旧连接是否仍被接受,而隔离改动悄悄不生效,往往就出在这里。

枢纽上的主机服务和它背后 LAN 的访问是两条独立路径,各自有各自的策略。记下矩阵覆盖了其中哪些,因为只测对等方之间流量的测试,对两者都说明不了什么。

相关文章