你期待一个隧道接口,找了一圈,防火墙里却没有可匹配的东西。对基于策略的网关来说这是正常的:加密交换从物理接口到达,而随后被转发的已解密内层数据包,同样被观测为从那同一个接口到达。没有 tun0,是因为没有任何东西创建它。

内层请求仍然是一个独立的数据包,有自己的源、目的和服务,而内核知道它来自哪个安全关联。防火墙规则能匹配的正是这份知识。

两个数据包,一个接口

设想入方向。一个封装于 ESP 或 UDP 的数据包到达,内核找到匹配的策略,解密后把明文数据包交给转发路径。只看接口名的规则无法把它与从同一根线到达的任何其他流量区分开。

区分它的是附着在数据包上的入方向安全路径上下文。nftables 将其暴露为 meta ipsec exists,当数据包经由 IPsec 安全路径到达时为真。把它与客户端池、目的地和服务组合起来,规则就足够具体而安全。

省略上下文是常见错误,而且这是安全错误而非外观错误。接受来自客户端池的任何内容的规则,同样乐于接受来自本地网络的伪造数据包,因为源地址本身并不能证明什么。安全路径上下文是让源变得有意义的最廉价方式。从一开始就带着它写规则,而不是审计之后再补。

编写转发规则

假设有一个已认证的 IPv4 远程访问服务,客户端位于 10.90.0.0/24,另有一个获准的目的地位于 203.0.113.40。该流的规则片段如下。

meta ipsec exists ip saddr 10.90.0.0/24 \
  ip daddr 203.0.113.40 tcp dport 443 \
  oifname "eth0" counter accept

地址和接口都是需要替换的示例。把审查过的等价规则放在转发策略中,位于那条否则会拒绝该流的规则之前,并且不要为了安装它而清空现有规则集;网关不是洁净室实验。

oifname 这一项值得保留。它记录了出口决策,并防止规则在你并不打算的方向上匹配,这在有多条通往互联网路径的网关上很重要。在只有一条上行链路的测试机上,这一项显得多余,而这正是它在机器获得第二条上行链路之前被删掉的原因。

外部交换的权限是分开的。网关的入路径需要它实际使用的 IKE 和 ESP 或 UDP 封装端口,而那些权限对内层的已解密数据包没有任何许可。把两者混为一谈,就是一条正常工作的隧道最终配上了一个转发一切的防火墙。

入方向上下文不是出方向上下文

同一族表达式用 rt ipsec 覆盖另一个方向,它描述一个数据包将使用的出方向安全关联。它回答的是另一个问题:不是这个数据包从哪来,而是它在下一段是否会被保护。

这个区别在同时终结一条隧道并向另一条转发的网关上很重要。数据包可以解密到达,却仍然必须加密离开,而只为入方向上下文写的放行,对出方向的要求只字未提。如果策略要求那种保护,规则集应当把它表达出来,而不是假设路由会处理。

把两个问题放在不同的规则里,读者才能看出正在执行哪一条。一条同时检查两者的聪明规则更难审查,当其中一半不再匹配时也远更难排查。两条带计数器的规则还会告诉你哪项要求正在失败,而一条合并规则永远不会做到。

NAT 不是防火墙决定

从私有客户端池发往互联网的流量通常需要源地址转换,以便响应找到回程。但为另一条出方向 IPsec 策略选中的流量必须保留其必需的豁免,而不考虑下一次保护决定就给每个私有源做伪装,会破坏这一点。

NAT 链里的放行也不是普遍许可。NAT 和过滤的职责不同,而 NAT 链并不会检查已建立流的每一个后续数据包,这正是把它当作安全依据会产生“测试时可用、负载下失败”的规则的原因。

反过来也成立,而且让人意外:一条过滤基础链里某处的放行,对另一条适用链里后续的丢弃毫无分量。决定结果的是顺序和链的范围,而不是文件里有没有一条放行。

验证确切的内层服务

用真实客户端测试你授权的那个目的地和端口,并确认响应能回来。然后再测一些你没有授权的东西,确认它过不去,因为过宽的规则通过第一项测试的程度和正确规则一样好。从未离开客户端的请求也不是防火墙结果,所以要读计数器,看数据包到底有没有被评估。两次测试加一次计数器读取,比一下午读规则集告诉你的更多。

nftables 参考记录了 meta ipsec、rt ipsec 以及按方向区分的表达式,对照已安装版本检查语法的地方就在那里。另外要把范围说清楚:这是 IPv4 示例,对 IPv6 只字未提,也没有把任意公网端口转发变成 VPN 服务的一部分。

相关文章