限制非中国IP访问服务器端口
¶一、 项目背景与核心痛点
服务器经常遭受来自全球恶意 IP 的 SSH 暴力破解及 Web 扫描(如本案中记录的越南 IP 203.113.174.95 与 中国香港 IP 101.36.122.139)。
- 核心业务诉求:精细化控制服务器端口(SSH、HTTP、HTTPS 等),只允许中国大陆境内的 IP 建立连接,非中国大陆 IP 一律在系统最外层直接丢弃(DROP)。
- 技术底座环境:阿里云 ECS(CentOS 8+ / Rocky Linux 等现代发行版),物理层由
nftables(作为 Firewalld 的现代编译后端)承载,上层配合 Fail2ban 联动监控。
¶二、 核心机制剖析:为什么规则会失效?
在排查过程中,我们挖掘并解决了影响防火墙生效的两个关键底层逻辑:
- Firewalld 匹配优先级:
标准服务或端口放行(services: http https或ports: 22/tcp)的优先级高于富规则(Rich Rules)。如果在标准服务里允许了全球访问,底层的地理位置富规则将直接被跳过,导致拦截失效。 - Docker 绕过机制:
若服务运行在 Docker 中且使用-p映射,Docker 会绕过 Firewalld 直达iptables/nftables转发链,因此必须通过限制监听127.0.0.1配合宿主机 Nginx 转发才能使防火墙生效。
¶三、 完整实施方案
¶步骤 1:构建地理位置 IP 数据库
利用国内公共源定期下载中国大陆 IPv4 段,并将其打包导入名为 china_ip 的 ipset 集合中。# 下载 IP 段
wget -O /root/china_ip.txt https://clang.cn
# 创建 ipset 集合
firewall-cmd --permanent --new-ipset=china_ip --type=hash:net
# 导入数据
firewall-cmd --permanent --ipset=china_ip --add-entries-from-file=/root/china_ip.txt
¶步骤 2:彻底清洗冲突的标准放行规则
检查并清空默认 Zone(如 public)内全局放行的服务与端口:firewall-cmd --permanent --zone=public --remove-service=ssh
firewall-cmd --permanent --zone=public --remove-service=http
firewall-cmd --permanent --zone=public --remove-service=https
firewall-cmd --permanent --zone=public --remove-port=22/tcp
firewall-cmd --permanent --zone=public --remove-port=8081-8089/tcp
¶步骤 3:部署标准小写 not 拦截富规则
显式拉起针对业务端口与 SSH 端口(如本案中的 22)的防线,并允许全局大陆 IP 访问:# 核心自定义 SSH 端口防御
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source not ipset="china_ip" port port="22" protocol="tcp" drop'
# 业务 Web 端口与其它端口防御
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source not ipset="china_ip" port port="80" protocol="tcp" drop'
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source not ipset="china_ip" port port="443" protocol="tcp" drop'
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source not ipset="china_ip" port port="8081-8089" protocol="tcp" drop'
# 兜底大陆白名单通行规则
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source ipset="china_ip" accept'
# 重新加载生效
firewall-cmd --reload
¶四、 自动化维护脚本 (Crontab)
由于全球 IP 分配并非一成不变,编写如下自动化脚本 /root/update_china_ip.sh 确保 IP 库的时效性(自动保持防线不失效且不误伤新分配的国内 IP):
URL="https://clang.cn"
TMP_FILE="/tmp/china_ip_new.txt"
IPSET_NAME="china_ip"
# 1. 容错下载
curl -s -f -o "$TMP_FILE" "$URL" || wget -q -O "$TMP_FILE" "$URL"
if [ ! -s "$TMP_FILE" ]; then
echo "[$(date)] 错误: 无法下载 IP 列表,放弃更新。"
exit 1
fi
# 2. 清洗数据
sed -i '/^$/d; s/[[:space:]]//g' "$TMP_FILE"
# 3. 动态更新运行时与永久配置
firewall-cmd --ipset="$IPSET_NAME" --clear-entries
firewall-cmd --ipset="$IPSET_NAME" --add-entries-from-file="$TMP_FILE"
firewall-cmd --permanent --ipset="$IPSET_NAME" --clear-entries
firewall-cmd --permanent --ipset="$IPSET_NAME" --add-entries-from-file="$TMP_FILE"
# 4. 重载生效
firewall-cmd --reload
rm -f "$TMP_FILE"
echo "[$(date)] 更新成功!当前集合内 IP 段数量: $(firewall-cmd --ipset=$IPSET_NAME --get-entries | wc -l)"
配置定时任务 (crontab -e):# 每月 1 号凌晨 3:00 自动维护更新并输入日志
0 3 1 * * /bin/bash /root/update_china_ip.sh >> /var/log/update_china_ip.log 2>&1
¶五、 状态验证与防线断定
¶1. Firewalld 应用层状态确认
执行 firewall-cmd --zone=public --list-all 后,预期正确输出结构如下:public (active)
target: DROP
services: dhcpv6-client
ports:
rich rules:
rule family="ipv4" source not ipset="china_ip" port port="22" protocol="tcp" drop
rule family="ipv4" source not ipset="china_ip" port port="80" protocol="tcp" drop
rule family="ipv4" source not ipset="china_ip" port port="443" protocol="tcp" drop
rule family="ipv4" source not ipset="china_ip" port port="8081-8089" protocol="tcp" drop
rule family="ipv4" source ipset="china_ip" accept
¶2. 内核底层 nftables 编译态核验
执行 nft list table inet firewalld | grep -E "22|china_ip",其输出表明内核完全接受了该网络阻断逻辑:set china_ip { ... }
ip saddr != @china_ip tcp dport 22 drop
ip saddr != @china_ip tcp dport 80 drop
ip saddr != @china_ip tcp dport 443 drop
ip saddr != @china_ip tcp dport 8081-8089 drop
ip saddr @china_ip accept
¶3. 最终拦截效果
- 海外/港澳台 IP 试图发起 TCP 连接:直接在系统内核层被丢弃(DROP),客户端表现为连接超时。Fail2ban 不会再收到任何有关此类 IP 的扫描和 Found 爆破日志。
- 中国大陆境内 IP 连接:顺利匹配
accept链,无感通行所有安全服务。
此技术方案目前已在你的服务器上成功闭环运行,底层状态显示非常健康。如果未来需要添加新端口或迁移到新服务器,直接按照“第三章”的规范模板即可快速复现。