首页 / 资讯中心 / 文章详情

FirewallD 安全加固与实时监控实战:从默认区域到 fail2ban 联动

FirewallD 安全加固与实时监控实战:从默认区域到 fail2ban 联动 ★ FEATURED ARTICLE
半夜被告警电话叫醒登上一台 Linux 服务器发现系统负载高得离谱翻日志一看SSH 端口上全是来自陌生 IP 的暴力破解尝试。这时候我才意识到光是把 FirewallD 装上、放行几个端口根本不算“配好防火墙”更谈不上“安全”。真正重要的是两件事持续监控它以及按照最小暴露原则把它加固到位。这篇内容就是围绕这两件事展开的目标很明确面向 CentOS Stream / RHEL / Rocky Linux / Fedora 这类 systemd FirewallD 体系的 Linux 服务器。无论你是刚接手服务器的运维新手还是已经在用 FirewallD 但总觉得哪里不对劲的开发者都能在这里找到一套可以直接照抄的方案。我不会绕弯子直接把配置、命令、踩坑记录都摊开写给你。1. 先看懂 FirewallD 的三层模型区域、服务、富规则1.1 为什么“默认区域”能决定你服务器的生死很多人在配置 FirewallD 时第一步就错了——他们从来不关心当前默认区域是什么只会看到端口不通就临时--add-port。但 FirewallD 是一条“默认拒绝链路”还是“默认放行链路”完全取决于默认区域。默认区域就是这台服务器上所有没有明确指定归属的流量的处理策略。FirewallD 一共有 9 个预置区域从trusted到drop信任度依次递减。生产服务器上最常用的两个是public默认拒绝外来连接只有明确放行的服务和端口才允许进入适合面向公网的服务器。drop所有外来连接直接丢弃连拒绝回包都不给适合暴露在公网但不提供任何服务的节点。我见过不少服务器网卡被 NetworkManager 自动绑定到了home或者work区域这两个区域默认信任程度比public高得多很多端口没放行却也能通。结果管理员以为防火墙在工作实际等于透明。所以拿到一台新机器先执行firewall-cmd --get-default-zone firewall-cmd --get-active-zones第一个命令看默认区域第二个命令看当前有哪些网卡绑定在哪个区域上。如果网卡不在你期望的区域用下面的命令把网卡挪过来firewall-cmd --zonepublic --change-interfaceeth0 firewall-cmd --set-default-zonepublic注意--set-default-zone会直接写入永久配置不需要再加--permanent。而--change-interface会同时改变运行时和永久配置实测最省事。做完这一步才算有了一个“默认拒绝”的安全起点。1.2 分清“运行时规则”和“永久配置”这是 90% 配置不生效的根源FirewallD 有一个特别容易坑人的设计规则分两层——运行时层和永久层。运行时层当前正在生效的规则存在于内存中重启 firewalld 服务后就没了。永久层存在/etc/firewalld/下的 XML 配置里每次服务启动时重新加载。不加--permanent的命令只改运行时层。我见过有人为了防止 SSH 被爆破敲了一条firewall-cmd --add-rich-rulerule source ip203.0.113.7 drop当时看着有效结果一重启服务器IP 照样能连接因为那条规则压根没写进磁盘。反过来也常见加了--permanent添加端口但忘了firewall-cmd --reload于是“永久配置里有运行时没有”别人访问不了服务你还找半天原因。我的习惯是每次查规则都同时看两层对比差异firewall-cmd --list-all --zonepublic firewall-cmd --permanent --list-all --zonepublic如果两者不一致用firewall-cmd --runtime-to-permanent把当前运行时规则保存为永久配置或者用--reload让永久配置覆盖运行时。具体用哪个取决于你想保留哪一层。刚配完规则想立即生效就是firewall-cmd --reload。1.3 服务、端口、富规则到底怎么选FirewallD 放行流量有三种常用方式新手最容易混淆方式示例适用场景服务servicefirewall-cmd --add-servicehttp该服务端口固定、语义明确如 HTTP、HTTPS、SSH端口portfirewall-cmd --add-port8080/tcp没有现成服务定义的应用端口富规则rich rulefirewall-cmd --add-rich-rulerule source address... service namessh accept需要限定来源 IP、协议、端口组合或多个条件的复杂放行能用服务尽量用服务因为服务定义里不仅有端口还包含了协议等元信息。端口规则只写了tcp/udp和端口号表达力弱一些。富规则是最灵活的但不要滥用——它是 XML 结构写多了自己都难维护。我比较推荐的原则是端口规则用于临时排查服务规则用于长期放行富规则只用于“带条件的放行或拒绝”。比如想放行某个内网网段访问 SSH我会用富规则明确写出来而不是把 SSH 端口向全世界开放。2. 可照抄的 FirewallD 安全加固流程从默认 zone 到 SSH 和 fail2ban2.1 第一步清点当前放行的服务和端口加固之前先给防火墙做一次“人口普查”。大多数发行版安装完 FirewallD 后public区域里已经默认放行了一些服务比如 SSH、DHCPv6 客户端某些带图形化管理面板的发行版可能还开着 Web 管理端口。执行firewall-cmd --zonepublic --list-services firewall-cmd --zonepublic --list-ports firewall-cmd --zonepublic --list-rich-rules看到的结果可能比你预期的多。我处理过的服务器里最常见的问题是“历史遗留”某次调试时添加了一个端口规则后来服务下线了规则却一直留在那儿。这些遗留规则就是攻击面要一条条确认并清理。不确定某个服务还有没有用我的判断标准是这个端口在公网上有没有对应的业务进程监听用ss -tlnp看看监听端口再对照防火墙放行列表能对上号的才保留。移除不需要的放行规则firewall-cmd --permanent --zonepublic --remove-serviceservice-name firewall-cmd --permanent --zonepublic --remove-port8080/tcp firewall-cmd --reload2.2 加固 SSH换端口、限来源、禁 root 直登SSH 是每台 Linux 服务器都必然开着的服务也是最容易被爆破的目标。就算防火墙默认拒绝了外来端口22 端口只要开着扫描器就一定会来试。我的建议分三步第一步修改 SSH 监听端口。编辑/etc/ssh/sshd_config把#Port 22改成Port 2222之类的非标准端口。改完之后要在防火墙里同步放行新端口firewall-cmd --permanent --zonepublic --add-port2222/tcp firewall-cmd --reload这里有个容易被忽略的坑在 RHEL 系发行版上SELinux 默认只允许 SSH 监听 22 端口如果你直接改端口SSH 服务可能起不来。需要把新端口加进 SELinux 的 SSH 端口类型semanage port -a -t ssh_port_t -p tcp 2222第二步限制 SSH 来源 IP。如果办公 IP 固定最好直接用富规则限定来源而不是对全网放行firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.168.1.0/24 port port2222 protocoltcp accept firewall-cmd --reload注意加了这条放行规则后原本public区域里对 SSH 服务的放行要记得移除否则限制来源就失去了意义。第三步禁止 root 直接登录。在sshd_config里设置PermitRootLogin no日常用普通用户登录后再sudo su -切换。这是性价比极高的防线能在源头上杜绝“root 弱口令被爆破”这类事故。2.3 用 fail2ban 联动 FirewallD 做自动化封禁只靠防火墙拦 IP 登录终究是静态的。攻击源 IP 频繁更换你得不停手动添加拒绝规则。这时候就该上 fail2ban 了。它监控日志里的登录失败记录达到阈值后调用 FirewallD 动态封禁来源 IP。在 RHEL 系发行版上fail2ban 的配置文件是/etc/fail2ban/jail.local可以参考以下内容[DEFAULT] ignoreip 127.0.0.1/8 ::1 192.168.1.0/24 banaction firewalld maxretry 5 findtime 600 bantime 3600 [sshd] enabled true filter sshd backend systemd action firewalld[port2222, protocoltcp]这里的action firewalld[...]会要求 fail2ban 调用firewall-cmd --add-rich-rule给触发告警的 IP 生成一条临时拒绝富规则。bantime是封禁时长单位秒我一般先设 1 小时观察效果确认无误后再调长。fail2ban 启动后可以用fail2ban-client status sshd查看当前被封禁的 IP 列表。有个细节提醒一下fail2ban 的backend systemd直接读取 journald 日志比监控/var/log/secure文件更不容易漏数据前提是 SSH 服务完整走 audit 日志输出所以建议优先用 systemd 后端。2.4 别忽视 ICMP 和协议层面的策略很多人配置防火墙只看 TCP/UDP 端口完全忽略 ICMPping、traceroute 等。我不建议把所有 ICMP 都禁止因为网络连通性排查也依赖它。但有些类型确实该关最典型的是ICMP redirect重定向它可能被用来操纵路由表属于一种协议层面的攻击面。关闭 ICMP redirectfirewall-cmd --permanent --zonepublic --add-icmp-blockredirect firewall-cmd --reload同时可以用firewall-cmd --permanent --zonepublic --remove-icmp-blockecho-request之类的方式按需保留 ping。这里的核心原则还是“最小化”不是把所有协议都理解成洪水猛兽而是只保留业务和运维必需的。3. 实时监控体系状态、日志、告警三步走3.1 一条命令快速掌握防火墙当前状态安全加固做完了接下来要让监控变成日常习惯。我的日常巡检命令是这些systemctl status firewalld --no-pager firewall-cmd --state firewall-cmd --get-active-zones firewall-cmd --list-all --zonepublic第一条看服务是否活着第二条看防火墙运行状态第三条看接口归属第四条看详细规则。这几条命令组合在一起10 秒内就能判断一台服务器的防火墙是否处于预期状态。如果用的是新版本 FirewallD底层基于 nftables还可以用nft list ruleset直接看内核里的完整规则链。注意这里的规则链和iptables -L不是一回事新版本下iptables命令看到的可能只是兼容层内容容易误判排查时优先用nft list ruleset。3.2 开启拒绝日志让扫描行为无处遁形FirewallD 有一个很实用但很多人不知道的参数——--set-log-deniedall。开启后所有被防火墙拒绝的数据包都会记录到系统内核日志中你可以清楚地看到谁在扫描你的服务器访问了哪些端口。firewall-cmd --permanent --set-log-deniedall firewall-cmd --reload查看拒绝日志journalctl -k --since today | grep -E DROP|REJECT日志会显示来源 IP、目标端口和协议比如这样的形态不同发行版格式略有差异kernel: DROP INeth0 OUT MAC... SRC203.0.113.10 DST198.51.100.2 LEN40 PROTOTCP SPT40000 DPT23看到大量来自同一 IP 对 23、3389、3306 等端口的请求基本可以判断是扫描器在试探可以直接用富规则封掉。不过这个日志开关有个副作用流量一大内核日志会被刷爆。我的建议是先开一段时间摸清扫描规律确认哪些 IP 该封哪些端口最常被试探再考虑是否永久开着。长期开的话务必配合日志轮转和保留策略一起做。3.3 规则漂移监控用脚本盯住“配置被偷偷改掉”比端口被扫描更危险的情况是防火墙配置不知不觉被改了。可能是运维同事临时排障后忘了恢复也可能是自动化脚本误操作。我习惯在每台服务器上放一个规则快照脚本通过 cron 定时比对规则是否发生漂移。脚本思路很简单把当前生效规则的哈希存下来下次执行时重新计算不一致就发告警。可以用以下脚本做基础版本#!/usr/bin/env bash STATE_FILE/root/.firewalld_state.sha256 SERVICEfirewalld # 检查服务是否存活 if ! systemctl is-active --quiet $SERVICE; then echo firewalld 未运行 | mail -s 防火墙宕机告警 adminexample.com fi # 检查规则是否漂移 firewall-cmd --list-all --zonepublic /tmp/firewalld_now.txt sha256sum /tmp/firewalld_now.txt /tmp/firewalld_now.sha256 if [ -f $STATE_FILE ]; then if ! cmp -s $STATE_FILE /tmp/firewalld_now.sha256; then echo 防火墙规则发生变化请登录确认 | mail -s 规则漂移告警 adminexample.com fi fi cp /tmp/firewalld_now.sha256 $STATE_FILE配合 cron 每小时执行一次基本上配置被改掉的 1 小时内就能发现。告警方式可以用mail也可以改成调用企业微信、飞书、Slack 之类 Webhook只要你本地有对应的工具就行。3.4 日志量太大怎么办轮转、过滤与采样拒绝日志开启之后最现实的问题就是日志增长太快。journald 默认会把内核日志和系统日志一起管理不设上限会持续积累到磁盘满。处理方案有两个第一限制 journald 占用的空间。编辑/etc/systemd/journald.conf设置SystemMaxUse500M MaxRetentionSec7day改完执行systemctl restart systemd-journald生效。这样日志最多占用 500MB超过 7 天的自动丢弃。第二过滤出真正有价值的告警。不要直接对着journalctl -k的滚动日志肉眼看而是用条件筛选journalctl -k --since 1 hour ago | grep DROP | awk {print $NF} | sort | uniq -c | sort -rn | head -20这条命令统计最近一小时来自不同端口的拒绝次数一眼就能看到哪些端口正在被高频试探。把这类查询做成每周定时邮件报告比天天盯终端轻松得多也不容易漏。4. 那些年踩过的坑Docker 冲突、IPv6 盲区和 reload 陷阱4.1 Docker 与 FirewallD为什么容器端口映射时好时坏服务器上跑着 Docker 的话FirewallD 和 Docker 的规则管理机制很容易打起来。Docker 默认会操作内核 iptables 链来创建端口映射的 DNAT 规则而新版 FirewallD 底层用的是 nftables。两套体系在规则链的处理上存在冲突的可能最典型的现象是防火墙执行了某些操作之后容器端口映射失效外部访问宿主机的 8080 端口指向的服务不通了。出现这种情况第一反应不是去动防火墙规则文件而是先检查 Docker 服务状态和规则链systemctl status docker --no-pager ss -tlnp | grep 8080如果宿主机监听没问题但外部访问不通大概率是 Docker 的 NAT 规则被防火墙重载影响了。最简单的恢复手段是重启 Docker 服务systemctl restart dockerDocker 重启后会把端口映射的规则重新写入内核一般能恢复。但从根本上讲生产环境里我建议在需要调整防火墙规则时优先用firewall-cmd --reload不要轻易用systemctl restart firewalld或--complete-reload后者清掉动态规则的概率大得多。如果 Docker 和 FirewallD 的冲突持续存在可以评估是否调整 Docker 的网络模式或者把 Docker 容器迁移到独立网段但这类改动要在维护窗口测试不要直接在业务高峰期操作。4.2 IPv6 是防火墙的盲区扫描一遍再下结论很多运维配置 FirewallD 的时候潜意识里只关心 IPv4。但 FirewallD 的规则默认同时作用于 IPv4 和 IPv6也就是说你给public区域放行的端口IPv6 流量一样能进。这里有个致命盲区如果服务器有 IPv6 地址但应用服务只监听在 IPv6 地址上而你的防火墙规则恰好默认放行了某个服务攻击者可能绕过 IPv4 防线从 IPv6 通道直接访问服务。我在一次内部审计中就遇到过某服务只绑了 IPv6 socket运维人员完全没意识到防火墙规则对 IPv6 同样生效结果服务毫无防护地暴露在公网。检查思路很简单ip -6 addr show ping6 -c 2 2001:db8::1确认服务器确实启用了 IPv6再去firewall-cmd --list-all里检查凡是明确只允许 IPv4 访问的规则必须在富规则里加上familyipv4限定。如果服务器不需要对外提供 IPv6 服务可以考虑在系统层面关闭 IPv6 或至少把默认区域规则改成只放行必要的 ICMPv6 类型。这里要特别提醒不要轻易禁止所有 ICMPv6IPv6 邻居发现等功能依赖它禁止过头会导致 IPv6 网络异常。4.3 reload 了为什么还不对运行时、永久与未保存的规则“我明明加了规则也 reload 了为什么还是不生效”这类问题的排查思路九成落在运行时与永久配置的差异上。举一个实际案例某开发者在public区域添加了 8080 端口的永久放行然后立即执行firewall-cmd --reload本地访问 8080 却不通。后来查出原因问题根本不在防火墙而是后端服务还没起来或者服务监听在127.0.0.1而不是0.0.0.0。防火墙的规则只是放行到端口的数据包如果服务本身没监听外面的包根本没有接收者。另一个常见原因是你添加的规则作用区不对。用--zoneinternal加了放行但网卡还在public那规则自然不生效。排查时先确认网卡和区域的关系再看服务监听状态然后看防火墙放行列表这个顺序基本能覆盖绝大多数“不生效”的场景。4.4 complete-reload 不是你想用就能用firewall-cmd --reload和firewall-cmd --complete-reload只差一个词后果天差地别。--reload会重新加载永久配置但不会彻底重启防火墙服务已建立的连接通常不会中断。而--complete-reload会完全停止并启动 firewalld期间所有基于用户态规则管理的动态规则都会清空重建已建立的连接也会断开。在生产服务器上--complete-reload基本等于一次有损操作。Docker 的动态映射、fail2ban 加进去的临时封禁规则都会在这次操作中丢得一干二净。执行完你还要靠其他手段把这些规则找回来反而制造了更多的管理噪音。我的原则是日常配置变更一律--reload确认需要彻底重置防火墙内核链的时候再在维护窗口执行--complete-reload。如果你连自己为什么会走到--complete-reload这一步都想不清楚那大概率不需要它。最后再分享一个我个人的操作习惯每次修改 FirewallD 配置之前我都会先做一份备份把/etc/firewalld/目录完整打包扔到/root/下文件名带上日期。这样不管发生了什么意外都能在两分钟内退回上一个状态。另外大规模变更前先在测试环境完整跑一遍操作流程验证规则顺序和 Docker 兼容性这比事后抢救有用得多。FirewallD 本身不复杂复杂的是你所在的服务器环境保持敬畏保持备份习惯就能少踩很多坑。
阅读完成 · 觉得有帮助?
咨询建站