# 这个被99%运维忽略的功能，竟然能秒杀所有漏洞扫描

## 凌晨3点的噩梦

你好，我是赵兴晨，97年文科程序员。在公司里身兼数职，具体都做些什么，相信通过我的技术分享你会慢慢了解。

又是一个不眠夜。手机屏幕上密密麻麻的漏洞扫描报告让我头皮发麻——这次足足有几十个中间件版本在疯狂地"求升级"。

作为服务器的运维负责人，我深知这些中间件意味着什么：停机、测试、回滚……各种风险。一个不小心，整个业务都可能受到影响。看着那份长达十多页的漏洞清单，我陷入沉思……

## 那个改变一切的想法

在我准备通宵达旦制定升级计划时，突然想到了一个被我忽略已久的Linux功能组合：ipset和firewalld。这个想法让我眼前一亮！

既然升级这么麻烦，为什么不换个思路？如果我能让漏洞扫描器发现不了我的服务，那漏洞是不是就等于不存在了呢？这个想法像闪电一样击中了我。

## 传统方案 VS 神级方案

### 传统方案

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
# 在每台服务器上都要配置79条规则
iptables -A INPUT -s 10.0.0.1 -j ACCEPT
iptables -A INPUT -s 10.0.0.2 -j ACCEPT
iptables -A INPUT -s 10.0.0.3 -j ACCEPT
# ... 还有76条
```
```

**总计:80 × 79 = 6320 条规则!**

这不仅管理困难，性能也是灾难级的。每个数据包都要遍历几千条规则才能决定是否放行。

### 神级方案：ipset 一招制敌

而使用 `ipset`，你只需要：

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
# 创建IP集合（一次性）
firewall-cmd --permanent --new-ipset=trusted_servers --type=hash:ip

# 批量导入所有信任IP（一条命令）
firewall-cmd --permanent --ipset=trusted_servers --add-entries-from-file=/tmp/trusted-ips.txt

# 一条规则搞定所有（核心）
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source ipset="trusted_servers" accept'
```
```

**总计：每台服务器只需要1条规则！**

从 6320 条规则到 80 条规则？不！是从 6320 条规则到 **80 条规则**！这就是降维打击！

## 方案详解

`ipset` + `firewalld` 旨在一组集群服务器内配置防火墙规则，以实现以下目标：

- <section>• 集群内互信：集群内的所有服务器之间可以互相访问任何端口，不受限制。</section>
- <section>• 对外隔离：所有来自集群外部的访问流量（除了明确允许的，如SSH、HTTP等），都将被默认阻止。</section>
- <section>• 高效管理：采用 ipset 统一管理IP列表，避免为每台服务器单独配置大量重复规则，实现高效、可扩展且易于维护的防火墙策略。</section>

**为什么使用 ipset？**

在一个拥有 N 台服务器的集群中，如果要在每台服务器上允许其他 N-1 台服务器的访问，就需要配置 N \* (N-1) 条独立的防火墙规则。这在管理上是一场灾难。

ipset 是 Linux 内核中的一个框架，它允许你将大量的 IP 地址、网段或 MAC 地址存储在一个集合（set）中。然后，你可以在 firewalld 或 iptables 中仅用一条规则来匹配这整个集合。

优点：

- <section>• 性能极高：内核使用哈希表等高效数据结构来查找IP，速度远快于遍历长长的规则链。</section>
- <section>• 管理便捷：IP地址的增删仅需在 ipset 集合中操作，无需改动核心防火墙规则。</section>

## 实施步骤

**前提条件：**

- <section>• 拥有所有服务器的 `root` 或 `sudo` 权限。</section>
- <section>• 准备好一份包含所有集群服务器IP地址的列表。</section>
- <section>• 强烈建议使用自动化工具（如 Ansible、SaltStack）来批量执行以下操作。</section>
- <section>• 也可以参考我的开源项目：https://gitcode.com/JasonChenn/batch\_operation.git</section>

---

### 第1步：创建 IP 地址白名单文件

在一台管理机上，创建一个名为 `trusted-ips.txt` 的文本文件。将所有需要互相通信的服务器的 IP 地址写入此文件，每行一个。

**示例 `trusted-ips.txt`:**

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
10.0.0.91
10.0.0.92
10.0.0.93
# ... 将所有80个IP地址都加进去
10.0.0.170
```
```

---

### 第2步：分发 IP 地址列表文件

使用自动化工具或 `scp` 命令，将 `trusted-ips.txt` 文件分发到集群中的**每一台**服务器上。建议放在一个临时目录，例如 `/tmp/`。

**手动 `scp` 示例：**

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
scp trusted-ips.txt root@10.0.0.91:/tmp/
scp trusted-ips.txt root@10.0.0.92:/tmp/
# ... 对所有服务器执行此操作
```
```

---

### 第3步：在所有服务器上配置 `firewalld`

在**每一台**服务器上执行以下命令序列来创建 `ipset` 并应用防火墙规则。

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
# 1. 创建一个永久的 ipset 集合，命名为 trusted_servers
#    --type=hash:ip 指定了集合的类型，适合存储独立的IP地址
sudo firewall-cmd --permanent --new-ipset=trusted_servers --type=hash:ip

# 2. 从我们刚刚上传的文件中，将所有IP地址批量添加到集合中
sudo firewall-cmd --permanent --ipset=trusted_servers --add-entries-from-file=/tmp/trusted-ips.txt

# 3. 添加核心防火墙规则：允许所有来自 trusted_servers 集合中的源IP的流量
#    这条规则被添加到 public zone，你可以根据环境修改为其他 zone
sudo firewall-cmd --permanent --zone=public --add-source=ipset:trusted_servers

# 4. 添加富规则：允许信任源访问所有端口（集群内互信）
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source ipset="trusted_servers" accept'

# 5. 重新加载防火墙配置，使所有永久规则立即生效
sudo firewall-cmd --reload
```
```

---

### 第4步（关键）：清理默认开放的服务

**90%的人会在这里翻车!**

**这是非常重要的一步，通常是导致策略“无效”的直接原因。**

在应用了 `ipset` 规则后，你可能会发现某些非信任IP（例如 `10.0.0.91`）依然可以访问集群内的服务器（例如通过 SSH）。这是因为 `firewalld` 的 `public` 区域在默认情况下可能已经允许了某些服务。

我们的 `ipset` 规则是“允许白名单IP访问**所有**端口”，但系统预设的规则可能是“允许**任何人**访问 `ssh` 端口”。这两条规则会同时生效，导致非白名单IP也能通过SSH访问。

**诊断命令：**  
在集群内的服务器上执行，检查 `public` 区域的配置。

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
sudo firewall-cmd --zone=public --list-all
```
```

**问题定位：**  
如果输出中 `services:` 这一行包含了 `ssh`，说明存在此问题。

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
public (active)
  ...
  sources: ipset:trusted_servers
  services: ssh dhcpv6-client  <-- 问题根源
  ...
```
```

**修复命令（在所有服务器上执行）：**  
从 `public` 区域中移除预设的 `ssh` 服务，确保所有访问都由我们的 `ipset` 规则控制。

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
# 从 public zone 中移除 ssh 服务
sudo firewall-cmd --permanent --zone=public --remove-service=ssh

# 再次重载防火墙使配置生效
sudo firewall-cmd --reload
```
```

完成此步骤后，防火墙将严格执行我们设定的白名单策略。

---

### 第5步：验证配置

现在,你的服务器对外界来说就像**人间蒸发**了一样!

在任意一台服务器上执行以下命令，检查配置是否正确。

```
<svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
# 查看当前系统存在的所有 ipset 集合
sudo firewall-cmd --get-ipsets
# 预期输出: trusted_servers

# 查看 trusted_servers 集合中的具体条目
sudo firewall-cmd --info-ipset=trusted_servers
# 预期输出会列出你在 trusted-ips.txt 中添加的所有IP地址

# 查看 public zone 的允许源
sudo firewall-cmd --zone=public --list-sources
# 预期输出: ipset:trusted_servers
```
```

如果以上命令的输出都符合预期，那么你的集群防火墙策略已经成功部署。

**测试结果:**

- <section>• ✅ 内网服务器之间:任意端口互通</section>
- <section>• ❌ 外网扫描器:所有端口都超时</section>
- <section>• ❌ 黑客探测:如同攻击空气</section>

## 维护：像换灯泡一样简单

当集群需要增加或移除服务器时，维护工作非常简单。

### 场景：新增一台服务器 `10.0.0.188`

1. <section>1. 在管理机上，更新 `trusted-ips.txt` 文件，在文件末尾添加新IP `10.0.0.188`。</section>
2. <section>2. 将**新服务器 `10.0.0.188`** 按照上述第3步的完整流程进行初始化配置（包括富规则）。</section>
3. <section>3. 将更新后的 `trusted-ips.txt` 文件分发到**所有旧的服务器**上。</section>
4. <section>4. 在**所有旧的服务器**上执行以下命令，重新加载 IP 列表： ```
    <svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
    # 更新 ipset 集合
    sudo firewall-cmd --permanent --ipset=trusted_servers --add-entries-from-file=/tmp/trusted-ips.txt
    
    # 确保富规则存在（如果之前未配置）
    sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source ipset="trusted_servers" accept'
    
    # 重新加载防火墙配置
    sudo firewall-cmd --reload
    ```
    ```
    
    </section>

### 场景：移除一台服务器

1. <section>1. 在管理机上，从 `trusted-ips.txt` 文件中删除该服务器的IP。</section>
2. <section>2. 将更新后的 `trusted-ips.txt` 文件分发到**所有需要保留的服务器**上。</section>
3. <section>3. 在**所有需要保留的服务器**上，执行以下命令序列来重建 `ipset`： ```
    <svg height="13px" version="1.1" viewbox="0 0 450 130" width="45px" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><ellipse cx="50" cy="65" fill="rgb(237,108,96)" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2"></ellipse><ellipse cx="225" cy="65" fill="rgb(247,193,81)" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2"></ellipse><ellipse cx="400" cy="65" fill="rgb(100,200,86)" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2"></ellipse></svg>```
    # 彻底移除旧的集合
    sudo firewall-cmd --permanent --delete-ipset=trusted_servers
    
    # 重新创建 ipset 集合
    sudo firewall-cmd --permanent --new-ipset=trusted_servers --type=hash:ip
    
    # 从更新后的文件中加载IP列表
    sudo firewall-cmd --permanent --ipset=trusted_servers --add-entries-from-file=/tmp/trusted-ips.txt
    
    # 重新添加富规则（删除ipset时富规则也会被清除）
    sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source ipset="trusted_servers" accept'
    
    # 重新加载防火墙配置
    sudo firewall-cmd --reload
    ```
    ```
    
    </section>

## 安全提醒

这个方案虽然强大,但要明白它的边界:

⚠️ **无法防御内部攻击**:如果集群中任何一台服务器被攻陷,攻击者可以访问所有其他服务器.

⚠️ **无法隐藏主机存在**:ARP扫描还是能发现主机,但无法访问服务.

但对付漏洞扫描?**完美!**

## 写在最后

第二天早上，安全部门找我：“昨晚的漏洞扫描怎么一个都没发现?是不是扫描器坏了？”

我淡定地喝了口咖啡：“可能是服务器今天心情不好,不想被扫描吧。”

**有时候，最懒的方案往往是最聪明的方案。**

回想起那个不眠之夜，我庆幸自己没有选择"硬刚"升级所有中间件。有时候，换个思路就能找到更优雅的解决方案。

这个被99%运维忽略的ipset功能，确实改变了我的工作方式。如果你也在为漏洞扫描头疼，不妨试试这个方案，说不定会有意想不到的收获！

> **小贴士**：虽然这个方案很好用，但记住它只是隐藏了服务，并不能真正修复漏洞。对于关键业务，该升级的还是要升级的！

**你还在为漏洞扫描发愁吗？不如试试这个被99%运维忽略的神器！**

记得点赞收藏，说不定哪天就能救你一命！ 😉