如果你在某个凌晨被工单叫醒,让看看一台服务器的防火墙为什么“好像没生效”,然后随手执行了一下systemctl status firewalld,结果在日志里看到一行扎眼的字:
WARNING: AllowZoneDrifting is enabled. This is considered an insecure configuration option. It will be removed in a future release. Please consider disabling it now.
这时候第一反应肯定是:防火墙服务不是还在跑吗?为什么会有 WARNING?这东西到底是不是导致流量异常的幕后黑手?
我可以直接给你结论:这个警告值得你认真对待,但它不一定是你业务故障的直接原因。它代表防火墙当前开启了一个名为AllowZoneDrifting的特性,而这个特性在安全评估中被视为“不安全配置”,并且会在后续版本中被移除。理解它并不难,难的是很多人第一次遇到时容易被“服务正常”的假象蒙混过去,直到某天流量方向变得诡异,才想起来这个被忽略的 WARNING。
这篇文章写给所有正在跑 RHEL/CentOS/Rocky/Fedora 系系统、用 firewalld 管理网络的运维和开发者。我会把警告的来龙去脉、机制原理、关闭方法和后面可能踩到的一系列坑,一次性讲透。
1. 第一次见到这行WARNING:日志、环境与第一时间的判断
1.1 现象:服务能起来,但日志里多了一行刺眼的警告
我是在一次给某台持续运行了几个月的业务服务器做安全加固时发现它的。当时的操作很简单,就是重启了一下 firewalld,想看它是否加载了新的规则。重启完成之后,执行journalctl -u firewalld --since "1 hour ago" --no-pager,输出里就混着这样一段:
Feb 14 03:12:47 host01 systemd[1]: Starting firewalld... Feb 14 03:12:47 host01 firewalld[27419]: WARNING: AllowZoneDrifting is enabled. This is considered an insecure configuration option. It will be removed in a future release. Please consider disabling it now. Feb 14 03:12:47 host01 systemd[1]: Started firewalld. Feb 14 03:12:47 host01 firewalld[27419]: WARNING: AllowZoneDrifting is enabled. This is considered an insecure configuration option. It will be removed in a future release. Please consider disabling it now.看到这行,我第一反应不是去搜索引擎,而是先确认服务的健康状态:
$ systemctl is-active firewalld active服务状态是 active,防火墙也能正常执行firewall-cmd --list-all。也就是说,这不是一个导致服务崩溃的错误,而是一个配置层面的安全提示。很多人走到这一步就关掉终端回去睡觉了,但我的直觉告诉我,一个能直接影响包匹配逻辑的配置项,不应该被一带而过。
1.2 为什么大多数运维人员会忽略它,以及我为什么没有
先说结论:大多数人都忽略了它,因为它表面上不产生任何负面影响。服务是Active,端口该通还是通,SSH 也没断。在一堆Started和Reached target的日志里混着一条 WARNING,真的太不显眼了。如果不是当天刚好在排查“某些访问行为不符合预期”的问题,我大概率也不会深究。
真正让我警觉的是它的措辞:insecure configuration option、will be removed in a future release。这说明 firewalld 的维护者自己都承认这个特性有问题,而且已经计划废弃它。任何被官方标记为“不安全”并准备删除的功能,都值得你在规划迁移之前就主动处理掉,否则某次版本升级之后,行为发生静默变化,那才是最难排查的故障。
1.3 什么环境下最容易撞见它
结合我自己的经验,这个警告容易出现在以下场景:
- RHEL 7 系列(firewalld 0.6.x)的服务器上,默认配置直接带出警告。
- RHEL 8 / Rocky 8(firewalld 0.8.x)虽然默认策略有变化,但升级后如果配置文件迁移不干净,也容易残留这个选项。
- 云厂商公共镜像里,如果镜像制作者没有清理
/etc/firewalld/firewalld.conf的默认参数,新机器同样会出现。 - 系统上有虚拟化/容器组件(例如 docker 或 libvirt),它们会动态创建接口和 zone,干扰默认 zone 匹配逻辑,从而让漂移现象更明显。
所以,如果你刚买了一台“全新”的云主机,一启动就看到这个警告,不要觉得是巧合。这几乎可以确定是发行版默认配置延续下来的老问题。
2. AllowZoneDrifting的机制拆解:为什么按目标地址匹配会成为“不安全的配置选项”
如果你只想知道怎么消除警告,可以直接跳到第 3 节。但如果你不想下次再被类似问题绊倒,我强烈建议你花几分钟理解这个“zone 漂移”到底做了什么。
2.1 回顾firewalld的zone模型:接口、区域、服务
要理解漂移,先得知道 firewalld 最基本的工作方式。firewalld 里有一个“区域(zone)”的概念,它把网络流量分成了多个安全等级,比如默认常见的public(最受限)、internal(中等信任)、trusted(完全信任)。
通常我们做的是把网卡(接口)绑定到某个 zone,例如:
firewall-cmd --zone=public --add-interface=eth0 --permanent这样一来,凡是进入eth0的流量,首先都会交给publiczone 的规则去过滤。这是最简单的逻辑:按来源接口决定规则。
用一句话概括就是:一个数据包应该使用哪个 zone 的防火墙规则,首先看它从哪个接口进来。接口是 zone 的入口。这也是绝大多数防火墙的管理方式,直观、可控。
2.2 漂移的“漂”法:从源接口到目标地址的转换
隆重介绍今天的主角AllowZoneDrifting。它在 firewalld 中实现了一个“额外匹配”:除了根据源接口找 zone,它还会根据数据包的目标 IP 地址再找一次 zone,如果目标 IP 落到了另一个 zone 的目标子网范围内,就“漂移”过去,改用那个 zone 的规则。
我画一个场景给你看:
- 机器上有两个 zone。
publiczone:绑定了eth0,对外提供服务,只放行 SSH(22/tcp)。internalzone:绑定了网段192.168.100.0/24,并放行了某业务端口8080/tcp。
这个时候,如果 eth0 收到一个数据包,源 IP 是外网的203.0.113.5,目标 IP 是192.168.100.7:8080。按照传统的“按接口匹配”逻辑,eth0 属于public,所以应该只允许 22 端口,这个包应该被丢弃。
但AllowZoneDrifting打开后,firewalld 会额外发现:目标 IP192.168.100.7属于internalzone 的地址范围。于是这个从外网接口进来的、目标是内网地址的包,被重新归入internalzone 去匹配。然后,internalzone 放行了 8080 端口,于是这个包被放行了。
这就是漂移:规则匹配从“你从哪个门进来”,悄悄变成了“你要去哪个房间”。一旦你允许这种动态匹配,只要攻击者把目标地址写成你内网网段里的任何一台机器,他就能“借用”那个内网 zone 的宽松策略。
2.3 这为什么危险:绕过源接口限制的路径
听完上面的例子,你应该已经明白,这个特性的本质是对访问控制的一种隐式扩容。它打破了“接口-zone”的固定映射关系,让规则选择变得多跳跃、不可预期。在安全基线里,这属于典型的绕过路径:
- 源接口 zone 本来只放行了极少数端口。
- 目标地址属于另一个更宽松的 zone。
- 攻击者构造目标 IP 为内部 IP 的数据包,直接享受内网规则。
哪怕你能控制外部到内部的路由,漂移的存在也使得你的安全边界没法被清晰描述。官方把这个特性标记为不安全,也正是因为这种“跨 zone 匹配”很容易让运维人员本以为“已经在 public zone 挡住的东西”,实则从 internal zone 的洞漏了进去。
| 匹配方式 | 依据条件 | 典型后果 |
|---|---|---|
| 标准 zone 匹配 | 数据包来源接口所属的 zone | 策略清晰,边界可控 |
| 开启 AllowZoneDrifting 后 | 优先看来源接口,再看目标 IP 所属 zone | 可能绕过 public 限制 |
从安全运维的角度,你应该把 zone 当作“物理隔离区”来使用,而不是让包自己选择该走哪条路。自动漂移虽然能带来一点配置上的便利,但它真的不适合用在隔离严格的环境里。
3. 彻底关掉这个警告:三种可落地的操作与验证清单
知道了它不安全,剩下的问题就是怎么关。下面我会给出从“确认状态”到“验证生效”的完整步骤。
3.1 首先检查当前状态:几条命令确认漂移是否开启
在动手之前,先确认当前环境到底是什么状态。下面几条命令都能给你线索:
# 方法一:直接查 firewalld 配置文件的参数 $ grep -i AllowZoneDrifting /etc/firewalld/firewalld.conf AllowZoneDrifting=yes # 方法二:用命令查询(某些早期版本支持) $ firewall-cmd --get-allow-zone-drifting yes # 方法三:看日志中的警告内容 $ journalctl -u firewalld --no-pager | grep -i drifting WARNING: AllowZoneDrifting is enabled...如果第一、第二种方法返回的都是yes,或者日志里出现了 WARNING,那说明当前确实开着漂移。
需要注意,部分 firewalld 版本可能已经移除了--get-allow-zone-drifting命令,遇到这种情况直接用配置文件检查即可,不影响后续操作。
3.2 推荐做法:修改firewalld.conf并重启服务
关掉漂移最可靠的方法是修改 firewalld 的主配置文件。我在生产环境里推荐用这种方式,因为它能做到持久化,重启机器或服务后配置依然保留。
具体步骤如下:
- 先备份原配置:
$ sudo cp /etc/firewalld/firewalld.conf /etc/firewalld/firewalld.conf.bak- 编辑配置文件:
$ sudo vim /etc/firewalld/firewalld.conf找到[Default]这一节(通常在文件最上方),里面会有一行:
AllowZoneDrifting=yes把它改成:
AllowZoneDrifting=no如果你的文件里没有这一行,说明当前用的可能是默认值,你手动新增一行同样是有效的。注意语法:必须是小写的no,不要写成NO、false、0,firewalld 解析器只认yes/no。
- 重启 firewalld 服务让配置生效:
$ sudo systemctl restart firewalld重启后,再查看日志会发现那条 WARNING 已经消失了。
3.3 其他方式:命令行临时设置与开机持久化的权衡
除了改配置文件,还有一个命令行方法:
$ sudo firewall-cmd --set-allow-zone-drifting=no这个命令能立即关闭漂移,在部分旧版本(主要是 RHEL 7 早期)中有效。它的优点是实时生效,不需要重启服务,适合在无法中断连接的应急场景下使用。
但它有一个明显的短板:重启 firewalld 后会被重置回默认值或配置文件里的值。所以如果你只是想临时压制这条警告,可以试试;如果想永久解决,最终还是要回到配置文件。
如果你不想手动编辑配置,也可以用sed直接替换:
$ sudo sed -i 's/AllowZoneDrifting=yes/AllowZoneDrifting=no/g' /etc/firewalld/firewalld.conf $ sudo systemctl restart firewalld不过我还是建议手动编辑,因为这个选项位置比较隐蔽,用sed容易误伤其他内容。
3.4 验证清单:重启后必做的三步检查
重启之后不能只看一眼日志就完事,我一般会做三步验证:
| 验证项 | 命令 | 预期结果 |
|---|---|---|
| 警告消失 | journalctl -u firewalld --since "1 min ago" --no-pager | 无 AllowZoneDrifting WARNING |
| 配置生效 | grep AllowZoneDrifting /etc/firewalld/firewalld.conf | AllowZoneDrifting=no |
| 业务连通 | ssh user@server_ip/curl http://server_ip:服务端口 | 正常 |
注意第三步不能省略。很多人改完配置只顾着看日志,忘了实际验证业务端口,等到第二天才发现某些服务因为依赖漂移而导致连接异常了。禁用之后功能变化,下一节我会仔细说。
4. 关掉之后到底改变了什么:功能对比与一个实际踩坑案例
4.1 正常情况下没有感知:NAT与服务规则不受影响
很多人担心关闭漂移会导致整个防火墙“变严”,从而误伤正常业务。从我实际运维看下来,绝大多数的常规部署并不会受影响。
为什么?因为常规部署里大家一般只用到:
- 把接口绑定到某个固定 zone;
- 用
--add-service、--add-port在当前 zone 内开放服务; - 用
--add-masquerade做 NAT; - 用
--add-forward-port做端口转发。
这些功能全都在“按接口匹配”的模型下工作,跟目标地址漂移没有关系。例如你的服务器上 eth0 绑定了 public zone,你用--add-port=80/tcp放行 HTTP 服务,那么无论外界访问的目标 IP 是什么,只要 eth0 进入,就按 public zone 规则处理,不需要也绝不会用到漂移。
所以从规则层面看,关闭漂移后,绝大多数环境下你感受不到任何变化,SSH、Web、数据库连接统统照常。
4.2 依赖漂移的场景:多zone策略路由、虚拟机流量
既然绝大多数场景不依赖,那到底什么场景会用到漂移?坦白讲,真正需要它的场景很少,但碰上一次就够让人头疼。
比较典型的是当你手动配置了多个 zone,且不同 zone 对应不同的内网网段时,你可能会“偷懒”地把某些跨区访问交给漂移自动选 zone。比如你新增了一个guestzone,网段是10.10.0.0/24,然后希望从 eth1 进来的流量(原本绑定public),只要目标是10.10.0.0/24,就直接按guestzone 规则走。
在开启漂移时,这个访问确实会被自动匹配到guestzone。一旦关闭,它就老老实实走 eth1 的源接口 zone(即 public),而 public zone 里没有放行到10.10.0.0/24的规则,访问就会失败。
这一类用法在虚拟化、多租户隔离环境中偶尔出现。比如某搞虚拟化测试的 A 同学,在宿主机上配置了internalzone 用于 VM 网络,同时又用publiczone 绑定了物理网卡。他以为自己在物理网卡上公开放行了 8080,却发现实际访问行为和自己想的不一样,就是因为漂移悄悄把某些包引导到了 internal zone。
4.3 教训:一次在模拟项目X中因为漂移导致规则顺序错乱
讲个我实际排查过的案例,虽然环境是模拟项目 X,但背后的逻辑和线上问题一模一样。
当时项目 X 由两台服务器组成:一台对外提供 Web 服务,另一台跑数据库。防火墙策略是这样设计的:
- Web 服务器
eth0绑定了 public zone,只放行 80/443; - 数据库服务器通过
eth0访问 Web 服务器所在的内网网段,同时把自身某个内网网段绑定到了internalzone,放行了 3306 端口。
看起来没有任何交集,但问题出在一台开发机上。开发人员想直连数据库服务器的 3306 端口做调试,他写的访问目标 IP 是数据库服务器的公网 IP。由于公网 IP 恰好落在了internalzone 的目标网段范围内(这是他自己原先配置失误导致的),AllowZoneDrifting竟然直接把来自公网的流量重定向给了 internal zone 的规则。
于是,本该被 public zone 拒绝的 3306 端口请求,被内网 zone 的策略放行了。这个洞从外部测试者看来,等同于数据库端口暴露在公网。等我们注意到日志里的 WARNING 并禁用漂移后,同样流量立即被拦截,一切恢复预期。
这个案例告诉我一个很简单的道理:你写在规则里的每一行,都应该精确说明“谁来访问、从哪个口进来、去往哪里”。漂移的存在会让“从哪个口进来”失去意义,规则越多,越危险。
5. 后面还会遇到的坑:配置不生效、其他工具叠加与我的加固习惯
5.1 改了配置重启还是警告?检查这四处
有一次我帮某开发检查一台 CentOS 7 服务器,明明改了/etc/firewalld/firewalld.conf,重启以后警告照旧。排查了一圈才发现,他改的文件路径是/usr/lib/firewalld/firewalld.conf。这是软件包自带的默认配置,会被/etc下的同名文件覆盖,修改它几乎没有任何效果。
所以第一个检查点:确认你编辑的是/etc/firewalld/firewalld.conf,而不是/usr/lib/firewalld/firewalld.conf。
第二个常见原因是某些自动化工具(如 Ansible 的模板、云初始化脚本)会在服务启动前覆盖配置文件。如果你拿的是公共镜像,建议直接执行grep -R AllowZoneDrifting /etc/firewalld/ /usr/lib/firewalld/,看看哪里还有这个选项。如果有多个匹配,把所有/etc下的都改成no,并把/usr/lib下的作为备用。
第三个原因是 firewalld 版本太低或太高,都可能找不到这个选项。比如某些发行版里它可能叫AllowZoneDrifting,但旧版本默认值不同,修改位置也可能为你改动时偷懒造成遗漏。确保你的firewalld --version结果和当前文档对应。
第四个原因比较隐蔽:服务启动时某个第三方工具通过 DBus 接口修改了运行时配置,覆盖了你的静态配置。遇到这种情况,我建议你检查是否存在firewall-cmd --runtime-to-permanent之类的操作被第三方脚本执行,或者使用 systemd override 在服务启动后强制执行关闭命令。
5.2 容器网络、虚拟化网络与zone漂移的交互
如果你的机器上装了 Docker、Podman 或 libvirt,它们会自动创建网桥(docker0、virbr0等)并插入到内核网络栈。有些工具还会自动向 firewalld 注册新的 zone,或者直接操作 iptables 规则。
这种情况下,AllowZoneDrifting可能会和容器网络产生叠加效应。比如你在 public zone 里只开了 22,但是 Docker 创建了一个docker0并把它分配到了internalzone,而某个容器的网段正好出现在 internal zone 的地址范围内。那么外网数据包只要目标 IP 指向容器网段,就可能被漂移到 internal zone,绕过 public 的限制。
我的建议很简单:不要把容器网段的 IP 当作防火墙规则里依赖的目标地址,更不要指望通过漂移让外网访问自动匹配到容器 zone。你应该明确把容器接口绑定到一个受控 zone,并且单独设置该 zone 的放行策略。关闭漂移后,至少要重新审视一遍你的网络脚本,确保没有依赖“自动跨区访问”的逻辑。
5.3 我的建议:即使没有警告也主动关闭
最后说说我的加固习惯。从第一次处理这个警告开始,我就在所有新部署的机器上直接关掉AllowZoneDrifting,即使系统没有报任何警告。原因有三:
- 它注定要被移除,与其等到版本升级后行为突然变化,不如提前让规则回归“按接口匹配”的直观模型。
- 它太容易被忽略,一旦你开启多 zone、多网段,很容易留下一条无法直观理解的隐式放行路径。
- 生产环境安全审计时,“非默认配置”往往会被单独拉出来询问,与其花时间解释为什么留着这个不安全项,不如直接清掉。
实际操作上,我通常会在初始化脚本中加入这样一段:
$ grep -q '^AllowZoneDrifting=no$' /etc/firewalld/firewalld.conf || sed -i 's/^AllowZoneDrifting=.*/AllowZoneDrifting=no/' /etc/firewalld/firewalld.conf $ systemctl restart firewalld这样无论初始配置是yes还是缺失,最终都会被强制收敛为no。
5.4 一个补充:如果你真的很喜欢自动选 zone,该怎么做
关闭漂移后,如果你确实需要让某些流量“按目标地址走不同 zone”,不要回到 AllowZoneDrifting 的老路,而是用 firewalld 本身提供的**策略规则(policy rules)**来实现。从较新的版本开始,firewalld 支持firewall-cmd --add-policy之类的功能,可以精确指定“从哪个 zone 来的、要去哪个 zone、放行哪些服务”,远比漂移那种隐式匹配好控制。
当然,如果你用的版本还没策略规则,老老实实把网卡拆开,分别绑定到不同 zone,然后写清规则,也比开着漂移强得多。毕竟,一个安全边界清晰的可维护配置,远比一个“自动帮我选规则”的黑盒子更让人放心。
我在处理这个问题之后,给自己定了一个小规矩:凡是想通过防火墙的“智能匹配”来偷懒的时候,都要先问一句——如果这个包需要被精确控制,我能不能明确说出它从哪个接口来、去哪台机器、走哪个端口?如果不能,说明我还没想清楚,这时候最需要做的不是找捷径,而是重新规划 zone 和接口的绑定关系。别让一行 WARNING 偷偷决定你安全边界的位置。