限制非中国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 联动监控。

二、 核心机制剖析:为什么规则会失效?

在排查过程中,我们挖掘并解决了影响防火墙生效的两个关键底层逻辑:

  1. Firewalld 匹配优先级
    标准服务或端口放行(services: http httpsports: 22/tcp)的优先级高于富规则(Rich Rules)。如果在标准服务里允许了全球访问,底层的地理位置富规则将直接被跳过,导致拦截失效。
  2. 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):

#!/bin/bash
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 链,无感通行所有安全服务。

此技术方案目前已在你的服务器上成功闭环运行,底层状态显示非常健康。如果未来需要添加新端口迁移到新服务器,直接按照“第三章”的规范模板即可快速复现。