# 15.1 Fail2Ban 配置（基于 IPFW、PF 与 IPF）

在网络安全实践中，暴力破解（brute-force attack）是针对身份认证系统的常见威胁。Fail2Ban 作为一种基于日志的入侵防御系统（Intrusion Prevention System, IPS），通过动态更新防火墙规则实现自适应访问控制，为服务器提供实时防护。

根据 [官方说明](https://github.com/fail2ban/fail2ban)，Fail2Ban 可封禁多次身份验证失败的主机，[即通过更新系统防火墙规则来拒绝来自这些 IP 地址的新连接](https://github.com/fail2ban/fail2ban/wiki/How-fail2ban-works)。Fail2Ban 几乎完全由 Python 实现（Python 代码占比约 96%）。本文适配了 FreeBSD 中常见的三种防火墙——IPFW、PF 与 IPF，读者无需同时配置所有防火墙，只需选择其中一款即可。

## 安装 Fail2Ban

Fail2Ban 的安装过程如下，用户可选择使用 pkg 或 Ports 进行安装。

* 使用 pkg 安装：

```sh
# pkg install security/py-fail2ban
```

* 或者使用 Ports 安装：

```sh
# cd /usr/ports/security/py-fail2ban/ 
# make install clean
```

安装完成后，需启用服务以实现开机自动启动。

* 启用 fail2ban 服务：

```sh
# service fail2ban enable
```

## 查看 Fail2Ban 安装后的说明

安装完成后，可通过以下命令查看软件包提供的配置说明。

```sh
# pkg info -D security/py-fail2ban
py311-fail2ban-1.1.0_1:
On install:
Please do not edit the fail2ban.conf, jail.conf, or any other
files in the distribution as they will be overwritten upon each
upgrade of the port. Instead, create new files named *.local e.g.
fail2ban.local or jail.local.
# 请不要直接修改 fail2ban.conf、jail.conf 或其他官方提供的配置文件
# 因为它们在每次升级软件包时会被覆盖
# 应该创建 *.local 文件，如 fail2ban.local 或 jail.local，来自定义配置。

For more information, see the official manual:
http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Configuration
# 更多信息可参见官方手册。

If you have custom filters or actions and you are upgrading from
0.9.x please check them.
# 如果你定义了自定义过滤器或操作，且从 0.9.x 升级而来，请检查其兼容性。

Users of pf: please read the notes in action.d/pf.conf and the
discussion at https://github.com/fail2ban/fail2ban/pull/1925
# 使用 pf 防火墙的用户请阅读 action.d/pf.conf 中的注释，
# 以及上面 GitHub 讨论链接中的相关说明。

Please note that fail2ban will put curly braces '{}' around the
ports in the action so you shouldn't do it yourself.
# 注意：fail2ban 会自动在动作命令中将端口号用大括号 {} 包裹，
# 因此你自己不需要再加。
```

## Fail2Ban 配置解释

> **注意**
>
> 此处的 `jail` 不是 BSD 系统中的 jail（一种容器）。在 Fail2Ban 中，jail 是指对特定服务的监控和封禁配置单元，每个 jail 对应一个需要防护的服务（如 SSH、FTP 等）。

Fail2Ban 的工作流程分为两个核心部分：filter（过滤器）负责解析日志文件，识别失败的登录尝试；action（动作）负责调用防火墙规则执行封禁操作。

配置示例位于 `/usr/local/etc/fail2ban/jail.conf`（请勿直接编辑，参见上文说明），下文仅列出本书所需内容：

```sh
/usr/local/etc/fail2ban/
├── jail.conf
├── filter.d/
│   ├── bsd-sendmail.conf
│   ├── bsd-sshd.conf
│   ├── bsdftp.conf
│   ├── courier-auth.conf
│   ├── mssql-auth.conf
│   ├── murmur.conf
│   ├── mysqld-auth.conf
│   ├── nginx-botsearch.conf
│   ├── slapd.conf
│   ├── softethervpn.conf
│   ├── sogo-auth.conf
│   └── sshd.conf
├── action.d/
│   ├── bsd-ipfw.conf
│   ├── ipfw.conf
│   └── ipfilter.conf
└── jail.d/
    └── sshd.conf
```

```ini
# DEFAULT 是全局定义的配置，之后每个 jail 都可以单独覆盖这些设置。

[DEFAULT]

# "ignoreip" 可以是单个 IP 地址、CIDR 子网或 DNS 主机名列表。Fail2Ban
# 不会封禁列表中匹配的主机。多个地址可以使用空格或逗号分隔
# "ignoreip" 即白名单
# ignoreip = 127.0.0.1/8 ::1

# "maxretry" 默认重试次数
maxretry = 5

# 如果主机在过去的 "findtime" 秒内产生了 "maxretry" 次失败，主机将被封禁。
# 即定义封禁频率。
findtime  = 10m

# "bantime" 表示主机被封禁的时间，可用秒为单位的整数或时间缩写格式（m 表示分钟，h 表示小时，d 表示天，w 表示周，mo 表示月，y 表示年）
# 即多长时间后可重试。
bantime  = 10m

[sshd]
# enabled = true
#
# 参见 jail.conf(5)

# "filter" 定义了 jail 所使用的过滤器。 ①
# 在默认情况下，jail 的名称与其过滤器名称相匹配。
#
filter = %(__name__)s[mode=%(mode)s]
# 例如：
# filter = sshd[mode=aggressive]
#  
# 操作快捷方式。用于定义 action 参数。  

# Fail2Ban 还需要防火墙来执行封禁动作。
# 默认封禁操作（例如 iptables、iptables-new、iptables-multiport、shorewall 等）。 ②
# 它用于定义 action_* 变量，可以在 jail.local 文件的全局或特定部分中覆盖。  
# banaction = iptables-multiport  
# banaction_allports = iptables-allports
```

* ① 列出 Fail2Ban 的过滤器选项：

```sh
# ls /usr/local/etc/fail2ban/filter.d/
……省略一部分输出……
bsd-sendmail.conf		mssql-auth.conf			slapd.conf
bsd-sshd.conf			murmur.conf			softethervpn.conf
bsdftp.conf			mysqld-auth.conf		sogo-auth.conf
courier-auth.conf		nginx-botsearch.conf		sshd.conf
```

需要注意，以 `bsd-` 开头的文件（如 `bsd-sshd.conf`）是 FreeBSD Port 维护者为适配特定环境提供的变体，但在本文中应直接使用 `sshd.conf`，因为它与 Fail2Ban 的标准配置兼容性更好。

* ② 查看 Fail2Ban 支持的防火墙：

```sh
# ls /usr/local/etc/fail2ban/action.d/
bsd-ipfw.conf					ipfw.conf				ipfilter.conf				pf.conf
……省略其他防火墙……
```

## Fail2Ban 封禁配置

创建并编辑文件 `/usr/local/etc/fail2ban/jail.d/sshd.conf`，写入如下内容：

```ini
[DEFAULT]
ignoreip=192.168.0.0/24
maxretry = 3
findtime = 10m
bantime = 8h

[sshd]
enabled=true
filter=sshd
action=bsd-ipfw
```

配置解释：

* 白名单，表示不会被封禁的 IP 段。`192.168.0.0/24` 表示从 `192.168.0.0` 到 `192.168.0.254`。
* `bsd-ipfw` 是示例防火墙，读者可自行选择，参见下文。

在 FreeBSD 中，Fail2Ban 提供了两个 IPFW 相关的动作配置：`ipfw` 和 `bsd-ipfw`。其中 `bsd-ipfw` 是专门为 FreeBSD 优化的版本，与系统集成更好。

> **警告**
>
> 若使用 IPFW 防火墙，必须选择 `bsd-ipfw`，而不能使用 `ipfw`，否则无法生效。

## 配置防火墙

完成 Fail2Ban 配置后，还需配置对应防火墙，然后启动服务。

启动 Fail2Ban 服务：

```sh
# service fail2ban start
```

### IPFW

IPFW 是 FreeBSD 自带的防火墙，其配置如下。

Fail2Ban 配置同上。

#### 配置自启服务

启用防火墙，实现开机自动启动：

```sh
# sysrc firewall_enable="YES"
```

> **警告**
>
> 请勿立即执行 `start` 命令，否则可能导致无法通过 SSH 连接。

#### 修改 IPFW 默认规则

IPFW 规则的默认策略是“默认拒绝”，即规则 65535 会阻断所有未匹配的流量。为避免在配置过程中被阻断访问，可先将其修改为“默认允许”：

```sh
# echo net.inet.ip.fw.default_to_accept="1" >> /boot/loader.conf.local   # 设置防火墙默认策略为接受，并开机生效（推荐使用 /boot/loader.conf.local 进行本地配置扩展）
# reboot # 重启生效
```

* 显示当前 IPFW 防火墙规则列表：

```sh
# ipfw list
……省略一部分……
65535 allow ip from any to any
```

#### 参考文献

* FreeBSD Project. ipfw(8) - IP firewall and traffic shaper control program\[EB/OL]. \[2026-03-26]. <https://man.freebsd.org/cgi/man.cgi?ipfw(8)> FreeBSD 官方防火墙管理工具参考手册，提供了 IPFW 完整命令说明
* dapeng.li. fail2ban\[EB/OL]. \[2026-03-26]. <https://dapeng.li/learning/fail2ban/d4.html> 本节架构基于此，是 Fail2Ban 与 FreeBSD 的集成方案

### PF

PF（Packet Filter，包过滤器）是源自 OpenBSD 的防火墙，其配置如下。

在 PF 中，table 用于存储需要封禁的 IP 地址列表，anchor 用于组织和管理特定的规则集。Fail2Ban 通过向 PF 的 table 中添加 IP 地址，并通过 anchor 应用相应的规则来实现封禁。

#### Fail2Ban 配置文件

将上方配置文件 `/usr/local/etc/fail2ban/jail.d/sshd.conf` 中 `action=bsd-ipfw` 改为 `action=pf[port={22 23}, name=ssh]`，其他不需要改。

#### 修改 PF 配置文件

需要先准备 PF 的配置文件，具体步骤如下。

* 为使 PF 能够正常启动，需将示例 PF 配置文件复制到 /etc 目录以便修改和使用：

```sh
/
├── etc
│   ├── pf.conf
│   ├── ipf.rules
│   └── ipnat.rules
├── usr
│   └── share
│       └── examples
│           ├── pf
│           │   └── pf.conf
│           └── ipfilter
│               ├── ipf.conf.sample
│               └── ipnat.conf.sample
├── var
│   └── log
│       ├── auth.log
│       └── fail2ban.log
└── boot
    └── loader.conf.local
```

```sh
# cp /usr/share/examples/pf/pf.conf /etc/ 
```

* 编辑 `/etc/pf.conf`，写入：

```ini
table <f2b> persist   # 定义并持久化名为 f2b 的表
anchor "f2b/*"        # 创建并引用 f2b 相关的 anchor
block drop in log quick on em0 from <f2b> to any   # 在 em0 接口上快速阻止并记录来自 f2b 表的所有流量
```

* `em0`：示例中的 `em0` 为网卡名称，请根据实际网卡修改，可使用命令 `ifconfig` 查看。

#### 服务

PF 服务的启动和配置如下。

```sh
# kldload pf # 加载内核模块，后续就不需要了
# service pf enable # 开机自启
# service pf start # 启动 PF 防火墙
```

**参考文献**

* Xavier Humbert. PF - fail2ban does not feed pf table\[EB/OL]. \[2026-03-26]. <https://forums.freebsd.org/threads/fail2ban-does-not-feed-pf-table.67798/> FreeBSD 社区论坛讨论 Fail2Ban 与 PF 集成问题
* mfechner. Fail2ban on FreeBSD drops complete pf firewall rules\[EB/OL]. \[2026-03-26]. <https://github.com/fail2ban/fail2ban/issues/1915> GitHub Issue 记录的 Fail2Ban 与 PF 规则冲突问题

### IPFILTER (IPF)

IPFILTER（IPF）是一款开源防火墙，其配置如下。

#### Fail2Ban 配置文件

将上方配置文件 `/usr/local/etc/fail2ban/jail.d/sshd.conf` 中 `action=bsd-ipfw` 改为 `action=ipfilter`，其他不需要改。`ipfilter` action 会调用 IPF 的命令来添加和删除封禁规则。

#### 服务

IPF 服务的配置和启动如下。

* 为使 IPF 能够正常启动并工作，需复制示例文件作为默认配置规则集文件（示例文件自带的规则不影响使用）：

```sh
# cp /usr/share/examples/ipfilter/ipf.conf.sample /etc/ipf.rules   # 将示例 ipfilter 配置文件复制为 /etc/ipf.rules 以便修改和使用
```

* 配置守护进程：

```sh
# service ipfilter enable   # 设置 ipfilter 服务开机自启动
# service ipfilter start    # 启动 ipfilter 防火墙服务
```

完成上述步骤后，Fail2Ban 即可正常使用。

## 测试效果

配置完成后，可以测试 Fail2Ban 是否能正常封禁 IP。使用 Fail2Ban 客户端手动将一个 IP 加入封禁列表来验证功能。

* 为了查看效果，使用 Fail2Ban 客户端将 IP 192.168.179.1 在 sshd 监控下加入封禁列表：

```sh
# fail2ban-client set sshd banip 192.168.179.1
```

* TTY 输出：此输出示例显示 SSH 服务检测到来自 192.168.179.1 的多次认证失败。

```sh
Mar 25 15:27:38 gkla sshd[970]: error : maximum authentication attempts exceeded for ykla from 192.168.179.1 port 8652 ssh2 [ preauth ]
```

已建立的 SSH 连接也会被强制断开。

### 查看状态

可以通过以下命令查看 Fail2Ban 的运行状态。

查看 fail2ban 对 sshd 监控的状态，包括被封禁的 IP 列表：

```sh
# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	4
|  `- File list:	/var/log/auth.log
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	192.168.179.1
```

### 解禁 IP

若需解除对特定 IP 的封禁，可执行以下操作。

从 sshd 监控中解除对指定 IP 的封禁：

```sh
# fail2ban-client set sshd unbanip 192.168.179.1
1
# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	4
|  `- File list:	/var/log/auth.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	1
   `- Banned IP list:
```

## 故障排除与未竟事宜

若遇到问题，可通过查看日志文件进行排查。

* Fail2Ban 的日志文件位于 `/var/log/fail2ban.log` 文件。

## 课后习题

1. 查找 Fail2Ban 源码中 `action.d/bsd-ipfw.conf` 文件，分析其与 `action.d/ipfw.conf` 的差异，重构一个最小化的 IPFW 封禁脚本，并验证其功能与原文中提到的“必须使用 bsd-ipfw 而非 ipfw 的原因”。
2. 选取 Fail2Ban 的日志分析机制，选取除 SSH 之外的另一个服务（如 nginx），为其配置 Fail2Ban 防护，验证封禁效果。
3. 修改 Fail2Ban 的封禁策略（如将 maxretry 从 3 改为 1，bantime 从 8h 改为永久），验证其行为变化，并讨论对攻击者可能的规避方式。
