CentOS 7 和 CentOS Stream 8/9 虽然生命周期不同,但在生产环境中依然占据大量份额。很多人装完系统第一件事就是把 firewalld 开起来,然后放行 22 端口就觉得万事大吉。这种策略在真正的入侵和扫描面前几乎等于裸奔。真正有效的防火墙策略,不是“允许所有然后逐个拒绝”,而是“默认拒绝所有,按需开放最小权限”。下面直接进入具体的定制技巧,不讲虚的。
理解 firewalld 的 zone 本质,别再用默认 public zone 凑合绝大多数管理员操作 firewalld 时从不切换 zone,所有规则全部堆在 public 里。public zone 的默认行为是允许所有连接,只是拒绝特定端口,这本身就违背了最小权限原则。你应该根据服务器的网络环境创建不同 zone。比如把内网管理接口绑定到 internal zone,把对外提供 Web 服务的接口绑定到 external zone,数据库集群之间的通信单独划分 dmz 或自定义 zone。创建自定义 zone 很简单:
firewall-cmd --permanent --new-zone=db-cluster firewall-cmd --permanent --zone=db-cluster --add-source=10.0.1.0/24 firewall-cmd --reload
这样来自 10.0.1.0/24 网段的流量就自动进入 db-cluster zone 的规则体系,与 public zone 完全隔离。每个 zone 的默认策略可以通过 --set-target 来修改,把目标设为 DROP 或 REJECT,而不是 default。DROP 直接丢弃数据包不回应,REJECT 会返回拒绝信息。对外网接口建议用 DROP,减少信息泄露;对内网接口用 REJECT,方便排错。
用富规则替代简单端口放行,实现更精细的访问控制简单用 add-port 放行端口太粗糙了。比如你只允许公司出口 IP 访问 SSH,用富规则可以精确到源地址、协议、端口、时间甚至日志记录。写法如下:
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.50/32" port port="22" protocol="tcp" accept'
如果有多条富规则需要管理,建议直接编辑 /etc/firewalld/zones/ 下的 XML 文件,结构清晰,方便批量部署。富规则还能做连接数限制,防止单 IP 暴力破解。例如限制同一 IP 在 60 秒内最多发起 3 个新的 SSH 连接:
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="0.0.0.0/0" port port="22" protocol="tcp" limit value="3/m" accept'
这个 limit 值后面的 m 代表分钟,还可以用 s、h、d。注意 limit 是基于每个规则的计数器,不是全局连接追踪,所以对于分布式攻击仍需配合 fail2ban。
利用 ipset 动态管理黑名单和白名单firewalld 原生支持 ipset,这比在富规则里写死 IP 高效得多。ipset 可以存储成千上万个 IP 地址而不影响匹配性能。先创建 ipset:
firewall-cmd --permanent --new-ipset=blacklist --type=hash:ip firewall-cmd --permanent --ipset=blacklist --add-entry=198.51.100.23
然后在富规则里引用这个 ipset:
firewall-cmd --permanent --zone=public --add-rich-rule='rule source ipset=blacklist drop'
之后只要往 blacklist 里添加或删除 IP,规则立即生效,无需重载整个防火墙。这在应对突发攻击时非常实用,可以用脚本自动从日志中提取恶意 IP 加入 ipset。
直接使用 nftables 绕过 firewalld 的抽象层CentOS 8 及更高版本底层已经全面转向 nftables,firewalld 只是 nftables 的一个前端。当规则变得非常复杂时,firewalld 的抽象层反而成为性能瓶颈和排错障碍。你可以选择停掉 firewalld,直接写 nftables 规则。一个典型的 nftables 最小化配置如下:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# 允许本地回环
iif lo accept
# 允许已建立和相关连接
ct state established,related accept
# 允许 ICMP
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
# 允许内网 SSH
ip saddr 10.0.0.0/8 tcp dport 22 accept
# 允许 HTTP/HTTPS
tcp dport {80, 443} accept
# 记录并丢弃其余流量
log prefix "nft-input-drop: " limit rate 10/second
counter drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
这个配置逻辑极其清晰:默认丢弃所有入站流量,只放行必要的服务和 ICMP,并且对丢弃的包做限速日志记录。ct state established,related accept 这一行保证了服务器主动发起的连接能够正常收到回复,这是很多人写规则时容易遗漏的。直接管理 nftables 的好处是规则集可以版本控制,用 git 管理 /etc/nftables.conf,任何修改都有记录,回滚方便。
内核参数与防火墙联动,防止协议层面的攻击防火墙规则写得再好,如果内核参数没调优,依然容易被 SYN Flood 或者碎片包攻击打垮。在 /etc/sysctl.conf 或 /etc/sysctl.d/ 下创建自定义配置文件,加入以下参数:
# 开启 SYN Cookie 防护 net.ipv4.tcp_syncookies = 1 # 缩短 SYN_RECV 状态超时时间 net.ipv4.tcp_syn_retries = 2 net.ipv4.tcp_synack_retries = 2 # 忽略所有 ICMP 重定向 net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 # 禁止源路由 net.ipv4.conf.all.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 # 记录并丢弃畸形包 net.ipv4.conf.all.log_martians = 1 # 增大连接追踪表大小 net.netfilter.nf_conntrack_max = 1048576 net.netfilter.nf_conntrack_tcp_timeout_established = 3600
执行 sysctl -p 生效。连接追踪表的大小直接决定了高并发场景下防火墙是否会丢包。如果服务器跑着负载均衡或者代理服务,nf_conntrack_max 必须调大,否则连接追踪表满了之后新连接全部被丢弃,现象就是服务间歇性不可用。
利用 firewalld 的 direct 规则处理特殊协议有些协议比如 FTP、SIP 需要动态协商端口,单纯放行 21 或 5060 端口没用,必须加载连接追踪辅助模块。firewalld 的 direct 规则可以直接操作 nftables 底层,加载 nf_conntrack_ftp 模块并设置策略:
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 21 -j ACCEPT firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
同时确保内核模块已加载:
modprobe nf_conntrack_ftp echo "nf_conntrack_ftp" >> /etc/modules-load.d/firewall.conf
对于生产环境,如果不需要 FTP 这种老协议,建议直接禁用相关模块,减少攻击面。同理,SIP 协议的 nf_conntrack_sip 模块如果不用也要黑名单掉。
自动化威胁情报联动,实现动态防御静态规则只能防住已知威胁,真正棘手的是那些不断更换 IP 的扫描器和僵尸网络。你可以在服务器上部署一个轻量级脚本,定期从公开威胁情报源拉取恶意 IP 列表,自动加入 ipset 或 nftables 的 set 中。以 nftables 为例,先创建一个命名 set:
nft add set inet filter threat-ipv4 { type ipv4_addr\; flags timeout\; }
nft add rule inet filter input ip saddr @threat-ipv4 drop
然后写一个 cron 脚本,每小时拉取一次恶意 IP 列表并更新:
#!/bin/bash
# 从 URL 拉取 IP 列表,每行一个 IP
curl -s https://example.com/threat-feed.txt | while read ip; do
nft add element inet filter threat-ipv4 { $ip timeout 3600s }
done
这里的 timeout 参数保证了即使脚本出错或者某个 IP 被误加入,一小时后也会自动移除,不会造成永久性封禁。这种动态防御机制配合静态最小权限策略,能大幅降低被扫描和暴力破解的成功率。
日志审计与告警,让防火墙策略可观测没有日志的防火墙等于闭着眼睛防守。firewalld 默认的日志级别很低,你需要手动开启。在富规则末尾加上 log 动作,并指定前缀和级别:
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="0.0.0.0/0" log prefix="FW-DROP: " level="info" limit value="5/m" drop'
这条规则会记录所有被丢弃的包,但限制每分钟最多 5 条,防止日志洪水。日志默认写入 /var/log/messages,建议用 rsyslog 或 journald 将防火墙日志单独分流到一个文件,方便后续用 fail2ban 或自定义脚本分析。如果你用的是 nftables,日志规则更直接:
nft add rule inet filter input log prefix "nft-drop: " limit rate 10/second counter drop
日志分析工具推荐使用 lnav 或者直接对接 ELK,设置关键字告警。例如 1 分钟内出现超过 20 条来自同一 IP 的丢弃记录,就触发告警并自动将该 IP 加入黑名单。
定期审计规则集,清理历史遗留的冗余规则服务器运行几个月后,防火墙规则往往会变成大杂烩。某个端口曾经为临时调试开放,调试完忘了关;某个 IP 白名单早已失效但一直保留。这些冗余规则就是潜在的安全隐患。建议每月执行一次规则审计。firewalld 查看当前生效规则:
firewall-cmd --list-all-zones
nftables 查看完整规则集:
nft list ruleset
把输出保存为文本文件,逐条审核。对于每一条规则,问自己三个问题:这条规则现在还必要吗?它的源地址或端口范围是否过于宽泛?有没有更严格的替代方案?审核完毕后,将清理后的规则集重新部署,并更新到配置管理系统中。
结合 SELinux 形成纵深防御防火墙是网络层面的防线,SELinux 是主机层面的强制访问控制,两者配合才能形成纵深防御。很多人因为 SELinux 配置麻烦就直接关掉,这在生产环境是极其危险的做法。即使防火墙被绕过或者某个服务存在漏洞被利用,SELinux 还能限制攻击者的操作范围。CentOS 默认启用 SELinux,保持 enforcing 模式,只对确实需要调整的策略进行定制。例如你的 Web 服务需要读写非标准目录,不要直接关掉 SELinux,而是用 semanage 修改上下文或者用 audit2allow 生成自定义策略模块。防火墙和 SELinux 的策略定制思路其实一致:默认拒绝,按需放行,最小权限。
把上述这些技巧组合起来,你的 CentOS 服务器安全策略就不再是简单的端口开关,而是一套动态、可审计、纵深防御的体系。安全不是一次性配置,而是持续迭代的过程,每次业务变更都应该重新审视防火墙规则是否仍然符合最小权限原则。
