ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

firewalld端口与IP白名单配置实战:从开放端口到精准管控

firewalld端口与IP白名单配置实战:从开放端口到精准管控 前阵子线上MySQL告警安全扫描发现3306端口对全网段开放。这个库本来只给办公网和几台应用服务器用结果防火墙规则里白纸黑字写着--add-port3306/tcp等于把数据库裸奔在公网上。改配置的时候我又发现团队里不少人对firewalld的白名单规则理解还停留在“开放端口”这一步一旦要限制来源IP就开始各种临时拼命令甚至有人手动去改iptables导致firewalld重启之后规则被清空。这篇就把我在CentOS 7上用firewalld做IP与端口白名单的完整配置经验整理出来从基本概念到底层逻辑、从单条命令到生产环境排错一次讲透。1. firewalld的核心机制zone、service与端口之间到底怎么联动1.1 为什么你总听到“默认zone”它决定了规则写在哪很多人刚接触firewalld时最困惑的就是zone区域这个概念。简单说zone就是一套规则的集合而每张网卡、每个网络接口都会被划分到某个zone里。CentOS 7安装完后默认的zone是public这也是绝大多数人操作firewalld时实际生效的那套规则。查看当前默认zone和网卡归属情况的命令很简单firewall-cmd --get-default-zone firewall-cmd --get-active-zones firewall-cmd --list-all注意--list-all不带zone参数时列出的就是默认zone的规则。如果你的服务器有多个网卡比如eth0是公网、eth1是内网生产环境通常会把它们划分到不同zone比如public和internal然后分别施加规则。只看默认zone很容易漏掉另一半规则这是排查“端口明明放了却连不上”时的第一个盲区。# 把eth1划入internal zone firewall-cmd --zoneinternal --change-interfaceeth1 # 永久生效 firewall-cmd --permanent --zoneinternal --change-interfaceeth11.2 service、port、rich rule三种规则的本质区别firewalld里有三种常用的规则载体service、port、rich rule。它们的定位完全不同service一组端口的逻辑抽象比如ssh服务对应22端口http服务对应80端口。使用service的好处是语义清晰不用记端口号还方便统一管理。port直接指定端口号或端口范围比如8080/tcp、1000-2000/tcp。它的粒度比service细但只能限定“端口协议”不能限定来源IP。rich rule一种更底层的规则语言可以同时指定来源地址、目标端口、协议、动作accept/reject/drop还能附加日志记录。IP与端口白名单的核心场景基本都要靠rich rule来实现。用一个生活化的类比service是“餐厅套餐”port是“单点菜”rich rule则是“指定谁可以进包厢谁只能在门外等”。前两者解决“开不开放”的问题rich rule解决“对谁开放、对谁拒绝”的问题。2. 开放端口的几种写法以及--permanent这个参数的真实代价2.1 最常用的三条开放端口命令选哪条要看场景给你一个端口比如8080如果要放行TCP流量你会看到网上有这几种写法firewall-cmd --add-port8080/tcp firewall-cmd --permanent --add-port8080/tcp firewall-cmd --zonepublic --add-port8080/tcp --permanent三条命令效果有差异。第一条只临时放行firewalld重启或者reload之后规则就没了第二条写入了永久配置但不会立刻生效到当前运行环境第三条是指定zone的永久放行最明确但最啰嗦。问题就出在“永久”两个字上。很多人以为加了--permanent规则立刻就生效实际上它只是把规则写进了/etc/firewalld/zones/public.xml当前运行中的防火墙规则并没有变化。只有执行firewall-cmd --reload永久配置才会真正加载进来。我遇到过不止一次这样的情况同事提交工单说“端口已经放开了”结果服务根本连不上。查下来发现他执行了--permanent --add-port但忘了reload。更隐蔽的问题是如果此时服务器重启firewalld会加载永久配置端口反而“莫名”开放了——这个时间差在排查问题时很迷惑人。最稳妥的做法是两条命令配合使用先用不带--permanent的命令立即放行验证确认服务正常后再执行带--permanent的写盘命令最后reload一次。这样既不影响当前业务也能保证重启后规则还在。firewall-cmd --add-port8080/tcp firewall-cmd --permanent --add-port8080/tcp firewall-cmd --reload2.2 端口范围与协议选择别把UDP漏了需要一次性放行连续端口时用短横线连接起始端口和结束端口firewall-cmd --add-port1000-2000/tcp这里有两个容易忽略的点。第一如果服务同时用到TCP和UDP比如DNS的53端口、某些音视频通信端口需要分别放行两种协议firewall-cmd --add-port53/tcp firewall-cmd --add-port53/udp第二端口范围的规则在生产环境要特别谨慎范围太大等于给攻击者留了一整片探索空间。我见过有人在服务器上开放1-65535/tcp来排查问题结果忘了删安全扫描一抓一个准。临时排障可以务必事后收敛。2.3 查看和移除规则的完整命令开放端口是第一步学会回收端口才能真正管好白名单。常用命令如下# 查看当前zone放行的所有端口 firewall-cmd --list-ports # 查看当前zone的全部规则含service、port、rich rule firewall-cmd --list-all # 查看所有rich rule firewall-cmd --list-rich-rules # 移除端口放行临时 firewall-cmd --remove-port8080/tcp # 移除端口放行永久需要reload生效 firewall-cmd --permanent --remove-port8080/tcp firewall-cmd --reload如果是通过service放行的端口要用--list-services查看--list-ports是看不到的。这个细节经常让人误判“明明没开放为什么端口通着”。3. IP白名单的实现从一条rich rule到完整配置模板3.1 最经典的场景3306端口只允许192.168.1.0/24访问回到文章开头的MySQL案例。需求看起来很简单数据库服务器上3306端口只允许内网192.168.1.0/24网段的机器访问其他地址一律拒绝。很多人第一反应是先放行3306端口再用rich rule拒绝其他IP。但这里有个关键认知要先纠正——firewalld的默认策略就是拒绝未放行的端口。也就是说你什么都不做的时候外部根本连不上3306。要达到“只允许指定网段访问”只需要添加一条允许规则就够了不需要再额外添加拒绝规则。真正完整的配置只需两步。第一步添加rich rule放行指定网段对3306的访问firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.1.0/24 port protocoltcp port3306 accept firewall-cmd --reload第二步验证规则是否生效firewall-cmd --list-rich-rules看到类似下面的输出就说明配置成功了rule familyipv4 source address192.168.1.0/24 port port3306 protocoltcp accept此时从192.168.1.0/24网段的机器连接3306是通的从其他IP连接则是被拒绝的状态。不需要额外写drop规则默认策略已经帮我们兜底了。3.2 rich rule语法逐段拆解看懂才能灵活改上面那条rich rule看起来像天书拆开就很好理解。以rule familyipv4 source address192.168.1.0/24 port protocoltcp port3306 accept为例关键字含义示例值rule声明这是一条自定义规则固定写法family地址族ipv4也可以写ipv6source address来源IP或网段192.168.1.0/24port protocol协议类型tcp、udp、icmp等port目标端口3306accept匹配后的动作accept/reject/drop动作的区别很重要accept是放行reject是拒绝并返回错误信息连接方会立刻收到“连接被拒”drop是直接把包丢弃连接方会一直卡在超时等待上。从安全角度drop更隐蔽不给攻击者反馈信息从排障角度reject更容易定位问题。生产环境一般推荐对非法来源用drop但如果你需要快速判断“防火墙是否拦了我”reject的报错更直观。如果需要允许单个IP把网段换成具体地址就行firewall-cmd --permanent --add-rich-rulerule familyipv4 source address203.0.113.10 port protocoltcp port22 accept如果需要允许多个分散的IP可以逐条添加rich rule。规则之间是或的关系匹配任意一条就会放行。但如果IP数量很多逐条写会让配置变得臃肿后续维护也不方便这种情况更推荐用ipset后面会专门讲。3.3 反向需求端口对全网开放唯独屏蔽某个IP还有些场景是“端口默认对所有人开放但某个IP搞破坏要单独屏蔽”。比如网站80端口面向公网正常服务但某个来源一直在刷接口需要单独拉黑。这时候先放行端口再针对该IP添加一条drop规则# 第一步放行80端口 firewall-cmd --permanent --add-port80/tcp # 第二步屏蔽恶意来源IP的80端口访问 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address198.51.100.66 port protocoltcp port80 drop firewall-cmd --reload这里的关键是理解规则的匹配顺序。firewalld的rich rule并不是按照添加顺序逐条匹配、命中即停的它内部会统一评估。实际经验是当同时存在accept和drop规则时更精确的匹配规则优先生效。比如上面的配置中来自198.51.100.66的请求会命中drop规则被丢弃其他IP的请求命中accept规则被放行。这也是为什么“先放行再屏蔽”能够正常工作。如果你担心规则顺序问题可以加一条日志规则来验证firewall-cmd --permanent --add-rich-rulerule familyipv4 source address198.51.100.66 port protocoltcp port80 log prefixBLOCKED_BAD_IP levelinfo drop加log之后匹配到的流量会记录到系统日志中既方便确认规则是否生效也为后续分析攻击来源留了证据。3.4 组合场景同一台服务器上的差异化白名单策略实际生产环境中很少只有一个端口需要白名单。这里分享一个我常用的配置模板假设场景是Nginx服务器80端口对公网完全开放443端口对公网开放但8888端口的管理后台只允许办公网203.0.113.0/24访问而22端口SSH只允许跳板机203.0.113.5访问。# 放行80和443面向公网 firewall-cmd --permanent --add-port80/tcp firewall-cmd --permanent --add-port443/tcp # 管理后台8888端口只对办公网段开放 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address203.0.113.0/24 port protocoltcp port8888 accept # SSH只允许跳板机IP firewall-cmd --permanent --add-rich-rulerule familyipv4 source address203.0.113.5 port protocoltcp port22 accept firewall-cmd --reload这套模板的优点是结构清晰每个端口的开放范围一眼就能看懂。后续新增来源IP只需再添加一条对应的rich rule移除权限删除对应规则即可。比把所有规则堆在一起再用--list-all去猜要省心得多。4. 白名单配置不生效的排查链路从firewalld自身到SELinux再到连接层4.1 第一层先确认firewalld规则真的如你所想配置完白名单后连不上第一反应应该是回看规则本身。首选的排查命令是firewall-cmd --list-all firewall-cmd --list-rich-rules重点看三件事规则是否出现在输出里、所在的zone是否是当前网卡实际使用的zone、来源IP/端口是否写对了。我遇到过不少次“眼睛盲区”——规则写得没错但写进了internal zone而网卡实际挂在public zone自然不生效。还有个隐蔽问题是多网卡服务器的“默认zone陷阱”。firewall-cmd --add-port8080/tcp这条命令本身只操作默认zone如果业务流量走的是非默认zone的网卡规则就加错了地方。所以生产环境里我习惯每条命令都显式指定zone比如--zonepublic --add-port8080/tcp宁可多打几个字也不给排查留隐患。4.2 第二层确认服务真的在监听你放的端口防火墙规则没问题时下一个嫌疑是服务本身没在监听。用下面的命令检查端口状态ss -lntp | grep 3306重点看Local Address那列。如果显示的是127.0.0.1:3306说明服务只监听了本机回环地址外部流量根本到不了这个端口防火墙放不放行都一样连不上。这种情况要改服务配置让MySQL监听0.0.0.0:3306或指定内网网卡IP。另外也要确认服务没有监听在其他端口上。比如你按默认端口放行了3306但MySQL实际配置改了端口自然连不通。这类问题靠ss -lntp一眼就能看穿。4.3 第三层SELinux在背后有没有捣乱CentOS 7默认开启SELinux它会独立于防火墙对进程的网络访问做限制。表现症状是防火墙规则看着全对、端口也在监听但外部连接就是不通或者服务启动时报告“Permission denied”。先查看SELinux状态getenforce如果输出是Enforcing那就要考虑是否为端口添加SELinux放行策略。以自定义SSH端口2222为例SELinux默认只放行22端口改端口后需要执行# 查看当前SSH相关的SELinux端口标签 semanage port -l | grep ssh # 为2222端口添加ssh_port_t标签 semanage port -a -t ssh_port_t -p tcp 2222注意semanage命令需要安装policycoreutils-python包yum install -y policycoreutils-python快速验证是不是SELinux的问题可以临时将其设为permissive模式setenforce 0如果此时服务立刻能通了那基本可以确定是SELinux拦截。确认之后建议不要长期关闭SELinux而是按上面的方式为具体端口添加策略这样既解决问题又保留安全防护。4.4 第四层验证工具的选择与误区规则、监听、SELinux都查完了还没解决就要回头审视你的验证方式。本机验证和远程验证是完全两回事。在服务器本机执行telnet 127.0.0.1 3306能通只能说明服务在本机正常不能说明防火墙白名单配置成功。因为有些流量在本地回环上根本不会经过防火墙的完整链路。正确的验证方式是从外部一台白名单内的机器执行telnet 服务器IP 3306或者用ncnc -vz 服务器IP 3306批量扫描端口时用nmap更直观nmap -p 3306 服务器IP这里还要注意一个环境差异如果服务器跑在云上除了操作系统里的firewalld云平台的安全组/防火墙规则也必须同步放行对应端口。遇到过太多“firewalld规则全对但安全组没放行”的情况。云厂商控制台的安全组规则和系统内部防火墙是两道独立关卡必须同时通过才行。5. 几个被问过无数次的细节自定义服务、持久化与日常验证5.1 把常用端口封装成自定义service多端口管理更优雅上面所有示例都是直接操作端口号这在规则少的时候没问题。但如果你的业务端口很多比如一个应用要同时放行8080、8081、8082三个端口每次加规则都要写三条命令配置文件也显得凌乱。更优雅的做法是自定义一个service。在/etc/firewalld/services/目录下创建一个XML文件比如myapp.xml?xml version1.0 encodingutf-8? service shortMyApp/short descriptionMy application service ports/description port protocoltcp port8080/ port protocoltcp port8081/ port protocoltcp port8082/ /service保存后重载firewalld然后直接用service名放行firewall-cmd --reload firewall-cmd --permanent --add-servicemyapp firewall-cmd --reload这样做的最大好处是语义化。以后再看到--list-services输出里有myapp团队任何人都能明白这个服务对应哪些端口而不是面对一串数字去猜测用途。5.2 规则持久化的真正含义runtime与permanent的区别firewalld有runtime和permanent两套配置状态。runtime是当前内存中生效的规则permanent是落盘到/etc/firewalld/zones/下的XML文件。两者的关系可以这样理解runtime像程序的当前进程状态permanent像配置文件。改配置后要重启进程才生效对应reload而进程运行中的临时修改如果不写回配置重启后一切归零。一个实用的技巧是--runtime-to-permanent参数。当你用不带--permanent的命令做了大量临时调整验证全部通过后一条命令就能把当前所有runtime规则固化为永久配置firewall-cmd --runtime-to-permanent这比手工逐条添加--permanent要省事得多还能避免“临时规则和永久规则不一致”的混乱状态。还要提醒的是不要手动去修改/etc/firewalld/zones/public.xml除非你非常清楚XML格式。手动编辑容易导致格式错误轻则规则加载失败重则firewalld服务直接起不来。建议一律通过firewall-cmd命令来修改。5.3 底层关系firewalld讲的是nftables的语言CentOS 7的firewalld底层其实是iptablesCentOS 7之后的版本开始转向nftables。但无论底层怎么变用户都不应该直接去改iptables规则。原因很简单firewalld维护着自己的规则状态你手动加的iptables规则在firewalld reload时可能被清掉反过来firewalld生成的规则也可能和手动规则冲突。可以用iptables -L -n查看当前生效的内核规则但只建议看不建议动。排查问题时的正确姿势永远是回到firewalld命令。5.4 生产环境中的白名单规范化建议最后分享几个我在生产环境踩过坑后沉淀下来的习惯第一维护一份端口与IP白名单对应表。哪怕只是个Markdown表格记录每个端口为什么开放、对谁开放、负责人是谁、申请日期是什么。线上环境人员流动频繁半年后没人说得清这条规则是谁加的有文档至少能追溯。第二变更前备份配置。每次修改规则前先复制一份当前配置文件cp /etc/firewalld/zones/public.xml /etc/firewalld/zones/public.xml.bak.$(date %Y%m%d%H%M%S)万一改错了回滚只需要覆盖文件并reload比在新规则上继续修补要快得多。第三变更遵循“先加后删”原则。替换白名单时先添加新规则并确认服务正常再删除旧规则避免中间出现权限空窗期。比如要把某个IP从允许列表换成另一个IP先添加新IP的allow规则确认新IP能正常访问后再删掉旧IP的规则。这个顺序能防止误删导致的管理入口丢失。第四定期审计。每隔一段时间用firewall-cmd --list-all和firewall-cmd --list-rich-rules导出规则和文档里的白名单表对照清理掉那些早已无人使用的放行规则。安全是持续运营出来的不是配置一次就完事的。今天讲的这些命令和思路覆盖了CentOS 7上firewalld最核心的使用场景从理解zone机制到正确放行端口再到用rich rule实现精细的IP白名单最后是完整的排错路径。这套方法论在CentOS 8、Rocky Linux、AlmaLinux这些同样使用firewalld的系统上也能直接复用区别只在于底层规则引擎换成了nftables但firewall-cmd的命令语法还是一致的。最后说一个我自己的小习惯每次提交完防火墙变更我都会顺手把public.xml复制一份带时间戳的备份。这个动作救过我两次——一次是误删rich rule导致管理端口全断另一次是reload之后发现规则顺序不对。firewalld的XML配置看着简单但线上环境里规则一多回滚能力比什么都重要。
返回列表