VPS 防火墙:只放行 VPN 端口,然后再去证明它
必须有一个端口保持公网开放,否则任何客户端都连不上。其余一律拒绝,把转发与输入分开处理,并留下一条事先测试过的恢复路径。
只放行 VPN 端口的防火墙不是一条规则,而是三个恰好涉及同一个端口号的独立判断。输入链决定什么能到达本机进程,转发链决定什么能代表隧道客户端穿过主机,NAT 表则在出站时改写源地址。其中任何一个弄错,结果往往不是干脆的中断,而是主机能接受客户端却什么都加载不出来,或者表面封闭、直到有人去读链的先后顺序。
唯一不能关闭的那个端口
VPN 监听器必须接受来自无法预测的地址的连接,因此按定义它就是一项公共服务。整个设计就围绕这唯一的例外展开:OpenVPN 用一个 UDP 端口,WireGuard 用一个 UDP 端口,SSL VPN 用一个 TCP 端口。其余一切都属于隧道之后,也就是说应用服务应绑定到隧道地址或 loopback,而不是绑定到所有网卡。
但绑定并不等于强制。绑定到 0.0.0.0 的进程只有在某条规则放行时才可达,而默认策略是未被任何规则匹配的流量的兜底,不是支配该端口的规则。放在 drop 上面的 accept 依然是 accept;把服务挪离公网地址是一项独立的改进,而不是链的替代品。
输入、转发与 NAT 回答的是不同问题
输入流量以主机本身为目的,所以 VPN 监听器和管理通道都住在这里。转发流量要穿过主机,隧道客户端除了需要它,还需要 net.ipv4.ip_forward=1。经典的一半坏掉的配置只放行出站方向、忘了回程方向,于是客户端完成握手却什么都加载不出来;针对已建立与相关连接的 conntrack 规则正好覆盖这个方向。
NAT 是第三个问题。没有 postrouting 改写,被转发的数据包带着隧道私网源地址出去,并在上游被丢弃,因为主机之外的网络都不知道如何把地址池路由回去。这条规则的源前缀必须匹配 VPN 地址池,而不是主机自己的地址;指定客户端子网的转发规则同样如此。
IPv6 是第二套策略,不是事后补充。一台主机如果 IPv4 链很严格,ip6tables 却是开放策略,那就什么都没关上;服务仍然可以通过另一个协议族访问。要么用 ip6tables 把规则集镜像过去,要么有意在公网网卡上关闭 IPv6,并写下你选了哪一种。
一遍就能读完的基准
从拒绝开始,然后只添加主机真正需要的东西。先 loopback 与回包,再管理通道,再隧道监听器,最后是给客户端的转发策略:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p udp --dport 1194 -m conntrack --ctstate NEW -j ACCEPT
sysctl -w net.ipv4.ip_forward=1
iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.0/24 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE网卡名、端口和地址池都是占位符;请从服务器上读,而不是照抄。保存结果和写出结果同样重要,因为只活在内存里的规则集会在下次重启时消失,并悄悄把主机重新打开。用发行版自带的机制让它持久化,并在机器之外留一份副本,以备哪天机器起不来。
关掉最后一个端口而不丢掉主机
设计里有一个陷阱。如果 SSH 只存在于隧道之中,VPN 服务一坏就会把 SSH 一起带走,而唯一的回头路可能是你从未打开过的控制台。在关掉最后一个公网端口之前,先确认那个控制台存在、而且你确实能用;否则就保留一条不依赖隧道的窄管理通道:从固定地址 SSH、只用密钥、关闭密码认证。
请从已经建立的会话里应用改动,而不是从你即将过滤的那条连接。安排一次回滚,让错误自动过期而不是永久生效,例如用一个 systemd-run --on-active=10min 任务恢复上一份规则集。在测试第一条会话时,保持第二条 SSH 会话开着。然后验证回滚任务是否真的触发,因为没测过的恢复等于没有恢复。
验证路径,而不是脚本
要读已加载的规则,而不是本该加载它们的文件。从一台被授权的外部主机确认 只有预期端口在应答;从隧道内部确认你想发布的服务可达。默认拒绝的策略如果连隧道流量一起挡住,看起来和正常封锁一模一样,直到有人真去用它。
iptables -S
iptables -t nat -S
ss -lntup
ip -4 addr show把允许的端口、客户端地址池、隧道网卡、管理来源和控制台流程记在配置旁边。下次改动地址池或网卡名时,过滤规则和 NAT 规则必须同步跟上,否则主机会在没人注意的情况下退回半开状态。
有一种习惯比规则集更值钱:每次改动之后,用一次客户端请求证明转发路径,用第二条会话证明恢复路径。没用过的封锁只是假设。