feat: 完善堡垒机与 Agent 智能运维能力

- 支持 Windows Agent 构建、安装脚本和统一上报协议

- 增强 RDP 剪贴板、虚拟盘和文件传输能力

- 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口

- 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
This commit is contained in:
Logan.Li committed 2026-07-05 23:49:36 +08:00
1 parent a9513c2056
commit e0d652d211
169 files changed
+12192 -9116

No files matched your search

+553
View File
@@ -0,0 +1,553 @@
# AIOps RDP 实现规格
> 更新:2026-07-05
> 目标:让另一个项目仅阅读本文档,也能实现一个可用的 Web RDP 堡垒机会话。
> 范围:RDP 图形连接、键鼠输入、剪贴板、RDPDR 虚拟盘、文件上传下载、录像回放、实时审计、安全策略。
---
## 1. 功能边界
### 1.1 必须实现
| 能力 | 验收标准 |
|------|----------|
| 图形登录 | 浏览器打开 Windows 桌面,登录后能稳定看到完整画面 |
| 键鼠输入 | 鼠标移动/点击/滚轮、普通键、组合键能到达远端 |
| 双向剪贴板 | 本地复制到远端、远端 Ctrl+C 后复制到浏览器 |
| 文件上传 | 浏览器上传文件后,远端 `\\Go-client\AIOps\Upload` 可见 |
| 文件下载 | 远端文件写入 `\\Go-client\AIOps\Download` 后,本地浏览器下载 |
| 录像回放 | 会话结束后能按时间顺序播放图形快照 |
| 实时审计 | 审计端可只读观看活动 RDP 会话 |
| 安全策略 | 后端能强制禁用剪贴板、上传、下载、录像、水印 |
### 1.2 不建议作为第一版实现
| 能力 | 原因 |
|------|------|
| RDPGFX/H.264 | 协议复杂,先用 Bitmap/FastPath 能完成可用闭环 |
| 动态虚拟通道 `drdynvc` | 文件和剪贴板可先用静态通道完成 |
| SMB 文件传输 | 外网 445 经常不可达,权限和安全边界差 |
| 远端任意路径浏览 | 容易变成文件管理器和权限绕过问题,先用虚拟盘 |
| 断点续传 | 大文件体验优化,基础链路稳定后再做 |
---
## 2. 端到端架构
```text
Browser
├── RdpDialog.tsx
│ ├── Canvas 绘制 RDPR 二进制帧
│ ├── 捕获 mouse/key/wheel/paste/drop
│ └── 发送 JSON input event
└── RdpToolsPanel.tsx
├── 粘贴到远端
├── 复制远端内容
└── 文件上传
Backend HTTP/WebSocket
├── /ws/rdp
│ ├── JWT 鉴权
│ ├── 加载资产和 RDP 凭据
│ ├── 加载会话安全策略
│ ├── 创建 sys_rdp_sessions
│ ├── 启动 rdp.Engine.Run()
│ └── 写入录像、审计、实时广播
├── /ws/rdp-audit/:sessionId
└── /api/v1/rdp-staged/:token
RDP Engine
├── TCP + TPKT + X.224
├── TLS + CredSSP/NLA
├── MCS/GCC/Channel Join
├── DemandActive/ConfirmActive/FontList/SuppressOutput
├── FastPath Bitmap/SurfaceCmd
├── FastPath Input
├── cliprdr 静态虚拟通道
└── rdpdr 静态虚拟通道
```
核心分层原则:
1. 控制器负责鉴权、策略、审计、WebSocket、数据库。
2. RDP 引擎只负责协议状态机和帧收发,不依赖 Gin/GORM/React。
3. 前端只知道 WebSocket 帧协议,不知道 CredSSP、MCS、PDU 细节。
4. 文件传输不走远端 SMB,不依赖远端开放 445。
---
## 3. 后端接口契约
### 3.1 连接配置
```go
type Config struct {
Host string
Port int
Username string
Password string
Domain string
Security string
Width int
Height int
Debug bool
}
```
实现要求:
- `Password` 只允许在服务端内存中存在,不返回给浏览器。
- `Width`、`Height` 是远端桌面初始分辨率,不是浏览器 CSS 尺寸。
- 浏览器最大化优先缩放 Canvas,不要重建 RDP 会话。
### 3.2 浏览器输入事件
```json
{ "type": "mouse_move", "x": 100, "y": 200 }
{ "type": "mouse_down", "x": 100, "y": 200, "button": 0 }
{ "type": "mouse_up", "x": 100, "y": 200, "button": 0 }
{ "type": "wheel", "x": 100, "y": 200, "delta_y": -120 }
{ "type": "key", "code": "KeyC", "key": "c", "down": true, "ctrl": true }
{ "type": "clipboard", "text": "from local" }
{ "type": "remote_copy" }
{ "type": "rdp_file_upload", "name": "php.ini", "text": "base64..." }
```
处理规则:
- `mouse_move` 高频事件可丢弃或合并。
- `mouse_down` / `mouse_up` / `key down` / `key up` 不允许丢弃。
- `key` 事件必须带 `ctrl`、`alt`、`shift`、`meta`,后端根据状态补齐修饰键。
- `remote_copy` 表示请求后端向远端剪贴板索取文本。
- `rdp_file_upload.text` 是 base64 文件内容,第一版可限制 50 MB。
### 3.3 服务端帧
JSON 帧:
```json
{ "type": "session", "message": "43", "width": 900, "height": 596 }
{ "type": "status", "message": "正在连接 RDP 1.2.3.4:3389" }
{ "type": "error", "message": "连接失败" }
{ "type": "clipboard", "text": "from remote" }
{ "type": "rdp_file_download", "name": "a.txt", "url": "/api/v1/rdp-staged/token" }
```
二进制图形帧:
```text
offset size type desc
0 4 bytes magic, fixed "RDPR"
4 4 uint32 LE x
8 4 uint32 LE y
12 4 uint32 LE width
16 4 uint32 LE height
20 n bytes RGBA, length = width * height * 4
```
实现要求:
- 实时画面优先使用 `RDPR + RGBA`,避免 PNG/base64 带来的 CPU 和体积开销。
- 历史回放可以保存 PNG data URL,便于持久化和跨版本播放。
- 服务端发送图形帧前必须裁剪到桌面范围,避免前端越界写 Canvas。
---
## 4. RDP 协议状态机
### 4.1 连接阶段
```text
TCP connect
→ X.224 Connection Request
→ X.224 Connection Confirm
→ TLS handshake
→ CredSSP/NLA
→ MCS Connect Initial
→ MCS Connect Response
→ ErectDomainRequest
→ AttachUserRequest/Confirm
→ Channel Join: user + global + cliprdr + rdpdr
→ Client Info
→ DemandActive
→ ConfirmActive
→ Synchronize / Control Cooperate / Control Request Control
→ FontList / FontMap
→ SuppressOutput(ALLOW_DISPLAY_UPDATES)
→ Bitmap/FastPath updates
```
关键点:
- 每一次协议探测失败后,下一次重试要新建 TCP 连接。
- CredSSP 的证书公钥绑定必须按服务端证书 `SubjectPublicKey` 做,不要误用完整 SPKI。
- 收到 `DemandActive` 后才能发送 `ConfirmActive`,能力集必须包含图形、输入、虚拟通道相关能力。
- `SuppressOutput(ALLOW_DISPLAY_UPDATES)` 对部分 Windows 主机很关键,否则登录后可能长时间无画面。
### 4.2 图形更新处理
```text
FastPath/SlowPath packet
→ parse update type
→ bitmap/surface command
→ MPPC decompress if compressed
→ RLE decode if bitmap compressed
→ convert BGRA/RGB565 to RGBA
→ draw into backend framebuffer
→ flush dirty rectangle
→ send RDPR binary frame
→ throttle snapshot into recorder
```
实现要求:
- 后端维护完整 framebuffer,不能只保存最后一个小块,否则历史回放会碎片化。
- 实时可发送脏矩形,录像建议按固定间隔保存合成后的快照。
- 启动阶段纯黑全屏帧应过滤,减少登录闪屏和黑屏回放。
### 4.3 输入处理
| 浏览器事件 | RDP 行为 |
|------------|----------|
| `mouse_move` | Pointer move |
| `mouse_down` | Pointer button down |
| `mouse_up` | Pointer button up |
| `wheel` | Wheel event |
| `key down/up` | Scancode keyboard event |
| `clipboard` | 写入 cliprdr 本地剪贴板并通知远端 |
| `remote_copy` | 请求远端当前剪贴板文本 |
组合键规则:
- `Ctrl+C`、`Ctrl+X` 必须同时发修饰键和主键,不能把 `c`、`x` 当普通输入。
- macOS 浏览器中的 `Command+C/V` 可映射成远端 `Ctrl+C/V`。
- 后端审计应识别 `[RDP复制] Ctrl+C`、`[RDP剪切] Ctrl+X`、`[RDP粘贴快捷键] Ctrl+V`。
---
## 5. 剪贴板实现
### 5.1 本地到远端
```text
Browser navigator.clipboard.readText()
→ WebSocket { type: "clipboard", text }
→ backend policy check clipboard_enabled
→ cliprdr set local data
→ send FormatList / FormatDataResponse
→ Windows remote clipboard update
```
验收:
1. 本地复制 `hello`。
2. 点击“粘贴到远端”或在 RDP Canvas 执行 Ctrl/Command+V。
3. 远端文本框出现 `hello`。
### 5.2 远端到本地
```text
User selects text in Windows
→ remote Ctrl+C
→ Windows updates remote clipboard
→ cliprdr FormatList arrives
→ backend requests CF_UNICODETEXT
→ backend WebSocket { type: "clipboard", text }
→ Browser navigator.clipboard.writeText(text)
```
验收:
1. 远端记事本输入 `rdp-copy-ok` 并选中。
2. 在远端按 Ctrl+C。
3. 点击“复制远端内容”或等待回传。
4. 本地 Command+V 能粘贴 `rdp-copy-ok`。
常见失败:
| 现象 | 原因 | 处理 |
|------|------|------|
| 远端 Ctrl+C 只输入 c | 修饰键状态未发送或被前端拦截 | key event 带 `ctrl=true`,后端补发 Ctrl down/up |
| 浏览器无法写入剪贴板 | 浏览器权限限制 | 必须在用户点击按钮的交互上下文执行 |
| 工具提示远端剪贴板为空 | 未收到 FormatList 或请求过早 | Ctrl+C 后延迟 200-500ms 再请求 |
---
## 6. RDPDR 虚拟盘文件传输
### 6.1 远端展示
```text
此电脑
└── Go-client 上的 AIOps
├── Upload
└── Download
```
固定命名:
| 项 | 值 |
|----|----|
| Client Name | `Go-client` |
| Drive Name | `AIOps` |
| UNC | `\\Go-client\AIOps` |
| Upload | `\\Go-client\AIOps\Upload` |
| Download | `\\Go-client\AIOps\Download` |
### 6.2 RDPDR 最小能力
必须处理:
1. Server Announce Request。
2. Client Announce Reply。
3. Client Name Request。
4. Server Client ID Confirm。
5. Client Device List Announce。
6. Device Create/Open。
7. Directory Query。
8. File Read。
9. File Write。
10. Close。
虚拟文件系统模型:
```text
/
├── Upload/ # 后端提供给远端读
│ └── user uploaded files
└── Download/ # 远端写入,后端捕获
└── remote copied files
```
### 6.3 上传链路
```text
Browser file input/drop
→ FileReader base64
→ WebSocket { type: "rdp_file_upload", name, text }
→ policy check file_upload_enabled
→ rdpdr.AddUpload(name, bytes)
→ Explorer opens \\Go-client\AIOps\Upload
→ Windows reads file from virtual disk
```
验收:
- 上传 `php.ini` 后,远端 `Upload` 目录出现文件。
- 远端可以打开文件。
- 远端可以复制到桌面或其他目录。
### 6.4 下载链路
```text
Windows copies file to \\Go-client\AIOps\Download
→ rdpdr Device Write
→ backend captures bytes
→ filestage.Put(name, data, ttl=30min)
→ WebSocket { type: "rdp_file_download", name, url }
→ Browser opens url
→ /api/v1/rdp-staged/:token returns attachment
```
验收:
- 远端将 `a.txt` 拖入 `Download`。
- 浏览器自动触发下载。
- 本地下载文件内容与远端一致。
实现注意:
- 不要用 SMB 管理共享作为主路径。
- 文件名必须清理路径分隔符,避免 `../`。
- `filestage` token 必须有 TTL,下载后可删除或等待过期。
- 大文件后续应改为分片和磁盘暂存,避免长期占用内存。
---
## 7. 审计、录像与安全策略
### 7.1 数据模型
RDP 会话表:
```text
sys_rdp_sessions
id
user_id / username
server_id / server_name / server_ip / server_port
client_ip
width / height
status: active | closed | failed
duration
error
record_data
record_size
start_time / end_time
```
操作审计表:
```text
sys_session_commands
session_id
session_type = rdp
user_id / username
server_id / server_name
command
output_size
timestamp
```
策略表:
```text
sys_audit_settings
session_watermark_enabled
session_record_enabled
clipboard_enabled
file_upload_enabled
file_download_enabled
command_policy_enabled
approval_required
```
### 7.2 录像策略
- 实时帧可以是 RGBA 脏矩形。
- 录像帧必须是合成快照,避免回放只看到小块。
- 推荐最小间隔 500ms。
- 过滤启动阶段纯黑全屏。
- 关闭 `session_record_enabled` 后仍记录会话元数据,但不保存 `record_data`。
### 7.3 审计策略
建议记录:
```text
[RDP输入] hello
[RDP复制] Ctrl+C
[RDP剪切] Ctrl+X
[RDP粘贴] http://localhost:8080/servers
[RDP上传到虚拟盘] php.ini
[RDP下载] a.txt
```
不建议记录:
- 每个 `mousemove`。
- 每个 RGBA 实时帧。
- 空白按键和无业务意义坐标。
### 7.4 策略拦截
前端禁用按钮只是体验优化,后端必须再次拦截:
| Event | 策略 |
|-------|------|
| `clipboard` | `clipboard_enabled` |
| `remote_copy` | `clipboard_enabled` |
| `rdp_file_upload` | `file_upload_enabled` |
| `rdp_file_download` | `file_download_enabled` |
| 录像写入 | `session_record_enabled` |
---
## 8. 前端实现要求
### 8.1 Canvas 绘制
```text
WebSocket binary
→ parse RDPR header
→ create ImageData(width, height)
→ imageData.data.set(rgba)
→ canvas.putImageData(imageData, x, y)
```
要求:
- Canvas 内部分辨率等于远端桌面分辨率。
- CSS 尺寸可以缩放,但鼠标坐标必须换算回远端坐标。
- 最大化和窗口缩放不应重新连接 RDP。
### 8.2 输入捕获
- RDP Canvas 必须可 focus,点击后接收键盘事件。
- 捕获 `keydown` 和 `keyup`,都发给后端。
- 浏览器默认快捷键如 Ctrl+C/Ctrl+V 需要按场景阻止默认行为。
- macOS Command 复制粘贴要映射到远端 Ctrl。
### 8.3 工具面板
工具面板只保留高价值动作:
| 动作 | 说明 |
|------|------|
| 粘贴到远端 | 从浏览器剪贴板读取并写入远端 |
| 复制远端内容 | 请求远端剪贴板并写入浏览器剪贴板 |
| 上传文件 | 发送到虚拟盘 Upload |
| 虚拟盘说明 | 展示 `Go-client 上的 AIOps`、Upload、Download 的用途 |
不要再展示 SMB、445、管理共享等文案。
---
## 9. 调试和验收
### 9.1 日志关键字
```bash
tail -f logs/aiops-local.log | rg "RDP|cliprdr|rdpdr|RDPR"
```
| 日志 | 含义 |
|------|------|
| `[RDP] ws connected` | 浏览器已连接 |
| `[RDP] engine start` | 开始连接远端 3389 |
| `[RDP] bitmap session=` | 收到图形帧 |
| `[cliprdr]` | 剪贴板通道 |
| `[rdpdr] device list announced` | 虚拟盘公告 |
| `[rdpdr] upload staged` | 上传文件已暂存 |
| `[rdpdr] download captured` | 下载文件已捕获 |
### 9.2 自动测试建议
| 层级 | 用例 |
|------|------|
| `credssp` | NTLMv2、公钥绑定、证书变体 |
| `rle` | 8/15/16/24/32 bpp 位图解码 |
| `display` | framebuffer 合成、脏矩形裁剪 |
| `rdpdr` | 目录枚举、文件读写、非法路径 |
| `filestage` | token TTL、并发下载、文件名清理 |
| 前端 | RDPR 解析、坐标缩放、快捷键映射 |
### 9.3 手动验收矩阵
| 场景 | 预期 |
|------|------|
| 13 寸屏打开 RDP | 能看到边框、工具栏、Canvas |
| 最大化窗口 | 不重连、不闪屏 |
| 登录初期黑屏 | 回放中不出现大量黑屏段 |
| 本地复制远端粘贴 | 成功 |
| 远端复制本地粘贴 | 成功 |
| 上传小文件 | `Upload` 可见、可打开、可复制 |
| 远端下载文件 | 浏览器自动下载 |
| 关闭剪贴板策略 | 前端禁用,后端拒绝 |
| 关闭上传策略 | 前端禁用,后端拒绝 |
| 实时审计 | 审计端可看到当前画面 |
| 历史回放 | 画面完整,不是大量小块 |
---
## 10. 常见问题
| 问题 | 优先排查 |
|------|----------|
| 只能看到 C 盘,没有 AIOps 虚拟盘 | `rdpdr` 是否完成 Device List Announce,Windows 是否刷新资源管理器 |
| `\\tsclient\AIOps` 无权限 | 路径名/ClientName 不一致,或 rdpdr create/read/write 返回码错误 |
| 远端 Ctrl+C 不能复制出来 | cliprdr FormatList/FormatDataRequest 是否到达,是否过早请求 |
| 文件可打开但不能复制到桌面 | rdpdr Read/Close/QueryInfo 返回不完整,或文件属性/长度错误 |
| 回放是小块或黑屏 | 没有 framebuffer 合成,或录像保存了实时脏矩形 |
| 最大化闪屏 | 前端重建了 WebSocket,应只缩放 Canvas |
| 上传大文件卡顿 | base64 + WebSocket 一次性发送,后续改分片 |