用 UFW 规则把 OpenVPN 网关限制在一个局域网内
让隧道客户端只够得着一个子网,别的一概不通。在 UFW 的早期放行之前插入守卫链、一条路由授权,以及证明边界的测试。
在打开防火墙文件之前先把权限定下来:已认证的客户端只允许向一个指定子网发起连接,网关必须拒绝其他任何转发路径,即使客户端自己装了额外路由也一样。这句话就是设计,下面的每条规则都是为了落实它。任何更宽的东西,比如整个私有地址空间、经隧道上公网、或者客户端之间的互访,都是另一套设计,需要单独做决定。规则也故意写得枯燥,因为聪明的防火墙就是那种后来谁都没法审计的防火墙。
先把拓扑和权限写下来
示例使用 Community 版OpenVPN 服务器,用户态 TUN 设备名为 tun0,客户端地址池为 10.77.0.0/24,另有一个挂在 192.168.60.0/24 上的子网 lan0。IPv4 转发已经可用,局域网也已经有回指地址池的路由,所以这里不依赖两者之间的地址转换。
这台主机的防火墙归 UFW 管,任何容器规则集或独立的 nftables 表都不得安装更早的放行路径。绕过过滤的流量卸载捷径会让守卫链变成摆设,所以先确认没有这类捷径在生效。OpenVPN 自带的 client-to-client 转发要保持关闭,因为 从不经过操作系统过滤器的客户端互访流量 无法用它来限制。对外监听和管理规则保持原样,因为这次改动管的是解密后的隧道流量,而不是替换防火墙。
把守卫放在 UFW 早期放行之前
只加一条路由授权再补一条拒绝,对严格的边界来说不够。系统自带的 before 规则会在任何用户规则被考虑之前,先放行已建立的流量和部分 ICMP,于是上周放行的连接仅因为还留着状态,就能待在新目的限制之外。编辑之前先备份 UFW 文件,并保持一条带外控制台开着。
在文件 *filter 的 /etc/ufw/before.rules 段里,把两条链声明在已有声明旁边,并且放在任何规则行之前。
:vpn-src-guard - [0:0]
:vpn-reply-guard - [0:0]紧接在声明之后、输入和转发放行之前,跳到这两条链里,定义它们放行什么。
-A ufw-before-input -i tun0 -j DROP
-A ufw-before-forward -i tun0 -j vpn-src-guard
-A ufw-before-forward -o tun0 -j vpn-reply-guard
-A vpn-src-guard -o lan0 -d 192.168.60.0/24 -s 10.77.0.0/24 -m conntrack --ctdir ORIGINAL -j RETURN
-A vpn-src-guard -j DROP
-A vpn-reply-guard -i lan0 -d 10.77.0.0/24 -s 192.168.60.0/24 -m conntrack --ctstate RELATED,ESTABLISHED --ctdir REPLY -j RETURN
-A vpn-reply-guard -j DROP保留这一段其余内容和它的 COMMIT 行。粘贴第二张过滤表或者删掉不相干的规则,正是防火墙改动变成故障的方式,而这个文件离成为唯一作数的副本只差一次重启。
读懂每条守卫放行什么、拒绝什么
第一条链只有在源地址属于地址池、目的地址是那一个子网、出口接口是局域网、并且报文沿原始方向行进时才继续处理。从隧道进来的其他一切都被丢弃,包括池外源地址和超出允许子网的任何目的地址。
客户端互访报文进出 tun0,所以在还没走到目的检查之前,它就已经过不了接口这一关。从局域网发往客户端的新连接同样过不了应答方向这一关,于是反向回调路径默认被关掉。
RETURN 这个目标让普通 UFW 策略继续起作用。这些链只做过滤,接下来加的路由授权才决定存活下来的报文是否被接受。正是这种分工,让你以后能在一个地方改许可的子网。如果守卫直接放行报文,将来每条规则都得写两遍。
授权子网并关掉侧门
先用预览看一遍路由授权,然后去掉预览标志把它应用下去:sudo ufw --dry-run route allow in on tun0 out on lan0 from 10.77.0.0/24 to 192.168.60.0/24。之后复查用户规则的顺序,因为前面的拒绝依然优先于后面的允许。当只应开放 某一台主机的 HTTPS 端口 时,整段子网的授权也是错的形状。
注意输入丢弃做了什么:隧道客户端够不着网关自身的任何服务,包括绑定在局域网地址上的 DNS 监听。要在那里提供 DNS,就得在丢弃之前专门开例外,并重写权限约定。如果 UFW 管 IPv6,还要在 tun0 里为 before6.rules 补上对应的丢弃,因为一份 IPv4 目的清单对 IPv6 报文什么也没说。
验证已加载的策略,而不是预览
先预览重载,在维护窗口里应用,然后读取正在运行的规则集。
sudo ufw --dry-run reload
sudo ufw show raw确认隧道守卫出现在默认的状态放行和 ICMP 放行之前。光看 ufw status 不会显示手工改过的 before 规则,所以运维人员常常以为规则丢了,其实它已经加载。要从上到下读原始清单,而不是搜某个片段。如果守卫出现在状态放行之后,那这道边界就没有文件里写的那样起作用。
然后测行为,而不是测文本。向允许的主机开一条新连接并观察应答,把一个无害请求送到子网之外的目的地并确认它不通,试一次客户端互访,试一个网关本机服务,再试一次没人要求的局域网到客户端连接。还要带一条改动之前就存在的越界流量,并且只清那一条,而不是把整台机器的跟踪表冲掉。
把限制一并记在规则旁边:这限制的只是转发,别的都不限制。它拦不住客户端用自己的上网连接,拦不住被允许的局域网主机继续往外转发,也替代不了许可服务内部自己的认证。