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:
255
wiki/SECURITY-AUDIT.md
Normal file
255
wiki/SECURITY-AUDIT.md
Normal 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
88
wiki/client.md
Normal 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
71
wiki/database.md
Normal 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
46
wiki/index.md
Normal 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+LENGTH),payload 用 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
78
wiki/plugins.md
Normal 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
58
wiki/protocol.md
Normal 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
84
wiki/server.md
Normal 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
70
wiki/web-frontend.md
Normal 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.vue(U盘策略管理)
|
||||
/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` | 各插件管理页面 |
|
||||
Reference in New Issue
Block a user