替换 iptables 时,学习大括号该写在哪里只占风险的一小部分。真正危险的是,等到迁移后才发现,已经转换的规则从来没有涵盖整套防火墙。
一台运行多年的主机上,规则可以来自启动脚本、发行版服务、容器引擎、VPN,也可以由管理员直接在命令行写入。有些命令已经借助兼容层调用 nf_tables 内核后端,表面仍沿用熟悉的 iptables 语法。因此,生成的 nftables 文件即使语法正确,运行结果仍会出错:它会重复另一套管理程序的工作,漏掉仅在运行时出现的规则,也会在下一次服务重新加载时被覆盖。
安全迁移包含两个有意分开的目标。第一步是在唯一且名称明确的所有者之下保持行为一致。等到切换过程可以观察,也能回退,团队再启用原生 nftables 的能力,例如 IPv4/IPv6 共用链、集合、映射以及规则集的原子更新。转换是一座桥,精简则属于后续工程。[1][5]
题图拍摄于 2016 年,Pablo Neira Ayuso 正在一场研习会上讲解 nftables 实践。这样的现场很贴合本文:防火墙迁移是一项动手处理系统的工作,决定性证据来自内核实际加载的规则,以及真正穿过这些规则的数据包。[7][8]
转换语法之前,先冻结所有权
先建立一份所有权台账,新配置文件留到后面。记录 iptables -V 和 ip6tables -V,让清单明确写出所用后端,避免依赖假设。保存 iptables-save、ip6tables-save 和 nft list ruleset 的输出;如果主机处于混用状态或后端不明,还要分别保存 iptables-legacy-save / ip6tables-legacy-save 与 iptables-nft-save / ip6tables-nft-save 所显示的内容。如果本次也纳入 ipset、ebtables 和 arptables,要一并记录它们的状态。[1][6] 随后找出所有能够改变最终结果的进程,检查服务单元、网络上线钩子、配置管理角色、容器或集群网络、VPN 生命周期脚本,以及人工维护的恢复脚本。逐项记录重启后、网络接口变化后、应用部署后由哪个组件掌握权威状态。
这一步可以防止最常见的类别错误:把后端当成所有者。一台主机可以运行 firewalld,内核则用 nftables 保存由此生成的策略;若再让一套手写的 nftables.service 规则集并行管理相同钩子,所有权就会发生重叠。Debian 当前的说明把这些层次讲得很清楚:nftables 是其默认防火墙框架;已安装的 iptables 命令通过 iptables-nft 使用 nf_tables 后端;希望由系统代管底层脚本的管理员可以考虑集成于系统的 firewalld 包装层。文档同时建议,只有理解其中相互作用的运维人员才考虑混用 nftables 与旧式规则管理界面。[6]
同样的道理也适用于这两项服务之外的组件。若编排系统负责 pod 转发,或 VPN 负责标记和路由链,可以保留原有所有权,并设计一个写入文档的集成点,也可以有计划地转移所有权。若只把当前运行时输出复制到静态文件,这项依赖依然存在。
切换开始之前,台账应当回答四个问题:
- 哪个程序创建每一张表或每一条链?
- 哪个事件会重新加载或修改它?
- 哪些流量依赖它,其中是否包括 IPv6 与转发流量?
- 替换失败时,由哪一种单一方式恢复访问?
只要答案仍有缺口,迁移就还没进入处理语法的阶段。
把转换稿当作行为一致的脚手架
Netfilter 项目提供两条实用路线。iptables-translate 和 ip6tables-translate 用于转换单条命令,iptables-restore-translate 和 ip6tables-restore-translate 则把保存的规则集转换为 nft -f 能够读取的输入。[1] 第一版原生草稿适合由这些工具建立,因为相比凭记忆手工改写,它们能保留更多顺序和策略细节。
工具转换只生成草稿,行为仍须验证。迁移指南展示了一种情况:遇到不受支持的转换时,结果会成为注释行,无法作为有效策略执行。另一方面,当前 nft 手册警告,xt 语句表明该规则由 xtables 兼容界面创建,nftables 自身无法恢复这类语句。[1][2] 因而,每一条转换注释、兼容语句、扩展、自定义 target 和警告都对应一项测试义务,需要逐一核验。
第一版草稿应当有意保持朴素。保留基础链钩子、优先级、默认策略、连接跟踪条件、NAT 方向、接口匹配、日志速率限制和规则顺序。即使 IPv4 与 IPv6 两份文件重复,也要保留各自的行为。若一次迁移同时处理转换、去重、重命名和策略变更,一旦某类流量中断,便很难找到清楚的解释。
还要区分 iptables-nft 和 iptables-legacy。前者用 xtables 语法操作 nftables 后端,后者使用较早的 x_tables 子系统。Netfilter 迁移指南提醒,旧式子系统与 nftables 子系统同时运行会带来意外结果。[1] 兼容路线可以减少过渡期内应用程序的中断,但这项依赖要有明确名称和期限。否则,两套命令语言会继续描述同一份内核策略,运维人员也始终无法确认哪一个界面具有权威地位。
让文件同时成为事务与回退工件
原生 nftables 适合以文件为单位操作。命令 nft -c -f candidate.nft 只检查文件中的命令,不会应用它们;nft -f candidate.nft 则提交整串命令并加以应用。[2] 就整套规则集而言,nftables 可以原子加载替换规则,数据包不会在命令执行期间遇到一套只建到一半的防火墙。[3]
能否成为完整替换,取决于文件内容。一个只含 add 命令的文件,会在当前状态之上不断累加项目。nftables 操作指南给出了覆盖整个命名空间的备份方式:写入 flush ruleset,附上列出的规则集,再用 nft -f 原子加载文件。[3] 只有当该工件独占整个命名空间中的 nftables 策略时,才能使用这种全局清空操作;flush ruleset 会删除每一张 nftables 表和每一个项目,其中也包括其他管理程序写入的内容。如果所有权经过设计而由多方共享,事务应当只删除和重建该工件拥有的表或协议族。
可以据此安排一次受约束的切换:
- 用
nft -c -f candidate.nft校验候选文件。 - 先在网络命名空间、一次性克隆环境或承担相同职责的金丝雀主机上应用。
- 保留独立于待修改规则的控制台或带外管理通道。
- 预先安排自动回退,再由独立健康检查在确认正常后取消。
- 在变更记录中保存切换前的转储、原生候选文件、软件包版本,以及确切的应用与恢复命令。如果旧策略含有 legacy 或
xt兼容状态,回退动作应交给创建它的服务或前端,原生nft不承担恢复工作。[2]
原子性保护的是两套规则集之间的转换过程;新规则集是否准确表达了预期策略,仍要另行验证。即使 SSH 锁定完全以原子方式发生,它仍是一场锁定事故。
测试以行为为准,文字相似放在一边
行为一致测试套件应按流量编写,而不是按链编写。最低限度要覆盖本地入站、本地出站、路由转发、已建立连接、新连接、各个实际使用方向上的 NAT、回环流量、管理访问、DNS 和两个 IP 协议族。标记、策略路由、隧道、网桥或容器网络等服务也要加入专门用例。每条获准流量旁边都应设置一条相近的拒绝流量,防止意外放宽的接受规则被误认为测试成功。
计数器很有用,但 nftables 不会为每条规则隐式附加计数器;计数器属于显式的有状态项目或语句。[5] 在观察期内,把它们放到转换后的决策点上。向新旧策略送入相同测试流量,比较各项策略结果对应的增量,同时要考虑后台流量存在后,计数器无法逐字节吻合。
遇到难以确认的链路,nftables 还有一种效力更强的工具。可以用范围狭窄的规则为选定数据包设置 meta nftrace,再由 nft monitor trace 按跟踪标识符报告它们走过的链路。[7] 跟踪范围应限定在测试地址、端口或接口。若无差别地打开跟踪,证据会迅速泛滥,反而更难解读。
进入正式环境的门槛取决于行为:金丝雀主机在重启后仍然工作;预定的所有者能重建同一套策略;重要的获准与拒绝链路表现正确;计数器和跟踪结果能够解释这些表现;回退流程也经过演练。“转换后的文件成功加载”只满足其中一个条件。
行为一致之后,再着手精简
原生规则集稳定运行后,nftables 才开始回报迁移成本。inet 协议族表允许 IPv4 和 IPv6 数据包穿过同一组基础链,必要时仍可用特定协议族的表达式加以区分。[4] 由此,真正共用的策略——已建立连接、双栈服务端口、接口分界——可以写成同一套规则,同时保留 IPv4 与 IPv6 规则之间真实存在的差异。
集合把大量近乎相同的重复规则换成带类型的值集。映射把键与值关联起来,verdict map(裁决映射)还可以把键直接关联到一项决定或一条链。Netfilter 文档把这种通用的集合与映射设施,以及区间和拼接,列为 nftables 与 iptables 的主要差异之一。[5] 收益超出了行数减少本身。“办公网络”“已发布服务”和“目的地址转换”等策略内容会成为有名称的数据,可以与控制流分开审阅。
每次只做一项语义改动。先合并双栈过滤链路,测试,然后停下。再把一组重复地址规则改成集合,测试,然后停下。接着把一项 NAT 选择移入映射,测试,然后停下。说明所有权和意图的注释应当保留;简短语法若缺少维护脉络,只会把含混之处压缩得更紧。
调整基础链钩子和优先级时尤其需要克制。nftables 允许运维人员选择链在数据包处理流程中的进入位置及优先级,当多个子系统共存时,这项能力十分强大。[2] 与此同时,一次看似无害的重组也会改变某条规则是在连接跟踪、NAT 或另一管理程序的链之前运行,还是在其后运行。优先级变化应当按数据包链路变化处理,规格高于格式调整。
识别哪些系统不适合直接转向 nftables
当团队拥有一套复杂的主机或网络防火墙,需要明确控制钩子和优先级,可以从集合或映射中获益,并且能像测试代码一样测试数据包链路时,直接使用 nftables 很合适。如果迁移后无人负责 Linux 网络,较高层的管理程序已经能够安全表达所需策略,或者设备及集群平台承诺接管底层规则集,直接管理 nftables 的做法就不合适。
小团队尤其要如实衡量这项运维取舍。原生语法可以让大型规则集更连贯,但审阅、预演、控制台访问、监控和值班人员的知识储备仍需团队另行准备。如果集成于系统的 firewalld 包装层已经覆盖实际需求,继续由这个较高层组件掌握所有权,会更易维护。[6] 如果平台持续改写主机网络,合适的迁移方式是更新平台支持的配置,仍由平台管理底层规则。
故障信号很明确:切换以后,运维人员仍然交替使用 iptables 和 nft;多项服务仍可重建彼此重叠的链;没人能说明重启后究竟由哪个文件取得最终控制权。此时语法已经变化,控制问题仍原样存在。
当唯一所有者能够重建、观察、测试和回退防火墙时,iptables 到 nftables 的迁移才算完成。更短的规则集可以留到以后。第一个发布目标是行为一致。
来源
- Netfilter Project,“Moving from iptables to nftables”——单条命令与完整规则集转换、
iptables-nft兼容后端、不受支持的表达式警告,以及混用 legacy 与 nftables 子系统的风险。 - Netfilter Project,
nft(8)手册——文件输入、仅检查校验、规则集检查、兼容语句、表达式、映射和裁决行为。 - Netfilter Project,“Operations at ruleset level”——完整规则集列示、显式清空、备份构造和基于文件的原子恢复。
- Netfilter Project,“Nftables families”——
ip、ip6、inet、arp、bridge与netdev协议族的范围,其中包括inet表中的双栈共用链路。 - Netfilter Project,“Main differences with iptables”——显式计数器、双栈管理、动态更新、集合、映射、区间和拼接。
- Debian Project,“nftables”——关于 nftables 作为默认框架、
iptables-nft兼容层、firewalld作为较高层所有者、启动配置以及避免混用规则管理界面的独立下游说明。 - Netfilter Project,“Ruleset debug/tracing”——限定范围地启用
nftrace,并用nft monitor trace检查数据包链路。 - NLUUG,“Pablo Neira Ayuso — Getting a Practical Grasp of nftables”(2016 年 5 月 26 日),Wikimedia Commons——文章题图所用研习会档案照片的出处及活动背景。