Files
aiops/docs/rdp/implementation-spec.md
T
Logan.Li e0d652d211 feat: 完善堡垒机与 Agent 智能运维能力
- 支持 Windows Agent 构建、安装脚本和统一上报协议

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

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

- 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
2026-07-05 23:49:36 +08:00

554 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
# 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 一次性发送,后续改分片 |