feat: 新增补丁管理和异常检测插件及相关功能

feat(protocol): 添加补丁管理和行为指标协议类型
feat(client): 实现补丁管理插件采集功能
feat(server): 添加补丁管理和异常检测API
feat(database): 新增补丁状态和异常检测相关表
feat(web): 添加补丁管理和异常检测前端页面
fix(security): 增强输入验证和防注入保护
refactor(auth): 重构认证检查逻辑
perf(service): 优化Windows服务恢复策略
style: 统一健康评分显示样式
docs: 更新知识库文档
This commit is contained in:
iven
2026-04-11 15:59:53 +08:00
parent b5333d8c93
commit 60ee38a3c2
49 changed files with 3988 additions and 461 deletions

255
wiki/SECURITY-AUDIT.md Normal file
View File

@@ -0,0 +1,255 @@
# CSM 安全审计报告
> **审计日期**: 2026-04-11 | **审计范围**: 全系统 (Server + Client + Protocol + Frontend)
> **方法论**: OWASP Top 10 (2021), CWE Top 25, 手动源码审查 + 攻击者视角分析
---
## 执行摘要
| 严重级别 | 数量 | 说明 |
|----------|------|------|
| **CRITICAL** | 4 | 可直接被远程利用,导致系统完全沦陷 |
| **HIGH** | 12 | 需特定条件但影响重大 |
| **MEDIUM** | 12 | 有限影响或需较高权限 |
| **LOW** | 8 | 纵深防御建议 |
**最关键发现**: JWT Secret 硬编码在版本控制中、注册 Token 为空允许任意设备注册、凭据文件无 ACL 保护、默认无 TLS 传输加密。这四个问题组合意味着攻击者可以在数分钟内完全接管系统。
---
## CRITICAL (4)
### AUD-001: JWT Secret 硬编码在 config.toml 中
- **文件**: `config.toml:12``crates/server/src/config.rs`
- **CWE**: CWE-798 (硬编码凭证)
- **OWASP**: A07:2021 - 安全配置错误
**漏洞代码**:
```toml
jwt_secret = "39ffc129-dd62-4eb4-bbc0-8bf4b8e2ccc7"
```
**攻击场景**: 任何能访问仓库的人可提取 secret为任意用户含 admin伪造 JWT获得系统完全管理控制权可推送恶意配置到所有医院终端、禁用安全控制、篡改审计记录。
**修复**:
1.`config.toml` 中移除硬编码 secret
2. 通过 `CSM_JWT_SECRET` 环境变量独占加载
3. secret 为空时拒绝启动
4. **立即轮换**已泄露的 secret `39ffc129-dd62-4eb4-bbc0-8bf4b8e2ccc7`
5.`config.toml` 加入 `.gitignore`
---
### AUD-002: 空 Registration Token — 任意设备注册
- **文件**: `config.toml:1`, `crates/server/src/tcp.rs:549-558`
- **CWE**: CWE-306 (关键功能缺失认证)
- **OWASP**: A07:2021
**漏洞代码**:
```toml
registration_token = ""
```
```rust
if !expected_token.is_empty() { // 空字符串直接跳过验证
```
**攻击场景**: TCP 端口 9999 可达的任何攻击者可注册恶意设备,注入伪造审计数据掩盖安全事件,获取所有插件配置(含安全策略)。
**修复**: 设置强 registration token空 token 时拒绝启动。
---
### AUD-003: Windows 凭据文件无 ACL 保护
- **文件**: `crates/client/src/main.rs:280-283`
- **CWE**: CWE-732 (关键资源权限不当)
- **OWASP**: A01:2021
**漏洞代码**:
```rust
#[cfg(not(unix))]
fn write_restricted_file(path: &std::path::Path, content: &str) -> std::io::Result<()> {
std::fs::write(path, content) // 无任何 ACL 设置
}
```
**攻击场景**: `device_secret.txt`HMAC 密钥)和 `device_uid.txt`(设备身份)以默认权限写入,设备上任何用户进程可读取。攻击者可提取密钥伪造心跳,在不同机器上模拟设备。
**修复**: 使用 `icacls``SetSecurityInfo` 设置仅 SYSTEM 可访问的 ACL。
---
### AUD-004: 默认无 TLS — 明文传输所有敏感数据
- **文件**: `config.toml` (无 `[server.tls]`), `crates/server/src/tcp.rs:411-414`
- **CWE**: CWE-319 (明文传输敏感信息)
- **OWASP**: A02:2021
**攻击场景**: TCP 端口 9999 以明文运行。`device_secret`、所有插件配置、Web 过滤规则、USB 策略、软件黑名单均以明文 JSON 传输。网络嗅探者可提取设备认证密钥并逆向工程安全策略。
**修复**: 生产环境强制 TLS无 TLS 时拒绝启动(`CSM_DEV=1` 除外)。
---
## HIGH (12)
### AUD-005: Refresh Token 未存储 — 撤销机制不完整
- **文件**: `crates/server/src/api/auth.rs:131-133`
- **CWE**: CWE-613
- `refresh_tokens` 表存在但从未写入。登录不存储 token record刷新仅检查 family 是否撤销,不验证 token 是否曾被实际颁发。无法强制注销所有 session。
### AUD-006: Refresh Token Family Rotation 存在 TOCTOU 竞争条件
- **文件**: `crates/server/src/api/auth.rs:167-183`
- **CWE**: CWE-367
- 检查 family 撤销状态与执行撤销之间无事务保护。并发使用同一 stolen token 的两个请求均可通过检查,攻击者获得全新的未撤销 token family。
### AUD-007: 客户端不验证服务器身份
- **文件**: `crates/client/src/network/mod.rs:149-162`
- **CWE**: CWE-295
- 客户端盲目连接任何响应的服务器。ARP/DNS 欺骗攻击者可推送恶意配置禁用所有安全插件、注入有害规则。HMAC 仅保护心跳,不保护配置推送。
### AUD-008: PowerShell 命令注入面
- **文件**: `crates/client/src/asset/mod.rs:82-83`, `crates/client/src/clipboard_control/mod.rs:143-159`
- **CWE**: CWE-78
- `powershell_lines()` 通过 `format!()` 拼接命令参数。若服务器推送含引号/转义字符的恶意规则,可能导致 PowerShell 命令注入。
### AUD-009: 服务停止/卸载未受保护
- **文件**: `crates/client/src/service.rs:17-69`
- **CWE**: CWE-284
- `csm-client.exe --uninstall` 无认证保护。终端管理员权限用户可完全移除安全代理,绕过所有监控。无服务恢复策略、无看门狗进程、无反调试保护。
### AUD-010: JWT Token 存储在 localStorage
- **文件**: `web/src/lib/api.ts:25-28, 175-176`
- **CWE**: CWE-922
- Access token 和 refresh token 均存储在 `localStorage`,可被同源任意 JS 访问。XSS 漏洞可直接窃取 7 天有效期的 refresh token。
### AUD-011: CSP 允许 unsafe-inline + unsafe-eval
- **文件**: `crates/server/src/main.rs:142-143`
- **CWE**: CWE-693
- `script-src 'self' 'unsafe-inline' 'unsafe-eval'` 使 CSP 对 XSS 几乎无效。结合 localStorage token 存储,单个 XSS 即可导致管理员会话完全沦陷。
### AUD-012: WebSocket JWT 在 URL 查询参数中
- **文件**: `crates/server/src/ws.rs:36-73`
- **CWE**: CWE-312
- JWT 通过 `/ws?token=eyJ...` 传输。Token 出现在浏览器历史、服务器访问日志、代理日志中。且 WebSocket handler 不检查用户角色,非管理员可接收所有广播事件。
### AUD-013: 告警规则 Webhook SSRF
- **文件**: `crates/server/src/api/alerts.rs:115-131`
- **CWE**: CWE-918
- `notify_webhook` 字段无 URL 验证。可设置为 `http://169.254.169.254/latest/meta-data/` (AWS 元数据) 或 `file:///etc/passwd`,将服务器变成 SSRF 代理。
### AUD-014: 仅基于用户名的速率限制可绕过
- **文件**: `crates/server/src/api/auth.rs:101`
- **CWE**: CWE-307
- 速率限制仅以用户名为 key。攻击者可用 `Admin``ADMIN` 等变体绕过。无 IP 限制,可分布式暴力破解。
### AUD-015: 磁盘加密确认在只读路由层 — 权限提升
- **文件**: `crates/server/src/api/plugins/mod.rs:42`
- **CWE**: CWE-862
- PUT `acknowledge_alert``read_routes()` 中(仅需认证,不需 admin。任何认证用户可确认忽略加密告警掩盖合规违规。
### AUD-016: 初始管理员密码输出到 stderr
- **文件**: `crates/server/src/main.rs:270-275`
- **CWE**: CWE-532
- 初始密码通过 `eprintln!` 输出。容器化部署中 stderr 被日志聚合系统捕获,有日志访问权限者可获取管理员密码。
---
## MEDIUM (12)
| # | 发现 | 文件 | CWE |
|---|------|------|-----|
| AUD-017 | 多个 Update handler 跳过输入验证 (软件黑名单/Web过滤器/剪贴板) | `software_blocker.rs:79`, `web_filter.rs:62`, `clipboard_control.rs:107` | CWE-20 |
| AUD-018 | USB 策略 rules 字段接受任意 JSON 无验证 | `usb.rs:94-135` | CWE-20 |
| AUD-019 | 密码无最大长度限制 (bcrypt 72 字节截断) | `auth.rs:303` | CWE-20 |
| AUD-020 | 多个字段缺少长度验证 (弹出窗口/剪贴板/USB策略名) | 多处 | CWE-20 |
| AUD-021 | 多个列表端点无分页 (黑名单/白名单/规则/策略) | 多处 | CWE-770 |
| AUD-022 | 磁盘加密状态列表无分页可全库转储 | `disk_encryption.rs:12-55` | CWE-770 |
| AUD-023 | JWT 角色仅信任 claim 不查库 (降级延迟) | `auth.rs:273` | CWE-863 |
| AUD-024 | 缺少 HSTS 头 | `main.rs:123-143` | CWE-319 |
| AUD-025 | CORS 配置需严格限制 | `main.rs:284-301` | CWE-942 |
| AUD-026 | 日志中泄露设备 UID 和服务器地址 | `main.rs:62`, `network/mod.rs:34` | CWE-532 |
| AUD-027 | 注册 Token 回退空字符串 | `main.rs:72` | CWE-254 |
| AUD-028 | conflict.rs 中 format! SQL 模式 (当前安全但脆弱) | `conflict.rs:205` | CWE-89 |
---
## LOW (8)
| # | 发现 | 文件 |
|---|------|------|
| AUD-029 | 组名未过滤 HTML 特殊字符 | `groups.rs:72-96` |
| AUD-030 | 弹出窗口阻止器更新可创建无过滤器的规则 | `popup_blocker.rs:67-104` |
| AUD-031 | 设备删除非原子 (自毁帧在事务前发送) | `devices.rs:215-306` |
| AUD-032 | 受保护进程列表硬编码且可修补绕过 | `software_blocker/mod.rs:9-73` |
| AUD-033 | hosts 文件修改可能与 EDR 冲突 | `web_filter/mod.rs:59-93` |
| AUD-034 | 软件拦截器 TOCTOU 竞争条件 (已缓解) | `software_blocker/mod.rs:329-386` |
| AUD-035 | 前端路由守卫不验证 JWT 签名 | `router/index.ts:38-49` |
| AUD-036 | WebSocket 不验证入站消息 (当前丢弃) | `ws.rs:108-119` |
---
## 修复优先级
### P0 — 立即 (24h)
| 修复项 | 对应发现 | 工作量 |
|--------|---------|--------|
| 轮换 JWT Secret移至环境变量 | AUD-001 | 30min |
| 设置非空 registration_token | AUD-002 | 15min |
| 凭据文件添加 Windows ACL | AUD-003 | 1h |
| 生产环境强制 TLS | AUD-004 | 2h |
### P1 — 短期 (1 周)
| 修复项 | 对应发现 | 工作量 |
|--------|---------|--------|
| Refresh token 存储到 DB + 事务保护 | AUD-005, 006 | 4h |
| Update handler 添加输入验证 | AUD-017 | 4h |
| Webhook URL 验证防 SSRF | AUD-013 | 1h |
| 磁盘加密确认移至 admin 路由 | AUD-015 | 15min |
| 初始密码写入文件替代 stderr | AUD-016 | 30min |
| 添加 IP 速率限制 | AUD-014 | 2h |
### P2 — 中期 (1 月)
| 修复项 | 对应发现 | 工作量 |
|--------|---------|--------|
| Token 迁移至 HttpOnly Cookie | AUD-010 | 8h |
| CSP 强化 (nonce-based) | AUD-011 | 4h |
| WebSocket ticket 认证 | AUD-012 | 4h |
| 服务器身份验证 (证书固定) | AUD-007 | 8h |
| 服务保护 (恢复策略/看门狗) | AUD-009 | 4h |
| PowerShell 注入面消除 | AUD-008 | 6h |
| HSTS + Permissions-Policy 头 | AUD-024, 036 | 1h |
| 分页补充 | AUD-021, 022 | 4h |
---
## 安全亮点 (做得好的地方)
1. **SQL 注入防御**: 全库一致使用 `sqlx::bind()` 参数化
2. **错误处理**: `ApiResponse::internal_error()` 不泄露内部错误详情
3. **密码哈希**: bcrypt cost=12符合行业标准
4. **Token Family 轮换**: 检测 token 重放并撤销整个 family
5. **常量时间比较**: 注册 token 验证已使用 `constant_time_eq()`
6. **帧速率限制**: 100 帧/5秒/连接
7. **审计日志**: 所有管理员写入操作记录到 `admin_audit_log`
8. **HMAC 心跳**: 设备认证使用 HMAC-SHA256
9. **进程保护列表**: 防止误杀系统关键进程
10. **输入验证**: Create handler 普遍有字段验证

88
wiki/client.md Normal file
View File

@@ -0,0 +1,88 @@
# Client客户端代理
## 设计思想
`csm-client` 是部署在医院终端设备上的 Windows 代理程序,设计为:
1. **无人值守运行** — 支持控制台模式(开发调试)和 Windows 服务模式(生产部署)
2. **自动重连** — 指数退避策略1s → 60s断线后 drain stale frames
3. **插件化采集** — 每个插件独立 task通过 `watch` channel 接收配置,通过 `mpsc` channel 上报数据
4. **单入口 data channel** — 所有插件共享一个 `mpsc::channel::<Frame>(1024)`network 模块统一发送
关键设计决策:
- **watch + mpsc 双通道** — `watch` 用于服务器推送配置到插件(多消费者最新值),`mpsc` 用于插件上报数据到网络层(多生产者有序队列)
- **device_uid 持久化** — UUID 首次生成后写入 `device_uid.txt`,与可执行文件同目录
- **device_secret 持久化** — 注册成功后写入 `device_secret.txt`,重启后自动认证
## 代码逻辑
### 启动流程
```
main() → load device_uid → load device_secret → create ClientState
→ create data channel (mpsc 1024)
→ create watch channels for each plugin config
→ spawn core tasks (monitor, asset, usb)
→ spawn plugin tasks (11 plugins)
→ reconnect loop: connect_and_run() with exponential backoff
```
### 网络层 (`network/mod.rs`)
- `connect_and_run()` — TCP 连接、注册/认证、双工读写循环
- `handle_server_message()` — 根据 MessageType 分发服务器下发的帧到对应 watch channel
- `PluginChannels` — 持有所有插件的 `watch::Sender`,用于接收服务器推送的配置
- 注册流程:发送 Register → 收到 RegisterResponse含 device_secret→ 持久化 secret
- 认证流程:已有 device_secret 时,心跳帧携带 HMAC-SHA256 签名
### 插件统一模板
每个插件遵循相同模式:
```rust
pub async fn start(
mut config_rx: watch::Receiver<PluginConfig>,
data_tx: mpsc::Sender<Frame>,
device_uid: String,
) {
loop {
tokio::select! {
result = config_rx.changed() => { /* 更新 config */ }
_ = interval.tick() => {
if !config.enabled { continue; }
// 采集数据 → Frame::new_json() → data_tx.send()
}
}
}
}
```
### 双模式运行
- **控制台模式**: 直接 `cargo run -p csm-client`Ctrl+C 优雅退出
- **服务模式**: `--install` 注册 Windows 服务、`--service` 以服务方式运行、`--uninstall` 卸载
## 关联模块
- [[protocol]] — 使用 Frame 构造上报帧,解析服务器下发帧
- [[server]] — TCP 连接的对端,接收帧并处理
- [[plugins]] — 每个插件的具体实现逻辑
## 关键文件
| 文件 | 职责 |
|------|------|
| `crates/client/src/main.rs` | 启动入口、插件 channel 创建、task spawn、重连循环 |
| `crates/client/src/network/mod.rs` | TCP 连接、注册认证、双工读写、服务器消息分发 |
| `crates/client/src/service.rs` | Windows 服务安装/卸载/运行(`#[cfg(target_os = "windows")]` |
| `crates/client/src/monitor/mod.rs` | 核心设备状态采集CPU/内存/进程) |
| `crates/client/src/asset/mod.rs` | 硬件/软件资产采集 |
| `crates/client/src/usb/mod.rs` | USB 设备插拔监控 |
| `crates/client/src/web_filter/mod.rs` | 上网拦截插件 |
| `crates/client/src/usage_timer/mod.rs` | 使用时长记录插件 |
| `crates/client/src/software_blocker/mod.rs` | 软件禁止安装插件 |
| `crates/client/src/popup_blocker/mod.rs` | 弹窗拦截插件 |
| `crates/client/src/usb_audit/mod.rs` | U盘文件操作审计插件 |
| `crates/client/src/watermark/mod.rs` | 屏幕水印插件 |
| `crates/client/src/disk_encryption/mod.rs` | 磁盘加密检测插件 |
| `crates/client/src/print_audit/mod.rs` | 打印审计插件 |
| `crates/client/src/clipboard_control/mod.rs` | 剪贴板管控插件 |
| `crates/client/src/patch/mod.rs` | 补丁管理插件 |

71
wiki/database.md Normal file
View File

@@ -0,0 +1,71 @@
# Database数据库层
## 设计思想
SQLite 单文件数据库WAL 模式支持并发读写。设计原则:
1. **只追加迁移** — 永不修改已有 migration 文件
2. **参数绑定** — 所有 SQL 使用 `.bind()`,绝不拼接
3. **upsert 模式**`ON CONFLICT ... DO UPDATE` 处理重复上报,必须更新 `updated_at`
4. **嵌入式迁移** — SQL 文件通过 `include_str!` 编译进二进制,运行时按序执行
5. **外键启用**`foreign_keys(true)` 强制引用完整性
## 代码逻辑
### 初始化
```
main() → init_database() → SQLite WAL + Normal sync + 5s busy timeout + FK on
→ run_migrations() → CREATE _migrations 表 → 按序执行 001-018
→ ensure_default_admin() → 首次启动生成随机 admin 密码
```
### 连接池配置
- 最大 8 连接
- cache_size = -64000 (64MB)
- wal_autocheckpoint = 1000
### 迁移历史
| # | 文件 | 内容 |
|---|------|------|
| 001 | init.sql | users, devices 表 |
| 002 | assets.sql | hardware_assets, software_assets, asset_changes 表 |
| 003 | usb.sql | usb_events, usb_policies, usb_device_patterns 表 |
| 004 | alerts.sql | alert_rules, alert_records 表 |
| 005 | web_filter.sql | web_filter_rules, web_access_logs 表 |
| 006 | usage_timer.sql | usage_daily, app_usage 表 |
| 007 | software_blocker.sql | software_blacklist, software_violations 表 |
| 008 | popup_blocker.sql | popup_blocker_rules, popup_block_stats 表 |
| 009 | usb_file_audit.sql | usb_file_operations 表 |
| 010 | watermark.sql | watermark_configs 表 |
| 011 | token_security.sql | token_families 表JWT token family 轮换) |
| 012 | disk_encryption.sql | disk_encryption_status, disk_encryption_alerts 表 |
| 013 | print_audit.sql | print_events 表 |
| 014 | clipboard_control.sql | clipboard_rules, clipboard_violations 表 |
| 015 | plugin_control.sql | plugin_states 表 |
| 016 | encryption_alerts_unique.sql | 唯一约束修复 |
| 017 | device_health_scores.sql | device_health_scores 表 |
| 018 | patch_management.sql | patch_status 表 |
### 数据操作层 (`db.rs`)
`DeviceRepo` 提供:
- 设备注册/查询/删除/分组
- 资产增删改查
- USB 事件记录和策略管理
- 告警规则和记录操作
- 所有插件数据的 CRUD
## 关联模块
- [[server]] — 通过 db.rs 访问数据库
- [[plugins]] — 每个插件有对应的数据库表
## 关键文件
| 文件 | 职责 |
|------|------|
| `crates/server/src/db.rs` | DeviceRepo 数据库操作方法集合 |
| `crates/server/src/main.rs` | 数据库初始化、迁移执行 |
| `migrations/001_init.sql` ~ `018_*.sql` | 数据库迁移脚本 |

46
wiki/index.md Normal file
View File

@@ -0,0 +1,46 @@
# CSM 知识库
## 项目画像
CSM (Client Security Manager) — 医院终端安全管控平台C/S + Web 三层架构。管理 11 个安全插件覆盖上网拦截、U盘管控、打印审计、剪贴板管控、补丁管理等场景。
**关键数字**: 3 个 Rust crate + Vue 前端 | 18 个数据库迁移 | 13 个客户端插件 | ~30 个 API 端点 | 自定义 TCP 二进制协议
## 模块导航树
```
CSM
├── [[protocol]] — 二进制协议层Frame 编解码、MessageType、payload 定义)
├── [[server]] — 服务端HTTP API + TCP 接入 + WebSocket + SQLite
├── [[client]] — 客户端代理Windows 服务、插件采集、自动重连)
├── [[web-frontend]] — Web 管理面板Vue 3 SPA
├── [[plugins]] — 插件体系(端到端设计、新增插件清单)
└── [[database]] — 数据库层SQLite、迁移、操作方法
```
## 核心架构决策
### 为什么用自定义 TCP 二进制协议而不是 HTTP
内网环境低延迟需求,二进制帧比 HTTP 更省带宽和延迟。帧头仅 10 字节MAGIC+VERSION+TYPE+LENGTHpayload 用 JSON 保持可调试性。
### 为什么插件配置用 watch channel 而不是 HTTP 轮询?
Server 主动推送配置变更到 Client避免轮询延迟。`tokio::watch` 保证每个插件总是读到最新配置值,配置下发 → 全链路秒级生效。
### 为什么嵌入前端而不是独立部署?
`include_dir!` 编译时打包 `web/dist/`,部署只需一个 server 二进制文件。SPA fallback 让前端路由(如 `/devices`)直接返回 `index.html`
### 为什么 SQLite 而不是 PostgreSQL
医院内网单机部署场景零外部依赖。WAL 模式 + 64MB 缓存足以支撑数百台终端的并发写入。
### 为什么三级作用域推送global/group/device
医院按科室分组管理设备。全局策略作为基线,科室策略覆盖特定需求,单设备策略处理例外情况。`push_to_targets()` 自动解析作用域并过滤在线设备。
## 技术栈速查
| 层 | 技术 |
|----|------|
| 服务端 | Rust + Axum + SQLx + SQLite + JWT + Rustls |
| 客户端 | Rust + Tokio + sysinfo + windows-rs |
| 协议 | 自定义 TCP 二进制MAGIC + VERSION + TYPE + LENGTH + JSON payload |
| 前端 | Vue 3 + TypeScript + Vite + Element Plus + Pinia + ECharts |
| 构建 | Cargo workspace + npm |

78
wiki/plugins.md Normal file
View File

@@ -0,0 +1,78 @@
# Plugin System插件体系
## 设计思想
CSM 的核心扩展机制,采用**端到端插件化**设计:
- Client 端每个插件独立 tokio task负责数据采集/策略执行
- Server 端每个插件有独立的 API handler 模块和数据库表
- Protocol 层每个插件有专属 MessageType 范围和 payload struct
- Frontend 端每个插件有独立页面组件
三级配置推送:`global``group``device`,优先级递增。
## 代码逻辑
### 插件全链路(以 Web Filter 为例)
```
1. API: POST /api/plugins/web-filter/rules → server/api/plugins/web_filter.rs
2. Server 存储 → db.rs → INSERT INTO web_filter_rules
3. 推送 → push_to_targets(db, clients, WebFilterRuleUpdate, payload, scope, target_id)
4. TCP → Client network/mod.rs handle_server_message() → web_filter_tx.send()
5. Client → web_filter/mod.rs config_rx.changed() → 更新本地规则 → 采集上报
6. Client → Frame::new_json(WebAccessLog, entry) → data_tx → network → TCP → server
7. Server → tcp.rs process_frame(WebAccessLog) → db.rs → INSERT INTO web_access_logs
8. Frontend → GET /api/plugins/web-filter/log → 展示
```
### 现有插件一览
| 插件 | 消息类型范围 | 方向 | 功能 |
|------|-------------|------|------|
| Web Filter | 0x2x | S→C 规则, C→S 日志 | URL 黑白名单、访问日志 |
| Usage Timer | 0x3x | C→S 报告 | 每日使用时长、应用使用统计 |
| Software Blocker | 0x4x | S→C 黑名单, C→S 违规 | 禁止安装软件、违规上报 |
| Popup Blocker | 0x5x | S→C 规则, C→S 统计 | 弹窗拦截规则、拦截统计 |
| USB File Audit | 0x6x | C→S 记录 | U盘文件操作审计 |
| Watermark | 0x70 | S→C 配置 | 屏幕水印显示配置 |
| USB Policy | 0x71 | S→C 策略 | U盘管控全阻/白名单/黑名单) |
| Plugin Control | 0x80-0x81 | S→C 命令 | 远程启停插件 |
| Disk Encryption | 0x90, 0x93 | C→S 状态, S→C 配置 | 磁盘加密状态检测 |
| Print Audit | 0x91 | C→S 事件 | 打印操作审计 |
| Clipboard Control | 0x94-0x95 | S→C 规则, C→S 违规 | 剪贴板操作管控(仅上报元数据) |
| Patch Management | 0xA0-0xA2 | 双向 | 系统补丁扫描与安装 |
| Behavior Metrics | 0xB0 | C→S 指标 | 行为指标采集(异常检测输入) |
### 新增插件必改文件清单
| # | 文件 | 改动 |
|---|------|------|
| 1 | `crates/protocol/src/message.rs` | 添加 MessageType 枚举值 + payload struct |
| 2 | `crates/protocol/src/lib.rs` | re-export 新类型 |
| 3 | `crates/client/src/<plugin>/mod.rs` | 创建插件实现 |
| 4 | `crates/client/src/main.rs` | `mod <plugin>`, watch channel, PluginChannels 字段, spawn |
| 5 | `crates/client/src/network/mod.rs` | PluginChannels 字段, handle_server_message 分支 |
| 6 | `crates/server/src/api/plugins/<plugin>.rs` | 创建 API handler |
| 7 | `crates/server/src/api/plugins/mod.rs` | mod 声明 + 路由注册 |
| 8 | `crates/server/src/tcp.rs` | process_frame 新分支 + push_all_plugin_configs |
| 9 | `crates/server/src/db.rs` | 新增 DB 操作方法 |
| 10 | `migrations/NNN_<name>.sql` | 新迁移文件 |
| 11 | `crates/server/src/main.rs` | include_str! 新迁移 |
## 关联模块
- [[protocol]] — 定义插件的 MessageType 和 payload
- [[client]] — 插件采集端
- [[server]] — 插件 API 和数据处理
- [[web-frontend]] — 插件管理页面
- [[database]] — 每个插件的数据库表
## 关键文件
| 文件 | 职责 |
|------|------|
| `crates/client/src/<plugin>/mod.rs` | 客户端插件实现(每个插件一个目录) |
| `crates/server/src/api/plugins/<plugin>.rs` | 服务端插件 API每个插件一个文件 |
| `crates/server/src/tcp.rs` | 帧分发 + push_to_targets + push_all_plugin_configs |
| `crates/client/src/main.rs` | 插件 watch channel 创建 + task spawn |
| `crates/client/src/network/mod.rs` | PluginChannels 定义 + 服务器消息分发 |

58
wiki/protocol.md Normal file
View File

@@ -0,0 +1,58 @@
# Protocol二进制协议层
## 设计思想
`csm-protocol` 是 Server 和 Client 共享的协议定义 crate。核心设计决策
1. **零拷贝编解码**`Frame::encode()` / `Frame::decode()` 直接操作字节切片,无中间分配
2. **类型安全**`MessageType` 枚举确保所有消息类型在编译期可见,`TryFrom<u8>` 处理未知类型
3. **JSON payload** — 网络传输用 JSON`serde`),兼顾可调试性和跨语言兼容性
4. **payload 上限 4MB**`MAX_PAYLOAD_SIZE` 防止恶意帧耗尽内存
二进制帧格式:`MAGIC(4B "CSM\0") + VERSION(1B) + TYPE(1B) + LENGTH(4B big-endian) + PAYLOAD(变长 JSON)`
## 代码逻辑
### 帧生命周期
```
发送方: T → Frame::new_json(mt, &data) → Frame::encode() → Vec<u8> → TCP stream
接收方: TCP bytes → Frame::decode(&buf) → Option<Frame> → Frame::decode_payload::<T>()
```
### MessageType 分块规划
| 范围 | 插件 | 方向 |
|------|------|------|
| 0x01-0x0F | Core心跳/注册/状态/资产) | 双向 |
| 0x10-0x1F | Core Server→Client策略/配置/任务) | S→C |
| 0x20-0x2F | Web Filter | C→S 日志, S→C 规则 |
| 0x30-0x3F | Usage Timer | C→S 报告 |
| 0x40-0x4F | Software Blocker | C→S 违规, S→C 黑名单 |
| 0x50-0x5F | Popup Blocker | C→S 统计, S→C 规则 |
| 0x60-0x6F | USB File Audit | C→S 操作记录 |
| 0x70-0x7F | Watermark + USB Policy | S→C 配置 |
| 0x80-0x8F | Plugin Control | S→C 启停命令 |
| 0x90-0x9F | Disk Encryption / Print / Clipboard | 混合 |
| 0xA0-0xAF | Patch Management | C→S 状态, S→C 配置 |
| 0xB0-0xBF | Behavior Metrics | C→S 指标 |
### 关键类型
- `Frame` — 帧结构version + msg_type + payload bytes
- `FrameError` — 解码错误枚举InvalidMagic / UnknownMessageType / PayloadTooLarge / Io
- 每个 MessageType 对应一个 payload struct`WebAccessLogEntry`, `HeartbeatPayload`
## 关联模块
- [[server]] — TCP 接入层调用 `Frame::decode()` 解析客户端帧,调用 `push_to_targets()` 推送配置帧
- [[client]] — 通过 `Frame::new_json()` 构造上报帧,通过 `Frame::decode()` 解析服务器下发的帧
- [[plugins]] — 每个插件定义自己的 payload struct 在此 crate 中
## 关键文件
| 文件 | 职责 |
|------|------|
| `crates/protocol/src/message.rs` | MessageType 枚举、Frame 编解码、所有 payload struct |
| `crates/protocol/src/device.rs` | DeviceStatus、ProcessInfo、HardwareAsset、UsbEvent 等设备相关类型 |
| `crates/protocol/src/lib.rs` | Re-export 所有公开类型 |

84
wiki/server.md Normal file
View File

@@ -0,0 +1,84 @@
# Server服务端
## 设计思想
`csm-server` 是整个系统的核心枢纽,同时承载三个协议:
1. **TCP 二进制协议** (端口 9999) — 接入 Client 代理
2. **HTTP REST API** (端口 9998) — 服务 Web 面板
3. **WebSocket** (`/ws`) — 实时推送设备状态变更到前端
关键设计决策:
- **SQLite + WAL** — 单机部署零依赖WAL 模式支持并发读写
- **include_dir 嵌入前端** — 编译时将 `web/dist/` 打包进二进制,部署只需一个文件
- **三层权限** — public登录/健康检查)→ authenticated只读→ admin写操作
- **ClientRegistry** — `Arc<RwLock<HashMap>>` 管理在线客户端的 TCP 写端,支持 `push_to_targets()` 三级作用域推送
## 代码逻辑
### 启动流程
```
main() → load config → init SQLite → run migrations → ensure admin
→ spawn TCP listener (9999)
→ spawn alert cleanup task
→ spawn health score task
→ build HTTP router (9998) with CORS/security headers/SPA fallback
→ axum::serve()
```
### TCP 接入层 (`tcp.rs`)
- `start_tcp_server()` — 监听 TCP每连接 spawn 一个 task
- `process_frame()` — 根据 MessageType 分发到对应 handler需先 verify_device_uid
- `ClientRegistry` — 线程安全的在线设备注册表,支持 `list_online()``send_frame()`
- `push_to_targets(db, clients, msg_type, payload, target_type, target_id)` — 三级作用域推送global/group/device
- 帧速率限制100 帧/5秒/连接
- HMAC 验证:心跳帧必须携带 HMAC-SHA256 签名,连续 3 次失败断开
- 空闲超时180 秒无数据断开
- 最大并发连接500
### HTTP API (`api/`)
路由分三层:
- **public**: `/api/auth/login`, `/api/auth/refresh`, `/health`
- **authenticated** (require_auth 中间件): GET 类设备/资产/告警/插件查询
- **admin** (require_admin + require_auth): 设备删除、策略增删改、插件配置写入
统一响应格式 `ApiResponse<T>``{ success, data, error }`,分页默认 page=1, page_size=20, 上限 100。
### WebSocket (`ws.rs`)
- `WsHub` 广播设备上线/离线/状态变更事件给所有连接的前端客户端
- JWT 认证通过 query parameter `?token=xxx`
### 后台任务
- `alert::cleanup_task()` — 定期清理过期告警
- `health::health_score_task()` — 定期计算设备健康评分
## 关联模块
- [[protocol]] — 使用 Frame 编解码和 MessageType 分发
- [[client]] — TCP 连接的对端
- [[web-frontend]] — HTTP API 和 WebSocket 的消费者
- [[plugins]] — API 层的 plugins/ 子模块处理所有插件相关路由
- [[database]] — 数据库操作集中在 db.rs
## 关键文件
| 文件 | 职责 |
|------|------|
| `crates/server/src/main.rs` | 启动入口、数据库初始化、迁移、路由组装、SPA fallback |
| `crates/server/src/tcp.rs` | TCP 监听、帧处理、ClientRegistry、push_to_targets |
| `crates/server/src/ws.rs` | WebSocket hub 广播 |
| `crates/server/src/api/mod.rs` | 路由定义、ApiResponse 信封、Pagination |
| `crates/server/src/api/auth.rs` | JWT 登录/刷新/改密、限流、require_auth/require_admin 中间件 |
| `crates/server/src/api/devices.rs` | 设备列表/详情/状态/历史/健康评分 API |
| `crates/server/src/api/plugins/mod.rs` | 插件路由注册read_routes + write_routes |
| `crates/server/src/api/plugins/*.rs` | 各插件 API handler每个插件一个文件 |
| `crates/server/src/db.rs` | DeviceRepo 数据库操作方法集合 |
| `crates/server/src/config.rs` | AppConfig TOML 配置加载 |
| `crates/server/src/health.rs` | 设备健康评分计算 |
| `crates/server/src/anomaly.rs` | 异常检测逻辑 |
| `crates/server/src/alert.rs` | 告警处理与清理 |
| `crates/server/src/audit.rs` | 审计日志 |

70
wiki/web-frontend.md Normal file
View File

@@ -0,0 +1,70 @@
# Web Frontend管理面板
## 设计思想
Vue 3 + TypeScript + Vite + Element Plus + Pinia + ECharts 的单页应用。关键决策:
1. **SPA 嵌入部署** — 构建产物 `web/dist/` 通过 `include_dir!` 编译进 server 二进制,部署零额外依赖
2. **JWT 本地存储** — token 存 `localStorage`路由守卫检查过期30 秒内即将过期视为无效
3. **按路由懒加载** — 所有页面组件使用 `() => import(...)` 动态导入
## 代码逻辑
### 路由结构
```
/login → Login.vue公开
/ → Layout.vue认证后
/dashboard → Dashboard.vue仪表盘/健康概览)
/devices → Devices.vue设备列表
/devices/:uid → DeviceDetail.vue设备详情
/usb → UsbPolicy.vueU盘策略管理
/alerts → Alerts.vue告警管理
/settings → Settings.vue系统设置
/plugins/web-filter → WebFilter.vue
/plugins/usage-timer → UsageTimer.vue
/plugins/software-blocker → SoftwareBlocker.vue
/plugins/popup-blocker → PopupBlocker.vue
/plugins/usb-file-audit → UsbFileAudit.vue
/plugins/watermark → Watermark.vue
/plugins/disk-encryption → DiskEncryption.vue
/plugins/print-audit → PrintAudit.vue
/plugins/clipboard-control → ClipboardControl.vue
/plugins/plugin-control → PluginControl.vue
```
### 认证流程
1. Login.vue → `POST /api/auth/login` → 获取 access_token + refresh_token
2. token 存入 localStorage
3. 路由守卫 `beforeEach` 检查 JWT 过期(解析 payload.exp
4. API 调用携带 `Authorization: Bearer <token>` header
5. token 过期 → 自动跳转 /login
### API 通信
`web/src/lib/api.ts` — 封装所有 API 调用,统一处理认证和错误。
### 状态管理
`web/src/stores/devices.ts` — Pinia store 管理设备列表状态。
## 关联模块
- [[server]] — 消费其 HTTP REST API 和 WebSocket 推送
- [[plugins]] — 每个插件页面对应 server 端的插件 API
## 关键文件
| 文件 | 职责 |
|------|------|
| `web/src/main.ts` | 应用入口、Vue 实例创建 |
| `web/src/App.vue` | 根组件 |
| `web/src/router/index.ts` | 路由定义、JWT 路由守卫 |
| `web/src/lib/api.ts` | API 通信封装 |
| `web/src/stores/devices.ts` | Pinia 设备状态管理 |
| `web/src/views/Layout.vue` | 主布局(侧边栏+内容区) |
| `web/src/views/Dashboard.vue` | 仪表盘页 |
| `web/src/views/Devices.vue` | 设备列表页 |
| `web/src/views/DeviceDetail.vue` | 设备详情页 |
| `web/src/views/plugins/*.vue` | 各插件管理页面 |