- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
16 KiB
16 KiB
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. 端到端架构
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 静态虚拟通道
核心分层原则:
- 控制器负责鉴权、策略、审计、WebSocket、数据库。
- RDP 引擎只负责协议状态机和帧收发,不依赖 Gin/GORM/React。
- 前端只知道 WebSocket 帧协议,不知道 CredSSP、MCS、PDU 细节。
- 文件传输不走远端 SMB,不依赖远端开放 445。
3. 后端接口契约
3.1 连接配置
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 浏览器输入事件
{ "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 帧:
{ "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" }
二进制图形帧:
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 连接阶段
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 图形更新处理
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 本地到远端
Browser navigator.clipboard.readText()
→ WebSocket { type: "clipboard", text }
→ backend policy check clipboard_enabled
→ cliprdr set local data
→ send FormatList / FormatDataResponse
→ Windows remote clipboard update
验收:
- 本地复制
hello。 - 点击“粘贴到远端”或在 RDP Canvas 执行 Ctrl/Command+V。
- 远端文本框出现
hello。
5.2 远端到本地
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)
验收:
- 远端记事本输入
rdp-copy-ok并选中。 - 在远端按 Ctrl+C。
- 点击“复制远端内容”或等待回传。
- 本地 Command+V 能粘贴
rdp-copy-ok。
常见失败:
| 现象 | 原因 | 处理 |
|---|---|---|
| 远端 Ctrl+C 只输入 c | 修饰键状态未发送或被前端拦截 | key event 带 ctrl=true,后端补发 Ctrl down/up |
| 浏览器无法写入剪贴板 | 浏览器权限限制 | 必须在用户点击按钮的交互上下文执行 |
| 工具提示远端剪贴板为空 | 未收到 FormatList 或请求过早 | Ctrl+C 后延迟 200-500ms 再请求 |
6. RDPDR 虚拟盘文件传输
6.1 远端展示
此电脑
└── 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 最小能力
必须处理:
- Server Announce Request。
- Client Announce Reply。
- Client Name Request。
- Server Client ID Confirm。
- Client Device List Announce。
- Device Create/Open。
- Directory Query。
- File Read。
- File Write。
- Close。
虚拟文件系统模型:
/
├── Upload/ # 后端提供给远端读
│ └── user uploaded files
└── Download/ # 远端写入,后端捕获
└── remote copied files
6.3 上传链路
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 下载链路
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 管理共享作为主路径。
- 文件名必须清理路径分隔符,避免
../。 filestagetoken 必须有 TTL,下载后可删除或等待过期。- 大文件后续应改为分片和磁盘暂存,避免长期占用内存。
7. 审计、录像与安全策略
7.1 数据模型
RDP 会话表:
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
操作审计表:
sys_session_commands
session_id
session_type = rdp
user_id / username
server_id / server_name
command
output_size
timestamp
策略表:
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 审计策略
建议记录:
[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 绘制
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 日志关键字
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 一次性发送,后续改分片 |