post: 防线是会自己停摆的
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: 防线是会自己停摆的
|
||||
slug: defense-that-stalls
|
||||
date: 2026-10-22T18:05:00
|
||||
lastmod: 2026-10-22T18:05:00
|
||||
draft: false
|
||||
description: 三天内两处防线静默失效,都是我自己部署时验证过的——交互 shell 里全绿,cron 里悄悄死。是它们变心了吗?不,是我从没在真实的环境里验证过。
|
||||
tags:
|
||||
- 运维
|
||||
- 安全
|
||||
- 自动化
|
||||
- 监控
|
||||
categories:
|
||||
- 运维手记
|
||||
weight: 44
|
||||
---
|
||||
|
||||
## 一、防线,也讲究长期的 commitment
|
||||
|
||||
5 台机器,我把"自动封禁脚本"当成给机器装的**免疫系统**—它们24/7运行在公网,每天几千到上万次的爆破/扫描,我总不能每次都手动去封吧。
|
||||
|
||||
所以几个月前,我在每台机器上部署了 auto-ban 脚本:定时扫描日志、识别爆破/扫描 IP、按照阈值封禁。
|
||||
|
||||
**今天检查发现:其中一台的"免疫系统",两年来一直空转。**
|
||||
|
||||
原因:它运行依赖的防火墙工具,在**那台机器上根本不存在**(cron 环境的 PATH 里找不到)。脚本的设计是"失败都不失败"——报错被 `2>/dev/null` 吞掉了,永久静默。
|
||||
|
||||
它每天按时启动,按时退出,日志里写"今天运行正常"。但实际上,**一条规则都没生效**。
|
||||
|
||||
> 不是防线变坏了,是我从没在**真实的环境里**验证过它能跑。
|
||||
|
||||
这周连续三次同类事故:
|
||||
|
||||
- DE 节点:auto-ban 突然 FATAL,说的是"ipset 不可用"
|
||||
- LA 节点:auto-ban 卡死,另一个变体的问题
|
||||
- 更早的一台:guard 脚本跑在错的用户身份下,**读都读不到自己的数据**
|
||||
|
||||
---
|
||||
|
||||
## 二、两种"防线静默失效"的病理
|
||||
|
||||
### 病例 1:FATAL PATHShock 🫠
|
||||
|
||||
DE 的 auto-ban 脚本,每天 09:30 开始报:
|
||||
|
||||
```
|
||||
[Oct 20 09:30:01] FATAL ipset 不可用
|
||||
```
|
||||
|
||||
每小时一次,共 51 次。
|
||||
|
||||
**根因**:`ipset` 这个工具装在 `/usr/sbin`,但 **cron 的默认 PATH 里没有 `/usr/sbin`**。脚本里 `ipset create ...` 找不到命令,直接 FATAL 退出。
|
||||
|
||||
为什么没发现?因为**部署那天我是在交互 shell(root 全环境 PATH)里验证的**——一切正常。一旦走 cron(受限 PATH),当场死。
|
||||
|
||||
这是**第三次踩 "cron PATH" 这个坑**。前面两次是宝塔的 fail2ban 和另一个 guard 脚本。同一个坑,我摔了三次。
|
||||
|
||||
### 病例 2:锁死锁上的 Douglas 阻塞 🔒
|
||||
|
||||
LA 的 auto-ban 更隐蔽:**进程没报错,但卡住了**。
|
||||
|
||||
它调用 `firewall-cmd --ipset --add-entry` 来封 IP。这个调用**每条要走一次 D-Bus**。正常几条无所谓,但 LA 一天被扫描出 **3571 个 IP**,要一条条走 D-Bus——几百秒跑不完 → 进程挂住 → flock 锁不释放 → 之后每小时的定时任务都直接 skip("另一个实例在跑")。
|
||||
|
||||
结果:**BANNED 0 条**,而日志看起来还在"正常"输出。
|
||||
|
||||
Hook 是:**卡住比报错更难发现**。报错至少有信号,卡住只在日志末尾留一句 skip,还需要你主动去看。
|
||||
|
||||
### 修复(两台都改完)
|
||||
|
||||
1. **DE**:脚本顶部加 `export PATH=...`,把 `/usr/sbin` 包进来。用 `env -i` 模拟 cron 环境实测通过,ipset 从 **0 → 1066 条**。
|
||||
2. **LA**:把 `firewall-cmd` 调用换成直接 `ipset add`(内核级,快 10 倍)。实测 108 秒跑完,**3,571 条**封禁 + state 同步。
|
||||
|
||||
---
|
||||
|
||||
## 三、为什么"测试时好的"会变成"运行时死的"
|
||||
|
||||
> **部署时的验证环境 ≠ 运行时的真实环境。**
|
||||
|
||||
同一个脚本,我用 root 的全功能 shell 验证:PATH 全、权限全、环境变量全、依赖全。但**上了 cron**,它拿着一个被限制的 PATH 运行。**唯一区别是我测试的那条路径和它实际跑的那条路径不同**。
|
||||
|
||||
这不是脚本 bug——脚本逻辑本身没错。是**部署流程的 bug**:我把"我在交互 shell 里跑通了"当成了"它能跑"。
|
||||
|
||||
如果换成工程师的术语:这是我**没有验证运行时依赖,只验证了逻辑依赖**。
|
||||
|
||||
### 三条破法(教训)
|
||||
|
||||
1. **验证必须用真实环境跑一遍**:`env -i`(清空全部环境变量)模拟 cron,能在这种环境下 live true 的才算过
|
||||
2. **关键参数用绝对路径**:不依赖 PATH(`/usr/sbin/ipset` 而不是 `ipset`)
|
||||
3. **别 `2>/dev/null` 吞掉 startup 错误**:如果它死了至少告诉我一声,不然我根本不知道防线停了
|
||||
|
||||
---
|
||||
|
||||
## 四、今天顺手扫了一圈全舰队的攻防态势
|
||||
|
||||
排查完防线停摆,我做了一轮**全舰队今日实况**(只看今天):
|
||||
|
||||
| 节点 | SSH 爆破(今日) | 成功登录 | 封禁中 |
|
||||
|------|---:|---:|---:|
|
||||
| **宁波** | **11,465** | 1,134 | 632 |
|
||||
| DE | 1,711 | 50 | 1,066 |
|
||||
| AU | 1,247 | 87 | 278 |
|
||||
| SG | 525 | 41 | 106 |
|
||||
| LA | **0**(SSH 在非标端口)| 40 | **3,571** |
|
||||
|
||||
所有成功登录都来自我自己的 IP(除了**一次奇怪但无害的 root 密码登录**,同网段有一个我们的客户端,判断是用户自己)。
|
||||
|
||||
Web 扫描:宁波主力站点 404 靠 WAF 全是 0;LA 没有 WAF,全靠 auto-ban 硬封 3571 IP 撑着。
|
||||
|
||||
**结论:有,而且很活跃;但没有被攻破。** 一个风险信号都没有。
|
||||
|
||||
但注意这个数字——**如果防线的停摆没被发现,这些"封禁中"的数字,有的会是 0。**
|
||||
|
||||
---
|
||||
|
||||
## 五、最让我后怕的一点:
|
||||
|
||||
### "防线在工作的证据" 和 "防线在工作" 是两回事
|
||||
|
||||
回过头看,我靠着两样东西了解防线状态:
|
||||
|
||||
- **脚本跑得欢**:日志里有输出("今天运行正常")
|
||||
- **封禁列表有数字**:DE 显示还残留着几条历史的封禁
|
||||
|
||||
但这两样都是**"防线在工作的证据"**,而不是**"防线在工作"**本身。证据可以是旧的(残留的历史数字),也可以是假的(主动打印的成功字样)。防线本身,只有在**真实封一次攻击者**时才被证明工作。
|
||||
|
||||
所以我今天给自己加了一条**新判据**:
|
||||
|
||||
> **封禁数长期为 0,本身就是一个警报**——不是"日志安静"的平静,而是"防线悄悄停了"。
|
||||
>
|
||||
> **封禁数从 3000 掉到 5、从 5 掉到 0** 这样的级差变化,也应该被当成独立信号触发告警。
|
||||
|
||||
---
|
||||
|
||||
## 六、本周还有一个让我后怕的假设
|
||||
|
||||
回过头看,今天五台机器的封禁数是:**632 / 1,066 / 278 / 106 / 3,571**。
|
||||
|
||||
如果防线的停摆再晚一周被发现,DE 的封禁数会从 948 自然衰减到 **0**(24 小时的 TTL 到期),而日志还在每天写"运行正常"。
|
||||
|
||||
那将成为我博客的又一篇——《它显示运行正常,其实是 0》。
|
||||
|
||||
**那天可能就真的晚了。**
|
||||
|
||||
> 一个自动化的部署,最危险的不是它**失败**(红色容易看见),而是它**成功到看起来在运行**,却一直什么都没做。
|
||||
|
||||
防线要靠**持续的真实验证**来维持,不靠部署一时的绿色标记。
|
||||
Reference in New Issue
Block a user