- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
554 lines
16 KiB
Markdown
554 lines
16 KiB
Markdown
# 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 一次性发送,后续改分片 |
|