外观
项目 6:防火墙与入侵防护初始化
约 3848 字大约 13 分钟
UFWfail2ban防火墙端口放行
2026-05-31
项目目标
第 5 课解决了 SSH 登录面本身的安全问题,本课继续完成服务器初始化的第二个子项目:编写 init_firewall_fail2ban.sh,自动安装并配置 UFW 防火墙 与 fail2ban 入侵防护。
最终脚本需要做到:启用 UFW 前先放行当前 SSH 端口,按配置放行业务端口,安装并配置 fail2ban 的 SSH jail,启动服务并输出可截图的验证结果。
一、为什么 SSH 加固之后还要做防火墙
SSH 端口迁移和密钥登录可以降低暴力破解成功率,但它们不等于网络访问控制。服务器上常见的服务不止 SSH,还可能包含 Web、数据库、缓存、监控端口。如果所有端口都暴露在公网,攻击面会随着服务数量不断扩大。防火墙的职责是建立一道明确的网络边界——只有声明过的端口才允许入站流量,其余一律拒绝。这种"白名单"思维(默认拒绝,按需放行)是服务器安全的第一原则。
本课的思路是把访问控制分成两层:
| 层级 | 工具 | 解决的问题 |
|---|---|---|
| 网络边界 | UFW | 哪些端口允许被外部访问,哪些端口默认拒绝 |
| 登录防护 | fail2ban | 某个 IP 多次登录失败后,是否自动封禁 |
UFW 是 Ubuntu 上常用的防火墙管理工具,它把底层 iptables/nftables 规则封装成更适合初学者使用的命令。fail2ban 则会读取认证日志,发现暴力破解特征后自动下发封禁规则。
1.1 UFW 的内核:iptables 与 nftables
理解 UFW 之前,有必要了解它包装了什么。Linux 内核的网络过滤框架经历了两个时代:
| 框架 | 出现时间 | 特点 |
|---|---|---|
| iptables | Linux 2.4(2001) | 基于 netfilter 钩子,规则按表/链组织,是目前最广泛使用的方案 |
| nftables | Linux 3.13(2014) | iptables 的继任者,统一了 IPv4/IPv6/ARP 的规则语法,性能更好 |
UFW 在 Ubuntu 上的默认后端可能是 iptables 或 nftables,取决于系统版本和配置。可以用以下命令查看当前后端:
sudo ufw status verbose | grep -i "logging"
# 或直接看规则表
sudo iptables -L -n | head -20
sudo nft list ruleset 2>/dev/null | head -20无论后端是哪个,UFW 都会把它翻译成 ufw allow 80/tcp 这样的简洁命令。运维人员不需要直接写复杂的 iptables 规则,但需要知道:UFW 的每一条 allow 或 deny 命令最终都会在 iptables/nftables 中生成对应的规则链。当故障排查深入到"UFW 显示 allow 但流量仍不通"时,往往需要检查底层规则是否被其他程序(如 Docker、K3s)直接修改了 iptables,绕过了 UFW 的管理。
1.2 UFW 的三种默认策略
UFW 有三条默认策略,它们决定了"没有明确规则的流量"如何处理:
| 策略方向 | 默认值 | 含义 |
|---|---|---|
| incoming | deny | 所有入站流量默认拒绝 |
| outgoing | allow | 所有出站流量默认允许 |
| routed | disabled / deny | 转发流量默认拒绝(本机不作路由时禁用) |
生产环境中,incoming 必须是 deny。outgoing 通常保持 allow,除非有严格的网络隔离需求(例如防止被入侵后的 C2 回连)。routed 在服务器作为网关或 Docker 宿主机转发容器流量时才需要关注——Docker 安装后通常会自动修改转发策略,这也是 Docker 与 UFW 之间最常见的不兼容来源。
二、启用 UFW 前最重要的一件事
UFW 初始化顺序演示
💻
管理员终端
已连接
SSH
🖥️
服务器
UFW (未启用)
SSH (22端口)
0
ssh root@server初始状态:管理员已通过 SSH 登录服务器,UFW 尚未启用。
1
ufw allow 22/tcp第一步:放行 SSH 端口。防火墙预留了 SSH 通道。
2
ufw enable第二步:启用防火墙。默认拒绝入站,但 SSH 流量可以通过放行的通道正常通信。
3
ssh root@server第三步:验证。新的 SSH 连接请求顺利穿过防火墙。
防火墙初始化最容易出现的低级事故是:还没放行 SSH 端口,就启用了默认拒绝入站策略。远程服务器一旦这样配置,当前连接之外的新 SSH 会话就进不来了。更糟的是,如果你在当前 SSH 会话中执行了 ufw enable 而没有先放行 SSH 端口,这条已有的连接通常不会断开(因为它是已建立的连接),但任何新的 SSH 连接都会被拒绝——这意味着你实际上把自己"软锁"在了服务器里,退出当前会话后就再也进不去了。
正确顺序如下:
如果当前 SSH 使用的是 2222,脚本必须在 ufw enable 之前执行类似命令:
sudo ufw allow 2222/tcp comment 'SSH'这里的关键不是命令本身,而是顺序。顺序错了,再漂亮的脚本也会把自己锁在门外。脚本里可以加一个保险检查:在 ufw enable 之前,用 sudo ufw status | grep -q "$SSH_PORT" 确认 SSH 端口已经在规则列表中。如果不存在,脚本应该报错退出而不是继续执行。
2.1 UFW 规则的幂等性
UFW 本身具有幂等性:重复执行 ufw allow 2222/tcp 不会创建重复规则。但如果脚本中同时操作了 UFW 规则文件和底层 iptables,就可能产生冲突。建议脚本遵循一个原则:所有端口放行统一通过 ufw 命令完成,不要混用直接写 iptables 命令的方式。
如果需要批量管理端口,可以用循环:
IFS=',' read -ra PORTS <<< "$ALLOW_PORTS"
for port in "${PORTS[@]}"; do
if ! sudo ufw status | grep -q "$port"; then
sudo ufw allow "$port/tcp" comment "Business port $port"
fi
done这段逻辑先检查端口是否已放行,避免重复添加,也避免在 ufw status 输出中看到多条相同记录。
2.2 UFW 应用配置文件
UFW 提供了一种更高级的端口管理方式——应用配置文件。它位于 /etc/ufw/applications.d/,可以给一组端口定义一个名字:
[OpenSSH]
title=Secure shell server
description=OpenSSH server
ports=22/tcp然后就可以用 sudo ufw allow OpenSSH 来放行,而不需要记忆端口号。你可以在脚本中预先放置一个自定义应用配置,再通过应用名引用:
sudo cp config/ufw-app-profile /etc/ufw/applications.d/custom
sudo ufw app update custom
sudo ufw allow custom这样做的好处是:当端口需要变更时,只改应用配置文件,不需要修改脚本中对具体端口的引用。
三、脚本参数设计
本课脚本需要读取两个核心参数:
| 参数 | 示例 | 说明 |
|---|---|---|
--ssh-port | 2222 | SSH 服务当前使用的端口,必须优先放行 |
--allow-ports | 80,443,8080 | 需要放行的业务端口,使用英文逗号分隔 |
--apply | 无值 | 正式执行;不加时只做 dry-run 预览 |
建议先 dry-run:
sudo bash init_firewall_fail2ban.sh \
--ssh-port 2222 \
--allow-ports 80,443确认输出顺序正确后再正式执行:
sudo bash init_firewall_fail2ban.sh \
--ssh-port 2222 \
--allow-ports 80,443 \
--apply操作红线
如果你是在远程服务器上操作,启用防火墙前必须确认 SSH 端口、云安全组和本机 UFW 三处都已放行。云安全组没有放行时,UFW 配对也无法从公网连入。一个稳妥的做法是:保持两个 SSH 终端同时连接,在其中一个执行 UFW 配置,另一个作为"救援通道"。如果配置出错导致新连接失败,还可以用救援终端修正。
四、fail2ban 的工作原理与配置
4.1 fail2ban 的匹配—封禁流水线
Fail2ban 匹配封禁流水线演示
展示日志被过滤、累计达到 maxretry 后下发封禁规则的过程
/var/log/auth.log
📄
Filter (sshd.conf)
🔍
^Failed password for .* from <HOST>
Jail 判定逻辑
⚖️
失败次数: 0 / 3
Action (iptables)
🛡️
暂无封禁规则
ℹ️
当前状态:Fail2ban 正在实时读取 auth.log。点击上方按钮模拟一次黑客输错密码的行为。
fail2ban 不是通过监听网络流量来检测攻击,而是通过读取日志文件来工作。它的执行流程如下:
这条流水线的核心组件是 filter(定义什么样的日志行算"一次失败")和 action(封禁动作是什么)。理解了这两个概念,才能判断 fail2ban 为什么没有按预期工作。
4.2 jail 配置详解
fail2ban 的核心配置单元叫 jail。一个 jail 通常包含三类信息:
| 字段 | 作用 | 典型值 |
|---|---|---|
enabled | 是否启用该防护规则 | true |
port | 保护哪个服务端口 | ssh 或 2222 |
logpath | 从哪个日志文件读取失败登录记录 | /var/log/auth.log |
maxretry | 触发封禁前允许失败几次 | 3 或 5 |
findtime | 统计失败次数的时间窗口(秒) | 600(10 分钟) |
bantime | 封禁持续时间(秒) | 3600(1 小时)或 -1(永久) |
filter | 使用的过滤器名称 | sshd |
action | 封禁动作 | iptables-multiport |
backend | 日志读取后端 | auto 或 systemd |
不同系统版本里 SSH 日志路径可能不同,常见路径是 /var/log/auth.log。如果 fail2ban 启动失败,第一时间查看日志路径和过滤器名称是否匹配。使用 systemd 作为后端时(CentOS/RHEL 系列),fail2ban 会从 journald 读取日志而不是直接读文件,此时 logpath 配置无效。
4.3 filter 正则表达式原理
fail2ban 的 filter 是一组正则表达式,定义在 /etc/fail2ban/filter.d/sshd.conf 中。以 SSH 的 sshd filter 为例,它匹配的典型日志行是:
May 31 10:15:22 server01 sshd[1234]: Failed password for root from 192.168.1.100 port 54321 ssh2filter 中的正则通过命名捕获组提取 IP 地址,例如:
^%(__prefix_line)sFailed password for .* from <HOST>( port \d+)?( ssh\d+)?$其中 <HOST> 是 fail2ban 预定义的 IP 地址匹配模式。如果日志格式与 filter 不匹配(例如使用了不同的 SSH 实现,或日志被 localisation 修改),fail2ban 将静默失效——jail 显示 enabled,但不会触发任何封禁。
验证 filter 是否匹配当前日志的方法:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf这条命令会输出匹配行数和未匹配行数。如果匹配数为 0,说明 filter 需要调整。
4.4 action 封禁动作类型
fail2ban 支持多种封禁动作,最常见的三种:
| action | 机制 | 适用场景 |
|---|---|---|
iptables-multiport | 向 iptables 添加 DROP 规则 | 通用,最常用 |
iptables-allports | 封禁 IP 的所有端口 | 需要完全隔离某个 IP |
cloudflare | 调用 Cloudflare API 封禁 IP | 有 Cloudflare 代理的站点 |
sendmail | 发送邮件通知 | 需要人工介入的场景 |
默认的 iptables-multiport 会在 iptables 的 f2b-sshd 链中添加规则。可以用 sudo iptables -L f2b-sshd -n 查看 fail2ban 实际下发的规则,这是排查"jail 已启用但仍有暴力登录"时最重要的检查点。
问题:fail2ban 中用于覆盖本机 jail 配置的常用文件名是什么?
4.5 常用 jail 场景扩展
除了 SSH,fail2ban 还可以保护以下服务:
| 服务 | filter 名称 | 日志路径 | 典型用途 |
|---|---|---|---|
| Nginx HTTP 认证 | nginx-http-auth | /var/log/nginx/error.log | 防止 Web 认证爆破 |
| Nginx 4xx 扫描 | nginx-botsearch | /var/log/nginx/access.log | 防止目录扫描 |
| Apache 认证 | apache-auth | /var/log/apache2/error.log | Web 认证防护 |
| MySQL | mysqld-auth | /var/log/mysql/error.log | 数据库登录防护 |
| vsftpd | vsftpd | /var/log/vsftpd.log | FTP 登录防护 |
本课只要求配置 SSH jail,但脚本结构应该预留扩展其他 jail 的空间——把 jail 名称做成数组,循环写入 jail.local,后续添加新防护只需在数组中追加名称。
五、解锁初始化脚本
下面的脚本把 UFW 安装、默认策略、端口幂等放行、fail2ban 配置和状态验证串成一条完整流程。输入 UFW 放行端口命令的前两个单词,解锁正式脚本。
问题:UFW 中用于放行端口规则的命令前两个单词是什么?
将解锁内容保存为 init_firewall_fail2ban.sh,并赋予执行权限:
chmod +x init_firewall_fail2ban.sh六、运行与验证
6.1 执行脚本
# 预览,不真正修改系统
sudo ./init_firewall_fail2ban.sh --ssh-port 2222 --allow-ports 80,443
# 正式执行
sudo ./init_firewall_fail2ban.sh --ssh-port 2222 --allow-ports 80,443 --apply6.2 验证 UFW 状态
sudo ufw status verbose
sudo ufw status numbered你应该能看到:
Status: active
Default: deny (incoming), allow (outgoing), disabled (routed)
2222/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere6.3 验证 fail2ban 状态
sudo systemctl status fail2ban --no-pager
sudo fail2ban-client status
sudo fail2ban-client status sshdstatus sshd 中至少应能看到 Filter、Actions、Currently banned 等字段。课堂验收时不要求一定制造真实封禁,但要能证明 jail 已启用。
6.4 手动验证 fail2ban 封禁流程
如果你有测试条件,可以模拟一次真实的暴力破解来验证端到端流程:
# 从另一台机器(或本机另一个终端)故意输错密码
ssh -p 2222 wronguser@目标IP
# 观察 fail2ban 日志
sudo tail -f /var/log/fail2ban.log
# 查看封禁列表
sudo fail2ban-client status sshd
# 解封测试 IP(实验完成后)
sudo fail2ban-client set sshd unbanip <测试IP>注意:不要用 localhost 或服务器自身的公网 IP 做测试——把自己封禁了就只能通过控制台恢复了。
七、提交物清单
| 提交物 | 要求 |
|---|---|
init_firewall_fail2ban.sh | 支持 dry-run、--apply、SSH 端口和业务端口参数 |
| UFW 状态截图 | 能看到默认策略和已放行端口 |
| fail2ban 状态截图 | 能看到 sshd jail 已启用 |
| 简短说明 | 写清楚脚本如何避免重复添加规则,以及启用防火墙前为什么必须放行 SSH,fail2ban 的匹配和封禁机制 |
八、常见故障排查
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
ufw enable 后新 SSH 连不上 | SSH 端口没有放行,或云安全组未放行 | 保留旧终端,通过控制台补充防火墙或安全组规则 |
fail2ban-client status sshd 报错 | jail 未启用,或服务未启动 | 检查 /etc/fail2ban/jail.local,然后 sudo systemctl restart fail2ban |
| fail2ban 启动失败 | logpath 指向的日志文件不存在 | 确认系统认证日志路径(/var/log/auth.log 或 journald),必要时改用 backend = systemd |
| 规则重复显示很多次 | 脚本没有做幂等判断 | 添加规则前先用 ufw status 检查是否已存在 |
| fail2ban jail 启用但从不封禁 | filter 正则不匹配当前日志格式 | 用 fail2ban-regex 测试 filter,检查日志时间格式和关键字 |
f2b-sshd 链为空 | action 未正确加载 | 检查 /etc/fail2ban/action.d/ 文件完整性,重启 fail2ban |
| Docker 与 UFW 端口互通异常 | Docker 直接操作 iptables 绕过 UFW | 在 /etc/docker/daemon.json 中设置 "iptables": false 或使用 ufw-docker 工具 |
九、进阶任务
探索任务 1:加入端口合法性校验
给脚本增加校验:端口必须是 1-65535 的数字,且业务端口不能与 SSH 端口重复。校验不通过时应打印明确的错误信息并退出,避免带着错误配置继续执行。
探索任务 2:模拟暴力破解封禁
在测试环境中故意输错 SSH 密码多次,然后观察 sudo fail2ban-client status sshd 的 Currently banned 是否变化。记录需要多少次失败才触发封禁(由 maxretry 决定),实验完成后用 sudo fail2ban-client unban <IP> 解封。
探索任务 3:增加其他服务的 jail
在 jail.local 中增加 Nginx HTTP 认证防护 jail(如果已安装 Nginx),验证 filter 是否匹配。即使目标服务器没有运行 Nginx,也应理解配置的通用结构:指定 logpath、port、filter 和 maxretry,所有 jail 遵循同一套模板。
