在firewalld使用时候过程中,定义的多条rich规则,或者和direct规则混用,导致firewalld的过滤的数据包的规则,在某些情况下不会按照我们定义的规则顺序去执行,之所以造成这种问题的原因,是因为不了解firewalld对区域和其规则的设定在底层发生了什么!例如
我在《第2篇:Linux防火墙-firewalld的rich规则配置》已经探讨过这个问题。
我们在深入探讨firewalld基于它的后端iptables的内部机制,需要了解iptables过滤数据表的原理,下图是一个很好的总结。
Firewalld内部规则
firewalld作为iptables的前端,它在iptables的 PREROUTING / FORWARD / INPUT/OUTPUT链内部为其活动区域都预设了的不同区域名称的链,这些链的执行尤其固定的顺序,下图我们以上面的iptables数据包过滤流程图为导向,绘制了一个关于firewalld的external和internal为活动区域的规则集执行流程,读者可以通过iptables命令来查看各个链来验证下图的完整性,不过这个图因为篇幅我做了一些精简。
下图的流程是承接上图的路由系统
从上图可知,firewalld的internal/external区域会映射到iptables内部的对应区域链的规则集,例如,在PREROUTING链中firewalld在iptables中加载了三个chain,并且这三个chain的优先级依次是
- PREROUTING_direct:用于装载通过firewalld定义的direct规则。
- PREROUTING_ZONES_SOURCE:这个链在CentOS8的firewalld中已经取消。
- PREROUTING_ZONES:用于装载firewalld活动区域相关的chain
我们通过iptables命令查看PREROUTING链来证实我们的说法
此时,我们通过firewalld定义下面一条direct规则,来继续印证上面提到的PREROUTING_direct链的作用。
firewall-cmd --permanent --direct --add-rule \
ipv4 mangle PREROUTING 0 -p icmp -i ens33 -j DROP
首先从下图演示中,我们定义了上面的direct规则,在重装firewalld后我们通过iptables命令来查看通过direct命令选项定义的规则会在装载到PREROUTING_direct链当中。
我们因此知道PREROUTING_direct是用于装载direct选项定义iptables规则。
区域的原理
我们继续通过iptables查看另外一个PREROUTING_Zones链,如下命令
iptables -t mangle -L PREROUTING_ZONES -nv
在上图输出中,我们得知PREROUTING_direct链中装载着三条规则,这三条规则分别指向着下面三个chain,它们是firewalld为其活动区域相关的。其匹配顺序如下
PRE_internal ➤ PRE_external ➤ PRE_public
没错,我们通过firewall-cmd --get-active-zones的演示来验证我们的想法。
其中的public区域对于的PRE_public链在0.63版本的firewalld中是默认存在的,而在centOS8中的0.70版本,除非显式定义public为活动区域,否则会默认取消了PRE_public
那么,我们继续在这里提出一个问题,firewall-cmd设定默认区域会否对- PRE_internal和PRE_external的先后次序有所影响?
我们按照如下图,首先查看firewalld的默认区域和活动区域,并且查看PREROUTING_ZONES链中三条链的顺序。根前文是一致的。
之后,firewalld的默认区域由public区域替换为external区域,其顺序变更为
PRE_internal ➤ PRE_external ➤ PRE_externl
然后,再将默认区域由external区域替换为internal区域,如下图所示
在iptables的PREROUTING链中,顺序如下PRE_internal ➤ PRE_external ➤ PRE_internal
在firewalld中,通过前后对比,我们得出如下几个基本的结论
- firewall设定默认区域的链会追加到iptables对应链(这里是PREROUTING)中的最后位置。
- internal区域的链(例如:PRE_internal)的优先于external区域的链被匹配
注意:
我们通过上面的操作,读者你们会否发现?如果firewalld已经存在两个活动区域,而将其中一个活动区域设定为默认区域,这种配置是错误的。因为internal区域内的规则会被优先匹配,而internal区域又被设置为默认的区域,同一个链的上下文中,internal作为最后一条默认规则并不会被执行。
因此,当我们的防火墙存在两个区域internal/trust/work和external时,这些区域是用于安全地相互传递信息,而对于一些不可信任的连接请求是会被拒之防火墙之外的,我们可以交由默认规则来处理,而firewalld中有一个区域叫drop非常适合作为iptables规则列表中的默认规则。
区域设定的本质
从上文的我们已经知道firewalld的特定区域会在iptables规则列表存在与该区域名称对应的链。例如在PREROUTING链中
internal区域会被映射为 pre_interanl链
external区域会被映射为pre_external链
在FORWARD链中,即更为复杂的映射关系,你们可以自行使用iptables命令去查看,这里不一一举例。
我们知道firewalld中引入了区域这个概念后,可以在区域中进行类似如下操作
- 开放目标端口,例如
firewall-cmd --zone=external --add-port=3389/tcp --permanent
firewall-cmd --reload
我们用iptables查看external相关的规则集,会发现在iptables的filter表-->INPUT链-->IN_external_allow链,会存在这样的一条规则。
- 开放源网络,例如执行下面命令后
firewall-cmd --zone=external --add-source=172.42.38.128
firewall-cmd --runtime-to-permanent
iptables的INPUT_ZONES_SOURCE链多了一条放行主机172.42.38.128的规则,其实就是由上面的命令执行后,添加到预设这个链中的。
- 定义rich规则,例如定义下面这样的一条规则
firewall-cmd --zone=internal --add-rich-rule='rule
family=ipv4 source address=10.10.10.0/24 \
service name=ssh drop'
那么上面设定rich规则会被内化为iptables的filter表-->INPUT链-->INPUT_ZONES链-->IN_internal_deny链内的一条iptables规则,如下图
我们再看看定义下面的一条带有日志规则的rich规则,这次的rich规则是运行10.10.10.4的一台主机访问防火墙的ssh端口
firewall-cmd --zone=internal --add-rich-rule="rule \
family=ipv4 source address=10.10.10.4/32 \
service name=ssh log prefix=\"ssh connect:\"
level=\"notice\" accept"
上面的rich规则首选会分成两个部分,如下图所示
首先会由iptables的LOG链来做日志处理,即内化为filter表-->INPUT链-->INPUT_ZONES链-->IN_internal_log链内的一条iptables规则。
-
然后内化为在filter表-->INPUT链-->INPUT_ZONES链-->IN_internal_allow链内的一条iptables规则
设定区域的target
下图是在设定external默认target之前,我们查看external对应IN_external链内的的最后一条规则是
ACCEPT icmp any any
在改变external的默认target后,变为
DROP all any any
如下图所示
当然对区域的target设定,firewall-cmd会同时修改FORWARDING链下各个FORWARD对应的子区域链的最后一条规则,例如
sudo firewall-cmd --zone=internal --set-target=default --permanent
sudo firewall-cmd --reload
FWDI_internal对应的是internal区域,上面的命令会在FWDI_internal链中的最后一条规则修改为
ACCEPT icmp any any
如下图所示
如果将internal区域的默认target设定为DROP,那么对应的FWDI_internal链的最后一条规则即会默认丢弃所有需要转发的数据包,例如如下命令
sudo firewall-cmd --zone=internal --set-target=DROP --permanent
sudo firewall-cmd --reload
对应的FWDI_internal链内的最终效果图
区域链的内部规则优先级
首先这里要明确一个概念,firewalld中的任何预设区域,例如public/external/internal/work,都会被映射为iptables规则列表中的与该区域名称相关的预设链(Firewalld-Preference-Chain),我称为这些预设链叫区域链(Chain for Zone)
前面列举这些区域配置相关的例子,是为了印证firewalld的区域执行各种配置设定,已经按照预设一套规则列表去执行这些设定。每个firewalld的区域链的入站方向(注意:关键字)都有4条固有基本规则
1. 日志规则, 如图中的IN_external_log
2. deny规则,如上图的IN_external_deny
3. allow规则,如上图的IN_external_allow
4. 默认规则,如上图的DROP all any any
那么,这四条规则的顺序,连firewalld.org撰写firewalld技术文档的主站都承认非常奇葩的设定,狠狠地抽开发者自己脸,奇葩的原因就是deny规则一定要在allow规则之前吗?这一设定会导致使用rich规则时,原本匹配精度更高的放行规则,永远不会被执行。例如刚才对internal区域的rich规则配置见下图
由于第一条rich规则会转换为IN_internal_deny链内的iptables规则,第二条rich规则会转换为IN_internal_allow链内的iptables规则,由于IN_internal_deny链在IN_internal_allow之前,所以对于那些目标网段一样的匹配精度不一样的rich规则列表来说,就算倒大霉!图中的第二条规则是永远不会被匹配到的。
看过我Linux防火墙系列文章的读者,就是这一篇《第2篇:Linux防火墙-firewalld的rich规则配置》,可能会知道rich规则有个叫priority关键字可以解决这一问题,但你要注意0.63版本的firewalld还没有关于rich规则的priority优先级设定。
对于CentOS8之后的版本是0.64版本之后的firewalld,已经在原来区域链新增一些预设规则,如下图,
- pre规则
- log规则
- deny规则
- allow规则
- 默认规则
其中的pre规则就是用于装载显式设定了priority关键字为某个负数后的rich规则的。这样可以确保那些需要执行的rich规则被优先匹配执行。
总结
其实,我写到这里,你应该要明确对firewalld的基本原理有个全局的认识,不认识iptables对firewalld的配置会寸步难行,因为复杂的规则,基本上需要你熟悉direct规则或rich规则内部运行原理,因为它们的原型都是iptables规则,只是匹配的优先级不一样。
- direct规则是全局优先匹配的,一旦direct规则被匹配,区域链中的相同含义的规则都不会被匹配。
- 匹配每个活动区域的区域链内的规则列表,按照如下顺序
1)pre规则
2)log规则
3)deny规则
4)allow规则
5)默认规则 - 匹配默认区域的规则