CentOS服务器防火墙的富规则(Rich Rule)很多人用不习惯,因为它的语法比普通端口放行长,但实际生产环境中,单纯用firewall-cmd --add-port已经不够用了。比如你需要限制只有某个IP段能访问22端口,或者只允许特定源地址连接数据库端口,同时还要记录日志,这时候富规则就是最直接的解决方案。富规则的核心价值在于把“来源、目标、服务、端口、协议、动作、日志”组合到一条规则里,不需要拆成多条iptables命令。

富规则的基本语法结构

富规则的通用格式是:firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="允许的IP或网段" port port="端口号" protocol="tcp" accept'。family指定ipv4或ipv6,source address是来源地址,可以写单个IP也可以写CIDR网段,port port后面跟具体端口,protocol是tcp或udp,动作可以是accept、reject或drop。注意富规则中所有条件之间是“与”的关系,也就是必须同时满足才会触发动作。如果你不写source address,就表示匹配所有来源地址。

只允许指定IP访问SSH端口

假设你的服务器IP是192.168.1.100,你只想让办公室固定IP 203.0.113.50能连接22端口,其他所有IP一律拒绝。先添加允许规则,再添加拒绝规则,顺序很重要,firewalld会按规则添加顺序匹配。第一条:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" port port="22" protocol="tcp" accept'

第二条拒绝所有其他来源访问22端口:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="22" protocol="tcp" reject'

执行firewall-cmd --reload后生效。这里用reject而不是drop,是因为reject会返回一个拒绝响应,方便排查连接问题,drop则直接丢弃数据包,对方会一直等到超时。对于SSH这种管理端口,建议用reject,能更快发现是不是被防火墙拦了。

限制IP段访问数据库端口并记录日志

生产环境数据库通常只允许内网网段访问,比如只允许10.0.0.0/8访问MySQL的3306端口,同时把非法访问记录下来。规则可以这样写:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="3306" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="3306" protocol="tcp" log prefix="MySQL-Deny: " level="warning" limit value="5/m" drop'

第二条规则中log prefix是日志前缀,方便在/var/log/messages里用grep过滤,level是日志级别,limit value="5/m"表示每分钟最多记录5条,防止日志被刷爆。drop动作放在log之后,表示先记录再丢弃。注意log和drop可以同时出现在一条富规则里,动作部分写log后面跟drop即可。

富规则中同时指定端口和协议

有些服务同时监听TCP和UDP,比如DNS的53端口。如果只想放行TCP的53端口,必须明确写protocol="tcp",不能省略。如果省略protocol,firewalld默认匹配所有协议,这在某些场景下会误放行。例如只允许TCP的53端口:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="53" protocol="tcp" accept'

如果还要放行UDP的53端口,需要单独再写一条protocol="udp"的规则。富规则不会因为你写了port port="53"就自动同时匹配TCP和UDP,必须分开写。

使用服务名代替端口号

富规则里除了直接写端口号,也可以使用服务名,前提是这个服务在firewalld的服务目录里有定义。比如http服务对应80端口,https对应443端口。写法是:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="http" accept'

使用服务名的好处是如果以后端口变更,只需要修改服务定义文件,不需要改富规则。但要注意,服务名只能匹配firewalld已加载的服务,自定义服务需要先放到/etc/firewalld/services/目录下并reload。对于不熟悉服务名的新手,直接写端口号更直观,不容易出错。

富规则中的源地址和目的地址

大多数情况下我们只限制来源地址,但富规则同样支持目的地址。比如服务器有多个IP,你只想让某个服务监听在特定IP上,可以写destination address。例如只允许访问本机192.168.1.100的8080端口:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" destination address="192.168.1.100" port port="8080" protocol="tcp" accept'

如果同时写了source和destination,就表示从特定来源到特定目的的流量才匹配。这在多网卡服务器上做流量隔离时很有用。另外源地址和目的地址都支持CIDR写法,比如source address="10.10.0.0/16"。

富规则的查看、删除和优先级

查看当前所有富规则用firewall-cmd --list-rich-rules,查看永久规则要加--permanent。删除富规则必须完整复制原规则字符串,一个字符都不能差,否则会报错。例如删除刚才添加的SSH允许规则:

firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="203.0.113.50" port port="22" protocol="tcp" accept'

如果规则字符串太长记不住,可以先--list-rich-rules查看,复制粘贴后再改remove。富规则的匹配顺序是自上而下的,先添加的规则先匹配。所以拒绝规则一定要放在允许规则之后,否则允许规则永远不会生效。如果你需要调整顺序,只能先删除再按新顺序重新添加。

富规则与普通端口放行的区别

很多人问:我已经用firewall-cmd --add-port=80/tcp放行了80端口,为什么还要用富规则?区别在于普通端口放行是对所有来源地址开放,而富规则可以精确控制来源。如果你的服务器直接暴露在公网,普通放行等于把端口完全敞开,任何IP都能连。对于Web服务80端口可能问题不大,但对于数据库、Redis、Docker管理端口这类敏感服务,必须用富规则限制来源IP。另外普通端口放行无法记录日志,也无法设置限速,富规则可以同时做日志和限速。

富规则中的限速和连接数限制

富规则还支持对连接频率做限制,防止暴力破解。比如限制同一个IP每分钟最多新建5个SSH连接:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" service name="ssh" limit value="5/m" accept'

这里的limit value="5/m"表示每分钟最多匹配5次,超过后后续连接会被拒绝。这个功能在防SSH暴力破解上非常实用,比fail2ban轻量得多。注意limit是对规则匹配次数的限制,不是对并发连接数的限制。如果要限制并发连接数,需要用到connlimit模块,富规则本身不直接支持,需要结合iptables的connlimit扩展。

富规则中的元素组合与复杂场景

富规则支持把多个条件组合在一起,比如同时限制来源IP、目标端口、协议、日志、动作。一个典型的复杂场景:只允许两个IP段访问服务器的9000端口(PHP-FPM或应用端口),其他来源记录日志后丢弃,并且每分钟最多记录10条。可以写成:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.20.0.0/16" port port="9000" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.30.0.0/16" port port="9000" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="9000" protocol="tcp" log prefix="App-Deny: " level="warning" limit value="10/m" drop'

这样配置后,只有两个内网网段能访问9000端口,其他所有IP的访问都会被记录并丢弃。日志前缀App-Deny方便你在系统日志中快速定位非法访问来源。

富规则配置中的常见错误

最常见错误是忘记加--permanent,直接添加后重启服务器规则就丢了。第二个错误是规则字符串中的引号嵌套问题,外层用单引号,内层用双引号,不能混用。第三个错误是reload后规则没生效,可能是规则语法有误,但firewall-cmd在添加时不会校验语法,只有reload时才会报错。建议添加规则后先执行firewall-cmd --check-config检查配置,再reload。第四个错误是删除规则时字符串不完整,导致删除失败。第五个错误是规则顺序不对,把拒绝规则放在了允许规则前面,导致所有流量被拒。

富规则在生产环境中的实际应用建议

对于CentOS 7和CentOS 8的服务器,建议把富规则写成脚本统一管理,每次修改后备份。所有管理端口(22、3306、6379、27017等)必须用富规则限制来源IP,不要用普通端口放行。对于Web端口80和443可以保持普通放行,但如果需要防CC或限制特定路径,可以结合富规则的limit功能。日志记录要设置合理的limit值,防止日志文件暴涨。定期用firewall-cmd --list-rich-rules审查现有规则,删除不再需要的规则。如果服务器有多个网卡或多个IP,建议在富规则中明确destination address,避免规则误匹配到其他网卡的流量。

富规则与firewalld区域的关系

富规则是附加在某个区域上的,默认添加到当前活动区域,通常是public。你可以用--zone参数指定区域,比如--zone=internal。不同区域的富规则相互独立,流量进入时先匹配区域,再匹配该区域内的富规则。如果你的服务器有多个网络接口,建议把内网接口划到trusted或internal区域,外网接口留在public区域,然后在public区域上配置富规则限制外网访问。这样内网流量不受富规则影响,外网流量被严格管控。查看当前活动区域用firewall-cmd --get-active-zones,查看某区域的富规则用firewall-cmd --zone=public --list-rich-rules。

富规则中的IPv6支持

如果你的服务器启用了IPv6,富规则同样可以限制IPv6流量。只需要把family="ipv4"改成family="ipv6",源地址写成IPv6格式即可。例如只允许特定IPv6地址访问SSH:

firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address="2001:db8::/32" port port="22" protocol="tcp" accept'

注意IPv4和IPv6的富规则需要分开写,不能在同一条规则里同时匹配两种协议族。如果服务器没有IPv6需求,建议直接关闭IPv6或至少对IPv6配置同样的限制规则,否则攻击者可能通过IPv6绕过IPv4的防火墙限制。

富规则的日志查看与排错

富规则中配置了log后,日志默认写入/var/log/messages,CentOS 8及以上版本可以用journalctl查看。使用grep过滤日志前缀:grep "MySQL-Deny:" /var/log/messages。如果发现日志中没有记录,检查log是否写在drop或reject之前,以及limit值是否设置过小。如果规则没生效,先用firewall-cmd --list-all查看规则是否在列表中,再用iptables -L -n查看底层规则是否生成。firewalld的富规则最终会转换成iptables规则,如果iptables里没有对应规则,说明firewalld没有正确加载。

富规则编写的完整示例汇总

下面给出一个生产环境常用的富规则配置示例,覆盖SSH、MySQL、Redis和自定义应用端口:

# 只允许办公室IP访问SSH
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" port port="22" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="22" protocol="tcp" reject'

# 只允许内网访问MySQL并记录非法访问
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="3306" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="3306" protocol="tcp" log prefix="MySQL-Deny: " level="warning" limit value="5/m" drop'

# 只允许内网访问Redis
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="6379" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="6379" protocol="tcp" drop'

# 限制应用端口访问频率
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.20.0.0/16" port port="9000" protocol="tcp" limit value="10/m" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="9000" protocol="tcp" drop'

firewall-cmd --reload

这套规则覆盖了常见的安全需求,实际使用时根据你的IP段和端口调整即可。富规则写好后一定要测试,从允许的IP和不允许的IP分别连接,确认规则按预期工作。