Files
MeshSate/docs/01-诊断套件方案.md
zhaolei 16f134e328 feat: MeshSafe 文件摆渡安全卫士 v1.0 初始提交
核心功能:
- 6 层检测管线: L0 结构分析 → L1 ClamAV → L2 文档深度检测 → L3 关键词匹配 → L4 YARA → L5 PE/二进制
- FastAPI + PostgreSQL + Redis + Docker Compose 架构
- 客户端 HMAC-SHA256 签名鉴权 + 限流 + 幂等性
- 控制台 UI (登录/Dashboard/诊断中心/策略管理/任务列表/测试中心/产品介绍页)
- 三层诊断定时器 (error_scan 30s / db_aggregate 30s / container_check 5min)

并发与稳定性修复:
- 诊断定时器 Redis 时间槽位锁 (多 worker 同一槽位只有一个执行)
- JSONL 存储 fcntl 跨进程排他锁 + 容错解码 + 原子重写
- 移除 SQLite fallback, 统一 PostgreSQL
- API_CLIENTS 脱敏为环境变量, .env 已 .gitignore
2026-10-28 19:07:11 +08:00

495 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MeshSafe Diagnostics Kit v1.0 方案
> 版本: v1.0
> 日期: 2026-10-28
> 状态: **待审核**
> 作者: MeshSafe Team
> 优先级: 高(P0)
---
## 一、定位
> **目标**:让 MeshSafe 运维人员在产品内部就能看到"系统健康状况",遇到问题能一键打包带出来给开发方分析。
>
> **边界**:
> - ❌ 不联网、不依赖外部服务、不做 LLM、不自动上传
> - ✅ 所有分析跑在客户容器里(在线定时)
> - ✅ 导出诊断包(手动触发,U 盘带出)
> - ✅ Docker 能力自动检测 + 三级降级
> - ✅ 全部定时器周期 + 规则阈值配置化
> - ✅ 总开关可关闭整个诊断套件(只留手动导出)
---
## 二、核心设计决策
| # | 决策 | 理由 |
|---|------|------|
| 1 | **在线层:只跑 grep 模式匹配,不收集完整日志** | 光闸内网,日志可能带客户敏感信息;完整日志只在**用户手动导出**时打包 |
| 2 | **三层定时器**(错误类 30s / DB 聚合 60s / 容器类 300s)| 拦截日志要快出快分析;容器/配置类变化慢 |
| 3 | **页面请求时必跑轻量规则**(DB 聚合 + 引擎健康 + 文件 stat) | ~430ms 用户无感,确保页面显示的是最新数据 |
| 4 | **日志匹配 + docker inspect 跑后台定时器** | 涉及 subprocess + IO,耗时 ~2.5s 不阻塞前台 |
| 5 | **Docker 能力三档降级**(full / partial / none) | 客户环境可能挂不了 docker.sock;降级后页面明确告诉用户哪些规则没跑 |
| 6 | **总开关 enabled** | 有的客户不需要/不想跑诊断,一键关掉只留手动导出 |
| 7 | **全部定时器周期 + 规则阈值配置化** | 光闸环境差异大,硬编码不合适 |
---
## 三、功能清单
### 3.1 控制台新页面:诊断面板
| # | 模块 | 展示内容 | 数据源 |
|---|------|---------|--------|
| A1 | 实时健康概览 | 4 张卡片:总扫描量、恶意拦截、**错误比例**(高就红)、引擎在线 | DB ScanTask + scanner.health() |
| A2 | 引擎健康详情 | 每个引擎状态、ClamAV 病毒库日期、YARA 规则数、最近检测耗时 | scanner.health() + 配置快照 |
| A3 | 容器运行状态 | 5 个容器 status / restart_count / Docker 能力检测结果 | docker inspect(或降级跳过)|
| A4 | 异常检测面板 | 规则引擎跑出来的问题列表(每条带严重度、消息、证据、检测时间戳)| 实时计算 + Redis 缓存 |
| A5 | 最近 20 条错误 | 从 DB 查 verdict='error' 的 ScanTask | DB |
| A6 | **一键导出按钮** | 生成 `diagnostics.tar.gz` → 浏览器下载 | 后端打包 |
**页面顶部信息栏**:
```
最后刷新: 10:29:15 🧩 Docker 能力: ✅ 完整(日志检测 + 容器状态)
或 ⚠️ 部分 / ℹ️ 未启用
或 ⛔ 诊断套件未启用
```
**每条规则结果带时间戳**(不同规则可不同,因来自不同运行通道):
```
🔴 R009 ClamAV 被 OOM Kill (检测于 10:29:15) ← 页面请求时跑
🟡 R002 ClamAV 病毒库 14 天未更新 (检测于 10:29:15) ← 页面请求时跑
🟢 R004 所有引擎健康 (检测于 10:28:02) ← 定时器跑
```
**未启用 Docker 时**:跳过 R009-R015,对应规则行显示 `ℹ️ Docker 能力不可用,本条规则未运行`。
**诊断套件总开关关闭时**:页面显示大横幅 `⛔ 诊断套件未启用`,下面规则列表隐藏,只保留手动导出按钮。
### 3.2 规则清单(15 条)
#### 页面请求必跑(轻量,~430ms,每次打开都算)
| 规则 | 类型 | 数据源 | 耗时 |
|------|------|--------|------|
| R001 error 比例 | DB SQL | ScanTask GROUP BY verdict | ~20ms |
| R005 24h 无扫描 | DB SQL | ScanTask MAX(created_at) | ~20ms |
| R002 ClamAV 病毒库日期 | 引擎协议 | clamd SIGINFO | ~100ms |
| R004 引擎健康 | 引擎协议 | scanner.health() | ~300ms |
| R003 YARA 规则 mtime | 文件 stat | os.path.getmtime | <1ms |
#### 定时器组 A:错误/拦截类(30s 周期)
| 规则 | 依赖 | 异常信号 | 匹配模式 |
|------|------|---------|---------|
| R009 ClamAV OOM/crash | docker logs clamd | clamd 被杀/崩 | `Killed` / `exit 137` / `segfault` / `oom-killed` |
| R010 Postgres FATAL | docker logs postgres | 连接池爆 | `FATAL` / `too many connections` / `PANIC` |
| R011 Redis OOM | docker logs redis | 内存满 | `OOM command not allowed` / `out of memory` |
| R012 磁盘满 | 5 个容器 docker logs | 磁盘爆 | `No space left on device` / `ENOSPC` |
| R013 Nginx upstream 失败 | docker logs nginx | api 挂了 | `Connection refused` / `upstream timed out` |
| R014 API 连接异常 | docker logs api | 依赖断了 | `Redis connection refused` / `Postgres connection refused` |
#### 定时器组 B:DB 聚合类(60s 周期)
| 规则 | 数据源 |
|------|--------|
| R006 suspicious > malicious | ScanTask GROUP BY verdict |
| R007 reject 比例 | ScanTask COUNT + verdict |
| R008 同 SHA256 重复 | ScanTask GROUP BY sha256 HAVING COUNT > 1 |
#### 定时器组 C:容器/引擎类(300s 周期)
| 规则 | 依赖 |
|------|------|
| R015 容器重启次数 | docker inspect × 5(任一 restart_count > 3)|
### 3.3 每条规则输出格式
```json
{
"id": "R009",
"severity": "high", // high / medium / low / info
"message": "ClamAV 被 OOM Kill(近 24h 重启 3 次)",
"hint": "给 clamav 容器加内存限制 512m → 1g",
"evidence": [
"meshsafe-clamav 最近日志:Killed (exit 137)",
"meshsafe-clamav restart_count = 3"
],
"run_at": "2026-10-28T10:29:15+08:00",
"run_by": "page_request", // page_request / timer:error_scan / export
"skipped": false,
"skip_reason": null
}
```
### 3.4 一键导出
**端点**:`GET /api/v1/telemetry/dump`(返回文件下载)
**导出包结构**:
```
diagnostics-20261028-103045.tar.gz
├── rules_report.json ← 导出这一刻跑一遍规则引擎的完整结果(带证据 + 时间戳)
├── containers/
│ ├── meshsafe-api.log ← docker logs --tail 2000(导出时抓完整)
│ ├── meshsafe-api.meta.json ← restart_count / started_at / 镜像 tag
│ ├── meshsafe-clamav.log
│ ├── meshsafe-clamav.meta.json
│ ├── meshsafe-postgres.log
│ ├── meshsafe-postgres.meta.json
│ ├── meshsafe-redis.log
│ ├── meshsafe-redis.meta.json
│ ├── meshsafe-nginx.log
│ └── meshsafe-nginx.meta.json
├── scan_tasks.json ← 最近 500 条 ScanTask(脱敏)
├── scan_findings.json ← 对应 ScanFinding(rule name / severity / engine)
├── engine_health.json ← scanner.health() 快照
├── policy_snapshot.json ← 策略版本号 + YARA 规则文件列表(**不含规则原文**)
├── docker_detect.json ← 启动时 Docker 能力检测结果
├── compose_state.json ← docker compose ps 输出(容器运行状态表)
└── meta.json ← 打包时间 / 生成人 / MeshSafe 版本 / 规则版本
```
**数据脱敏策略**:
| 字段 | 处理方式 |
|------|---------|
| 原始文件名 | 保留扩展名 + SHA256 前 8 位 |
| 文件路径 | 截断为相对路径 |
| 用户名 | `u***n` |
| IP | `10.0.1.*`(保留最后一段)|
| 策略 YAML | 默认只传版本号 + 规则列表;加开关可选全量 |
### 3.5 Docker 能力检测
启动时自动运行(`@app.on_event("startup")`),三档降级:
| 检测结果 | 条件 | 影响 | 页面显示 |
|---------|------|------|---------|
| **full** | docker.sock 挂载 + docker CLI 可用 + `docker logs/inspect meshsafe-api` 成功 | R009-R015 全部正常运行 | ✅ Docker 能力完整 |
| **partial** | docker CLI 有但 socket 权限不够 | R009-R015 可能部分失败 | ⚠️ Docker 访问受限,部分容器日志规则未运行 |
| **none** | socket 未挂 / CLI 未装 | R009-R015 **自动跳过** | ℹ️ 未启用 Docker 日志检测 |
检测结果写 `docker_detect.json`(导出包),同时存 Redis 供页面查询。
---
## 四、定时器运行架构
### 4.1 三层定时器 + 页面必跑
```
MeshSafe API 进程
│
├── FastAPI app
│ └── startup event
│ ├── ① DIAG_ENABLED? → false 直接 return(全部定时器不启动)
│ ├── ② Docker 能力检测 → 存内存 dict + Redis
│ ├── ③ asyncio.create_task(_timer_loop("error_scan", 30s, R001/R005/R009-R014))
│ ├── ④ asyncio.create_task(_timer_loop("db_aggregate", 60s, R006/R007/R008))
│ └── ⑤ asyncio.create_task(_timer_loop("container", 300s, R002/R003/R004/R015))
│
├── 页面请求 GET /console/diagnostics
│ ├── 同步跑 R001/R002/R003/R004/R005(~430ms,用户无感)
│ ├── 读 Redis 缓存拿 R006-R015
│ └── 合并 + 渲染(每条带时间戳)
│
└── 手动导出 GET /api/v1/telemetry/dump
├── 阻塞跑完整规则引擎(含实时 + 日志 + 容器)
├── 抓 docker logs --tail 2000(全容器)
├── 打包 tar.gz
└── 用后台线程跑,超时 60 秒
```
### 4.2 单次运行耗时预期
| 通道 | 规则 | 耗时 | 周期 | 并发安全 |
|------|------|------|------|---------|
| 页面必跑 | R001/R002/R003/R004/R005 | ~430ms | 每次页面加载 | 同步 await,不阻塞 worker |
| 定时器 A | R001/R005/R009-R014 | ~2.5s | 30s | asyncio.create_subprocess_exec |
| 定时器 B | R006/R007/R008 | ~60ms | 60s | 单次 SQL 查询 |
| 定时器 C | R002/R003/R004/R015 | ~1.5s | 300s | clamd TCP + docker inspect × 5 |
| 导出 | 全部 15 条 + docker logs 全量 | ~5-8s | 用户手动 | 后台线程,60s 超时 |
### 4.3 Redis 缓存结构
```
ms:diag:latest ← Hash: { rules: [...], generated_at: 1730406555, docker_status: "full", enabled: true }
ms:diag:cache:R001 ← String: { "result": {...}, "ts": 1730406555, "run_by": "timer:error_scan" }
ms:diag:cache:R009 ← 同上
...
ms:diag:docker_status ← String: "full" / "partial" / "none"
ms:diag:enabled ← String: "true" / "false"
```
**TTL**:单条规则缓存 6 分钟;`latest` 整个包 10 分钟。
### 4.4 定时器异常保护
```python
async def _timer_loop(name, interval, rules, docker_status):
"""通用定时器循环。"""
while True:
try:
await asyncio.sleep(interval)
results = await _run_rule_group(rules, docker_status)
await _write_redis(results, name)
except asyncio.CancelledError:
logger.info(f"定时器 {name} 被取消")
break
except Exception as e:
logger.exception(f"定时器 {name} 异常,{interval}s 后重试: {e}")
# 继续 while True,下次 sleep 后重试
```
---
## 五、后端接口清单
| 方法 | 路径 | 说明 | 响应 |
|------|------|------|------|
| GET | `/console/diagnostics` | 诊断面板 HTML | 页面(含 DIAG_ENABLED=false 横幅)|
| GET | `/api/v1/telemetry/summary` | 实时面板数据(页面调用)| JSON |
| GET | `/api/v1/telemetry/engine-health` | 引擎健康快照 | JSON |
| GET | `/api/v1/telemetry/errors?limit=20` | 最近错误列表 | JSON |
| GET | `/api/v1/telemetry/docker-status` | Docker 能力检测结果 | JSON |
| GET | `/api/v1/telemetry/rules` | **所有规则最新结果**(带时间戳 + 运行来源)| JSON |
| GET | `/api/v1/telemetry/dump` | **一键导出 tar.gz** | 文件下载 |
| GET | `/api/v1/telemetry/config` | 当前配置快照(周期、阈值、Docker 模式)| JSON |
---
## 六、后端代码结构
```
app/
├── diagnostic/ ← 新增目录
│ ├── __init__.py
│ ├── config.py ← 读取 diagnostics.yaml + 环境变量覆盖
│ ├── engine.py ← DiagnosticEngine(跑规则集)
│ ├── rules_db.py ← DB 聚合类规则实现(R001/R005-R008)
│ ├── rules_engine.py ← 引擎健康类规则实现(R002-R004)
│ ├── rules_log.py ← 日志匹配类规则实现(R009-R014)
│ ├── rules_container.py ← 容器状态类规则实现(R015)
│ ├── docker_probe.py ← 启动时 Docker 能力检测
│ ├── collector.py ← DB + scanner.health() 原始数据拉取
│ ├── dumper.py ← 打包 tar.gz + 脱敏
│ └── scheduler.py ← 三层定时器循环
├── console.py
│ └── 新增 1 个页面路由 + 7 个 API 路由
├── main.py
│ └── startup event 加 DIAG_ENABLED 检查 + scheduler 启动
├── config/
│ └── diagnostics.yaml ← 新增配置文件
├── templates/
│ ├── diagnostics.html ← 新增诊断面板模板
│ └── _nav.html ← nav_active='diagnostics' 入口
└── Dockerfile
└── 加 `RUN apt-get install docker.io-cli`
```
---
## 七、配置文件完整内容
**文件**:`app/config/diagnostics.yaml`
```yaml
# ===============================================================
# MeshSafe Diagnostics Kit v1.0 配置
# ===============================================================
# 环境变量可覆盖:
# MESHSAFE_DIAG_ENABLED=true|false
# MESHSAFE_DIAG_DOCKER=auto|true|false
# MESHSAFE_DIAG_ERROR_INTERVAL=30
# MESHSAFE_DIAG_CONTAINER_INTERVAL=300
# MESHSAFE_DIAG_DB_INTERVAL=60
# ===============================================================
diagnostics:
# ---------- 总开关 ----------
# true = 启动定时器、检测 Docker、运行规则引擎
# false = 只保留手动导出功能,所有自动诊断关闭
enabled: true
# ---------- Docker 日志/容器状态能力 ----------
# auto = 启动时自动检测 docker.sock 挂载
# true = 强制启用,连不上启动报错
# false = 强制关闭
docker:
enabled: auto
# ---------- 后台定时器周期(秒,0 = 不跑)----------
timers:
error_scan_interval: 30 # 错误/拦截类日志(R001/R005/R009-R014)
container_check_interval: 300 # 容器/引擎类(R002/R003/R004/R015)
db_aggregate_interval: 60 # DB 聚合类(R006/R007/R008)
# ---------- 日志行数控制 ----------
docker_log_tail: 500 # 定时器 grep 最近多少行
export_log_tail: 2000 # 导出时完整抓多少行
# ---------- 规则阈值 ----------
rules:
error_ratio_threshold: 0.05 # R001: error > 5% 告警
clamav_db_stale_days: 7 # R002: 病毒库 > 7 天告警
reject_ratio_threshold: 0.20 # R007: reject > 20% 告警
suspicious_exceeds_malicious: true # R006 开关
```
---
## 八、页面设计
### 8.1 诊断面板布局
```
┌────────────────────────────────────────────────────────────────────┐
│ Nav: 概览 | 策略配置 | 任务历史 | 测试靶场 | 🔧 诊断 ← active │
│ 最后刷新: 10:29:15 🧩 Docker: ✅ 完整 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─ 实时健康概览 ────────────────────────────────────────────────┐ │
│ │ 扫描总量 恶意拦截 可疑待核 错误比例 引擎在线 │ │
│ │ 12,472 117 47 10.0% 🔴 5/5 ✅ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ ⚠️ 异常检测(本地规则引擎)───────────────────────────────────┐ │
│ │ 🔴 R001 错误比例偏高(10.0% > 阈值 5%) (10:29:15) │ │
│ │ 近 100 次扫描 12 次 error │ │
│ │ 💡 建议检查 ClamAV / Redis / Postgres 引擎 │ │
│ │ │ │
│ │ 🔴 R009 ClamAV 被 OOM Kill(重启 3 次) (10:29:15) │ │
│ │ 证据: meshsafe-clamav stderr: Killed (exit 137) │ │
│ │ 💡 建议给 clamav 容器加内存 512m → 1g │ │
│ │ │ │
│ │ 🟡 R002 ClamAV 病毒库 14 天未更新 (10:29:15) │ │
│ │ 💡 在线更新失败 → U 盘离线导入 │ │
│ │ │ │
│ │ 🟢 所有其他规则通过 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ 🧩 容器运行状态 ──────────────────────────────────────────────┐ │
│ │ API ✅ healthy restart: 0 启动: 2h 前 │ │
│ │ Postgres ✅ healthy restart: 0 │ │
│ │ Redis ✅ healthy restart: 0 │ │
│ │ ClamAV ✅ running restart: 3 ⚠️ 近 24h 不稳定 │ │
│ │ Nginx ✅ healthy restart: 0 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ 🧪 引擎状态 ─────────────────────────────────────────────────┐ │
│ │ 🦠 ClamAV ✅ 病毒库: 2026-10-14 (14天前) │ │
│ │ 🎯 YARA ✅ 规则: 205 条 │ │
│ │ 📄 Document ✅ │ │
│ │ 🔑 Keywords ✅ │ │
│ │ ⚙️ Binary ✅ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ ❌ 最近 20 条错误 ────────────────────────────────────────────┐ │
│ │ [table: 时间 | 文件名 | 错误原因 | 引擎 | 分数] │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ [📦 导出诊断包] 按钮 │
└────────────────────────────────────────────────────────────────────┘
```
### 8.2 总开关关闭时
```
┌────────────────────────────────────────────────────────────────────┐
│ ⛔ 诊断套件未启用 │
│ diagnostics.yaml: diagnostics.enabled = false │
│ │
│ 诊断套件提供实时健康检测、容器运行状态监控、一键诊断包导出。 │
│ 关闭后仅保留手动导出功能,所有自动规则引擎将停止运行。 │
│ │
│ 启用方式: │
│ 1. 修改 app/config/diagnostics.yaml: enabled → true │
│ 2. 或设置环境变量:MESHSAFE_DIAG_ENABLED=true │
│ 3. 重启 api 容器 │
│ │
│ [📦 手动导出诊断包(离线收集)] │
└────────────────────────────────────────────────────────────────────┘
```
---
## 九、实施计划
```
Phase 1(3-4 天)— 核心框架
├── app/diagnostic/ 目录结构 + 配置读取
├── DiagnosticEngine 框架 + 规则基类
├── DB 聚合类规则(R001/R005-R008)
├── 引擎协议类规则(R002/R003/R004)
└── Docker 能力检测
Phase 2(2-3 天)— 定时器 + 页面
├── 三层定时器 + Redis 缓存
├── 页面必跑规则通道
├── 诊断面板 HTML 模板(含 Docker 状态 + 总开关横幅)
├── 异常列表 + 时间戳显示
└── docker-compose.yml api 服务加 docker.sock 挂载
Phase 3(2 天)— 日志匹配 + 导出
├── 日志匹配类规则(R009-R014)
├── docker inspect 类规则(R015)
├── 一键导出 tar.gz + 脱敏
├── Dockerfile 加 docker CLI
└── 导出包结构完成
Phase 4(1 天)— 测试 + 验证
├── docker build + 重启 + 功能验证
├── 模拟无 Docker 环境(不挂 socket)→ 验证降级
├── 关闭总开关 → 验证横幅 + 手动导出仍可用
└── 全接口自测
```
---
## 十、后续演进(不在 v1.0)
| 版本 | 功能 |
|------|------|
| v1.1 | 导出包可选"含完整策略 YAML";规则阈值页面可配(不用改 yaml)|
| v1.1 | 导出包可选"上传到指定 S3 桶"(如果客户有内网 S3)|
| v2.0 | 云端收集服务(MeshSafe Pulse):多客户聚合 + dashboard + 规则引擎云端版 |
| v2.0 | LLM 辅助分析:把 diagnostics.tar.gz 喂给 LLM 生成结构化报告 |
| v3.0 | AI 辅助修复闭环(仅在你自己的 sandbox 里做,不碰客户环境)|
---
## 附录 A:Dockerfile 改动
```dockerfile
# ===== 新增:装 docker CLI =====
# api 容器需要 docker CLI 才能通过 docker.sock 访问宿主机 Docker
RUN apt-get update && apt-get install -y --no-install-recommends \
docker.io-cli \
&& rm -rf /var/lib/apt/lists/*
```
## 附录 B:docker-compose.yml 改动
```yaml
api:
volumes:
# ... 原有 volume ...
- /var/run/docker.sock:/var/run/docker.sock:ro # ← 新增:只读挂载
```
## 附录 C:快速降级指引
| 问题 | 处理 |
|------|------|
| 启动报 `ModuleNotFoundError: docker` | 忘装 docker CLI,跑 `apt-get install docker.io-cli` |
| 启动后页面显示 `ℹ️ Docker 能力未启用` | 没挂 docker.sock;compose api volumes 加一行 + rebuild |
| 想完全关掉诊断 | `diagnostics.enabled: false` 或环境变量 |
| 定时器吃 CPU | 三个定时器周期都可以调大;或设为 0 不跑某个 |
| 导出包太大 | `export_log_tail` 调小;或只导出 `scan_tasks.json` |
---
**文档结束** — 待审核 → 确认后开始编码