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

16 KiB
Raw Blame History

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 静态虚拟通道

核心分层原则:

  1. 控制器负责鉴权、策略、审计、WebSocket、数据库。
  2. RDP 引擎只负责协议状态机和帧收发,不依赖 Gin/GORM/React。
  3. 前端只知道 WebSocket 帧协议,不知道 CredSSP、MCS、PDU 细节。
  4. 文件传输不走远端 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

验收:

  1. 本地复制 hello。
  2. 点击“粘贴到远端”或在 RDP Canvas 执行 Ctrl/Command+V。
  3. 远端文本框出现 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)

验收:

  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 远端展示

此电脑
└── 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。

虚拟文件系统模型:

/
├── 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 管理共享作为主路径。
  • 文件名必须清理路径分隔符,避免 ../。
  • filestage token 必须有 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 一次性发送,后续改分片