阻止 VPN 客户端在隧道中互相访问
访问服务是路由,访问另一个客户端是策略。找到决定对等流量的转发点,然后从客户端证明这条拒绝规则。
两个客户端都能打开跳板机,却仍然没有任何理由互相访问。访问服务是一项转发决策,而访问对等端是一项策略决策,大多数 VPN 栈把两者放在不同的对象里。你为前者配置的任何东西都不会强制执行后者。因此任务不是加一条规则,而是找到这台服务器上决定对等流量的对象,在那里写下拒绝规则,并从客户端证明它。
通往共享服务的路由不是对等端之间的许可
通告客户端子网只是告诉对等端如何把数据包放到通往另一个客户端地址的线路上。这个数据包是否被送达由之后转发它的东西决定。在 Linux 网关上那是 FORWARD 链;在用户空间守护进程内则是隧道自己的会话表。路由只回答可达性,完全不触碰许可。
所以只读取路由的隔离检查什么也证明不了。/24 可以推送给每个客户端,而每一个对等数据包都在转发钩子上被丢弃。危险的情况恰恰相反:没有显式规则,默认放行,隔离之所以存在只是因为没有人测试过它。
打开终端之前,把需求写成一行。能到服务块和跳板机,不能到客户端块内的其他任何东西。
弄清楚到底由谁转发对等数据包
服务器的两种行为从客户端一侧看完全一样,却需要不同的规则。要么内核再次把数据包从隧道接口转发出去,要么守护进程在会话之间复制它,内核根本看不到。
WireGuard 属于第一种:它没有对等端之间的开关,因此从 10.8.0.4 到 10.8.0.5 的数据包到达 wg0,命中转发钩子,并在 AllowedIPs 覆盖两个地址时再次从 wg0 路由出去。OpenVPN 属于第二种,只要服务器配置里设置了 client-to-client,因为该指令在用户空间把数据包在会话之间转交。写任何东西之前,先检查已加载配置里有没有这条指令。一条永远看不到流量的内核规则和一条生效的规则无法区分,部署就是这样在完全没有隔离的情况下收尾的。
要读生效的配置,而不是你假定已加载的文件,因为发行版软件包会带来自己的包含文件。如果由守护进程转发,拒绝规则属于它自己的按客户端控制,而主机防火墙只过滤发往主机的流量。如果由内核转发,转发钩子是唯一重要的地方。
路由和地址转换的部署还需要一项检查:远端客户端看到的源地址可能不是你以为的那个。那样匹配客户端子网的规则就什么也匹配不到。写匹配条件之前,先记下经过转换后仍然保留的地址,并优先匹配接口对,而不是猜 NAT 地址。
给每个地址一个能单独吊销的身份
两台设备共用一个凭据的那一刻,隔离就崩溃了。绑定到该身份的规则无法把它们分开,吊销一台丢失的笔记本就等于吊销整组。按设备发放证书或按对等端发放密钥,可以让吊销保持局部、策略保持可读,因为每个地址恰好对应一个人或一台机器。设计做不到时,就在运行手册里说明,而不要把规则描述成细粒度的。
地址分配必须与策略一致。为服务保留一个地址块,为客户端保留一个地址块,并从你控制的池子里分配客户端地址,而不是从先到先得的范围里分配。如果服务器做转换或桥接,就把映射表放在配置旁边,因为下一个读者正是需要这张表来重新推导规则。
把拒绝放在数据包穿越隧道的地方
在转发 WireGuard 流量的网关上,两条规则覆盖这种情况:先允许服务块,再丢弃客户端块的其余部分。接口对和地址同样重要,因为只匹配目的地址也会抓到来自局域网流量。按这个顺序加载,让允许先被评估。
用 nftables 时,这一对进入已有的 inet filter 表,先允许,后丢弃。两个匹配都同时固定接口对和地址。
nft add rule inet filter forward iifname "wg0" oifname "wg0" ip saddr 10.8.0.0/24 ip daddr 10.8.2.0/24 accept
nft add rule inet filter forward iifname "wg0" oifname "wg0" ip saddr 10.8.0.0/24 ip daddr 10.8.0.0/24 drop
加载后把链读回来,而不是相信退出状态,并确认两条规则都在你想要的顺序上。为 IPv6 池添加匹配的 ip6 规则,否则在双栈隧道上 v4 那一对只是装饰。如果服务器已有宽松的转发策略,就追加丢弃,而不是整体替换这条链。
在 OpenVPN 上启用 client-to-client 时,这些转发规则永远匹配不到对等流量,拒绝必须放在守护进程自己的控制里。为主机流量的内核规则可以保留,但不要把它们算作隔离控制。
从客户端而不是从服务器证明拒绝
从网关测试什么也证明不了,因为本地产生的流量走输出路径,永远不会经过转发钩子。用两个真实客户端:一个应该能到服务,一个应该被拒绝。先跑允许的情况,免得把坏掉的隧道当成生效的策略。
从被允许的客户端确认服务在自己的端口上应答。从同一个客户端尝试对等端地址,期待静默丢弃而不是拒绝:
ping -c 3 -W 2 10.8.0.5
nc -z -w 2 10.8.0.5 22
nft list chain inet filter forward
iptables -vnL FORWARD
ping -6 -c 3 -W 2 fd00:8::5
被丢弃的数据包和离线对等端看起来都像超时,所以下结论之前先看计数器。确认对等端明显在线时丢弃规则的计数器在增长。在 IPv6 上重复一遍,因为缺少 ip6 规则会让 v4 的拒绝看起来有效。把接受和拒绝的结果连同日期记入运行手册,因为下一次迁移会重新推导这条规则。