- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
AIOps Web RDP 远程桌面
作者:Logan.Li 更新:2026-07-05 归档副本:
/opt/rdp/docs/rdp.md· 变更记录:/opt/rdp/CHANGELOG.md
概述
AIOps RDP 是内置在浏览器里的 Windows 远程桌面能力,采用“后端 Go 自研 RDP 协议栈 + WebSocket + 前端 Canvas”的方式实现。当前功能已从远程桌面连接推进到可用堡垒机阶段,支持图形会话、键鼠输入、双向剪贴板、RDPDR 虚拟磁盘、文件上传下载、会话录像回放、实时审计和全局会话安全策略。
当前 RDP 不再依赖 SMB、445 端口或第三方 RDP 实现。文件传输走 RDP 自身的 rdpdr 设备重定向通道,远端资源管理器中显示为 Go-client 上的 AIOps,UNC 路径为 \\Go-client\AIOps。// Logan.Li
文档阅读路径
本目录按“能让其他项目复刻实现”的标准组织:
| 文档 | 适合读者 | 内容 |
|---|---|---|
| README.md | 产品、架构、联调人员 | 功能范围、调用链、文件清单、现有限制 |
| implementation-spec.md | 后端、前端、测试工程师 | 从零实现 RDP Web 网关所需的协议分层、状态机、WebSocket 帧、剪贴板、虚拟盘、审计和验收用例 |
如果要在另一个项目中实现同类能力,建议先按 implementation-spec.md 搭出最小闭环,再回到本文档对照 AIOps 的实际文件落点。// Logan.Li
| 能力 | 状态 | 说明 |
|---|---|---|
| RDP 图形连接 | 已完成 | 自研 X.224 / TLS / CredSSP / MCS / FastPath / Bitmap |
| 键鼠输入 | 已完成 | Fast-Path Input,支持组合键审计与服务端修饰键补齐 |
| 双向剪贴板 | 已完成 | cliprdr 通道 + 工具按钮 + Ctrl/Command 快捷键 |
| 文件上传 | 已完成 | 浏览器选择/拖拽文件,暂存到远端虚拟盘 Upload |
| 文件下载 | 已完成 | 远端文件复制到虚拟盘 Download,浏览器自动下载 |
| 会话录像 | 已完成 | RDP bitmap 快照录制,过滤启动黑帧,支持历史回放 |
| 实时审计 | 已完成 | 支持 RDP 实时画面订阅 /ws/rdp-audit/:sessionId |
| 会话水印 | 已完成 | RDP/SSH 会话显示当前登录用户水印 |
| 会话策略 | 已完成 | 水印、录像、剪贴板、上传、下载、命令策略、审批开关 |
| SMB 文件传输 | 已移除 | 不再使用 445 端口和管理共享 |
用户体验
入口
- 进入
服务器管理。 - Windows / RDP 资产点击终端按钮。
- 弹出 RDP 图形窗口。
- 顶部工具图标打开 RDP 工具面板。
RDP 窗口
| 区域 | 说明 |
|---|---|
| 顶栏 | 显示资产名称、IP、连接状态、工具、最大化、关闭 |
| Canvas | 渲染远端桌面图形帧 |
| 水印层 | 显示当前登录用户,例如 admin |
| 工具面板 | 剪贴板同步、虚拟盘上传说明、上传进度 |
13 寸屏幕默认使用较小窗口尺寸,避免看不到窗口边框和工具栏。最大化时前端只缩放现有 Canvas,不重建 RDP 会话,避免登录阶段闪屏。// Logan.Li
虚拟盘体验
远端 Windows 中应看到:
此电脑
└── Go-client 上的 AIOps
├── Upload
└── Download
| 目录 | 用途 |
|---|---|
Upload |
浏览器上传的文件会出现在这里,远端可复制到桌面或目标目录 |
Download |
远端文件复制/拖入这里后,AIOps 捕获并触发本地浏览器下载 |
架构与调用链
frontend/src/components/server/RdpDialog.tsx
│ WebSocket /ws/rdp?id=&width=&height=&token=
│ Canvas 渲染、键鼠捕获、快捷键、下载触发
▼
backend/controller/rdp.go
│ 创建 RDPSession、加载会话策略、审计、录屏、WebSocket frame sink
│ loadRDPCredential() → rdp.Engine.Run()
▼
backend/pkg/rdp/session_engine.go
│ OpenSession() → readLoop() → handleEvent()
├── 图形帧:FastPath / SlowPath Bitmap → RGBA → WebSocket binary
├── 输入:浏览器 key/mouse/wheel → Fast-Path Input
├── 剪贴板:cliprdr 静态虚拟通道
└── 文件:rdpdr 静态虚拟通道
文件下载链路:
Windows 文件 → \\Go-client\AIOps\Download
▼
rdpdr Device I/O 写入捕获
▼
pkg/rdp/filestage.Put(name, data, 30min)
▼
WebSocket frame: { type: "rdp_file_download", name, url }
▼
浏览器访问 /api/v1/rdp-staged/:token 自动下载
文件上传链路:
浏览器选择/拖拽文件
▼
RdpToolsPanel FileReader 读取为 base64
▼
WebSocket event: { type: "rdp_file_upload", name, text }
▼
rdpdr.Handler.AddUpload()
▼
Windows 资源管理器读取 \\Go-client\AIOps\Upload\文件名
后端文件清单
协议栈 backend/pkg/rdp/
| 文件 / 目录 | 说明 |
|---|---|
engine.go |
Engine 接口,当前固定使用自研 SessionEngine |
session_engine.go |
图形会话主循环、输入事件、虚拟通道分发、黑帧过滤 |
session_connect.go |
OpenSession、CredSSP、MCS、SuppressOutput 激活 |
mcs_probe.go |
MCS/GCC/ClientInfo/ConfirmActive 构建与探测 |
packet_io.go |
TPKT / FastPath 读写 |
fastpath_recv.go |
FastPath 更新、SurfaceCmd、MPPC 解压入口 |
bitmap_decode.go |
Bitmap/RLE 解码与 RGBA 转换 |
input_fastpath.go |
键盘、鼠标、滚轮 Fast-Path Input 编码 |
keymap.go |
浏览器 KeyboardEvent.code 到 RDP 扫描码映射 |
mppc.go |
MPPC-64K 解压器 |
transport/ |
TPKT、X.224、TLS |
credssp/ |
CredSSP + NTLMv2 + ASN.1 |
rle/ |
位图 RLE 解压 |
channels/cliprdr/ |
RDP 剪贴板静态虚拟通道 |
channels/rdpdr/ |
RDP 设备重定向虚拟磁盘 |
filestage/ |
Download 捕获文件的内存暂存与 token |
trace/ |
RDP_DEBUG=1 协议级调试日志 |
控制器 / 模型
| 文件 | 说明 |
|---|---|
backend/controller/rdp.go |
WebSocket 会话、策略拦截、审计、录屏、实时广播 |
backend/controller/rdp_session.go |
RDP 会话列表、详情、回放、终止、录像下载 |
backend/controller/rdp_staged.go |
RDPDR Download 捕获文件的浏览器下载入口 |
backend/controller/rdp_cred.go |
RDP 凭据加载与解密 |
backend/model/rdp_session.go |
RDPSession 表 |
backend/model/audit_setting.go |
会话安全策略字段 |
前端
| 文件 | 说明 |
|---|---|
frontend/src/components/server/RdpDialog.tsx |
RDP 弹窗、Canvas、WebSocket、键鼠、下载触发 |
frontend/src/components/server/RdpToolsPanel.tsx |
剪贴板、上传、虚拟盘说明、上传进度 |
frontend/src/components/common/SessionWatermark.tsx |
会话水印 |
frontend/src/components/system/RdpAuditViewer.tsx |
RDP 历史回放 / 实时审计查看 |
frontend/src/components/system/SystemSettingsTab.tsx |
会话安全策略开关入口 |
frontend/src/store/index.ts |
useSessionSecurityStore 前端策略缓存 |
WebSocket 协议
服务端到浏览器
JSON frame:
{ "type": "session", "message": "123", "width": 900, "height": 596 }
{ "type": "status", "message": "正在连接 RDP 1.2.3.4:3389" }
{ "type": "error", "message": "连接失败" }
{ "type": "clipboard", "text": "远端剪贴板内容" }
{ "type": "rdp_file_download", "name": "a.txt", "url": "/api/v1/rdp-staged/<token>" }
Binary frame:
magic: "RDPR"
x: uint32 little-endian
y: uint32 little-endian
width: uint32 little-endian
height: uint32 little-endian
rgba: width * height * 4
历史回放仍使用 JSON bitmap 快照:
{ "type": "bitmap", "x": 0, "y": 0, "width": 900, "height": 596, "image": "data:image/png;base64,..." }
浏览器到服务端
{ "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": "KeyA", "key": "a", "down": true, "ctrl": false }
{ "type": "clipboard", "text": "本地剪贴板内容" }
{ "type": "remote_copy" }
{ "type": "rdp_file_upload", "name": "php.ini", "text": "base64..." }
剪贴板机制
| 方向 | 入口 | 机制 |
|---|---|---|
| 本地到远端 | 工具面板“粘贴到远端”、Canvas 粘贴、Ctrl/Command+V | 浏览器读取本地剪贴板,发送 clipboard event,后端写入 RDP cliprdr |
| 远端到本地 | 工具面板“复制远端内容”、远端 Ctrl+C/Ctrl+X | 后端发送/识别复制事件,通过 cliprdr 请求远端文本并回传浏览器 |
注意:
- 浏览器读取/写入系统剪贴板受浏览器权限限制,需要页面处于用户交互上下文。
- 远端 Ctrl+C/Ctrl+X 会按普通键盘事件发送给 Windows,同时后端延迟请求远端剪贴板文本。
- 审计会记录
[RDP复制] Ctrl+C、[RDP剪切] Ctrl+X、[RDP粘贴] ...、[RDP粘贴快捷键] Ctrl+V。
RDPDR 虚拟磁盘
RDPDR 使用静态虚拟通道 rdpdr,实现最小可用的磁盘设备重定向:
| 项目 | 当前值 |
|---|---|
| Client Name | Go-client |
| Drive Name | AIOps |
| UNC | \\Go-client\AIOps |
| 远端展示 | Go-client 上的 AIOps |
| 固定目录 | Upload、Download |
Upload
上传流程:
- 用户在 RDP 工具面板选择或拖拽本地文件。
- 前端读取文件并通过 WebSocket 发送到后端。
- 后端加入 rdpdr upload map。
- 远端资源管理器进入
\\Go-client\AIOps\Upload后可看到文件。 - 用户可在远端复制到桌面或目标目录。
当前前端单文件上传限制为 50 MB。上传进度表示浏览器读取和发送到 WebSocket 的进度,不是远端 Windows 复制进度。
Download
下载流程:
- 用户在远端 Windows 中把文件复制/拖入
\\Go-client\AIOps\Download。 - rdpdr 捕获远端写入数据。
- 后端将文件暂存到
filestage,默认 30 分钟有效。 - 后端向浏览器发送
rdp_file_downloadframe。 - 浏览器自动访问
/api/v1/rdp-staged/:token下载到本地下载目录。
不再使用 SMB
已移除 SMB 直传和 445 端口依赖。原因:
- 外网访问 Windows 445 通常不可达或不安全。
- 管理共享需要额外权限,部署和排障成本高。
- 堡垒机场景更适合走 RDP 自身的设备重定向能力。
会话安全策略
入口:
系统管理 → 系统设置 → 安全 → 会话设置
策略字段存储在 sys_audit_settings:
| 字段 | 默认 | 影响 |
|---|---|---|
session_watermark_enabled |
true | RDP/SSH 会话显示当前用户水印 |
session_record_enabled |
true | 是否保存 RDP 录像数据 |
clipboard_enabled |
true | 是否允许剪贴板同步 |
file_upload_enabled |
true | 是否允许 RDP 文件上传 |
file_download_enabled |
true | 是否允许 RDP 文件下载 |
command_policy_enabled |
false | 命令策略预留开关 |
approval_required |
false | 访问审批预留开关 |
RDP 后端在 WebSocket 读事件时会执行策略拦截:
| 事件 | 策略 |
|---|---|
clipboard / remote_copy |
clipboard_enabled |
file_upload / rdp_file_upload |
file_upload_enabled |
file_download |
file_download_enabled |
前端也会同步禁用对应按钮,但后端拦截才是最终边界。// Logan.Li
审计与回放
会话录像
RDP 会话关闭时写入 rdp_sessions.record_data 和 record_size。录制策略:
- 非 bitmap 状态帧直接记录。
- bitmap 帧通过后端 framebuffer 合成整屏快照。
- 启动阶段黑屏帧会被过滤,减少历史回放黑屏片段。
- 快照最小间隔约 500ms,避免小块 bitmap 过密导致回放碎片化。
- 关闭
session_record_enabled后不写入录像数据。
操作审计
RDP 输入会进入 session_commands,用于统一审计:
| 类型 | 示例 |
|---|---|
| 文本输入 | [RDP输入] hello |
| 快捷键 | [RDP复制] Ctrl+C、[RDP剪切] Ctrl+X |
| 粘贴 | [RDP粘贴] http://localhost:8080/servers |
| 上传 | [RDP上传到虚拟盘] php.ini |
| 下载 | [RDP下载] a.txt |
鼠标移动、点击轨迹只进入图形录像,不写命令审计,避免审计列表噪声。
实时审计
活动 RDP 会话可通过 /ws/rdp-audit/:sessionId 实时订阅图形帧,供审计页面查看在线会话画面。
闪屏与画面优化
当前已做的优化:
| 问题 | 处理 |
|---|---|
| 登录初期黑屏闪烁 | 后端跳过启动阶段纯黑全屏 bitmap |
| 最大化导致重连 | 前端只缩放 Canvas,不重建 WebSocket |
| 回放很多小块 | 后端 framebuffer 合成整屏快照再录制 |
| 高频 mousemove 堆积 | 前端 16ms 合并移动事件,后端队列优先保留关键输入 |
| 13 寸屏看不到工具栏 | 默认窗口缩小,最大化再使用全屏空间 |
环境变量与调试
| 环境变量 | 行为 |
|---|---|
RDP_DEBUG=1 |
打开 [RDP:TRACE] 协议级日志 |
RDP_ENGINE |
已不再参与引擎选择,保留也不会改变行为 |
常用日志过滤:
tail -f logs/aiops-local.log | rg "RDP|rdpdr|cliprdr"
常见日志含义:
| 日志 | 含义 |
|---|---|
[RDP] ws connected |
浏览器 RDP WebSocket 已连接 |
[RDP] engine start |
后端开始连接远端 3389 |
[RDP] bitmap session=... |
收到并发送图形帧 |
[rdpdr] device list announced |
虚拟盘设备已公告 |
[rdpdr] upload staged |
文件已进入虚拟盘 Upload |
[rdpdr] download captured |
远端写入 Download 已被捕获 |
[cliprdr] |
剪贴板虚拟通道交互 |
路由
| 路径 | 说明 |
|---|---|
GET /ws/rdp |
RDP 图形会话 WebSocket |
GET /ws/rdp-audit/:sessionId |
RDP 活动会话实时审计 WebSocket |
GET /api/v1/rdp-staged/:token |
RDPDR Download 暂存文件下载 |
GET /api/v1/rdp-sessions |
RDP 会话列表 |
GET /api/v1/rdp-sessions/:id |
RDP 会话详情 |
GET /api/v1/rdp-sessions/:id/record |
RDP 录像数据 |
GET /api/v1/rdp-sessions/:id/download |
RDP 录像下载 |
POST /api/v1/rdp-sessions/:id/kill |
终止活动 RDP 会话 |
POST /api/v1/rdp-sessions/:id/replay-view |
记录回放查看行为 |
GET /api/v1/session-commands?session_type=rdp |
RDP 输入/文件/剪贴板操作审计 |
GET /api/v1/admin/audit-settings |
获取会话安全策略 |
PUT /api/v1/admin/audit-settings |
保存会话安全策略 |
当前限制
| 限制 | 说明 |
|---|---|
| 大文件上传 | 前端当前限制 50 MB,尚未实现分片、断点续传 |
| 大文件下载 | 当前以内存暂存 token 方式实现,尚未实现断点下载 |
| RDPGFX/H.264 | 未实现,当前以 bitmap/RLE/SurfaceCmd 为主 |
| 动态虚拟通道 | drdynvc 当前识别但不处理 |
| 复杂文件操作 | 重命名、属性编辑、复杂 Explorer 操作仍需继续压测 |
| TLS 指纹 | RDP 自签证书当前 InsecureSkipVerify,后续可做 TOFU 指纹落库 |
| 细粒度授权 | 当前是全局会话策略,尚未做到用户/角色/资产/协议维度的 ABAC |
验证清单
构建
cd frontend && npx tsc --noEmit
cd backend && env GOSUMDB=sum.golang.org GOTOOLCHAIN=auto go test ./pkg/rdp/... ./controller
./run.sh build
手动验证
- 登录
http://localhost:8080。 - 进入
服务器管理,打开 Windows 资产 RDP。 - 确认 RDP 桌面画面稳定,无明显登录黑屏闪烁。
- 确认远端
此电脑能看到Go-client 上的 AIOps。 - 工具面板上传文件,远端进入
\\Go-client\AIOps\Upload可打开/复制。 - 远端文件复制到
\\Go-client\AIOps\Download,浏览器触发下载。 - 本地复制文本后在远端 Ctrl+V 或工具面板粘贴。
- 远端选中文本 Ctrl+C 后,工具面板复制远端内容并在本地粘贴。
- 进入
系统管理 → 系统设置 → 安全 → 会话设置,关闭剪贴板/上传/下载后验证前端按钮禁用和后端策略拦截。 - 进入审计页面查看 RDP 会话、操作审计和历史回放。
外部项目借鉴边界
| 项目 | 可借鉴点 | 不直接采用原因 |
|---|---|---|
| FreeRDP | 协议完整性、通道、编解码、客户端能力矩阵 | C 生态和体量过大,不适合直接嵌入 |
| Apache Guacamole | Web 网关、协议代理、录制与前端桥接分层 | 依赖 guacd/C,不符合单 Go 后端目标 |
| IronRDP | 现代模块化、状态机测试、安全边界 | Rust 生态,适合作为测试和分层参考 |
| Teleport | 身份、RBAC、审计、Desktop Gateway 架构 | 产品目标偏完整堡垒机,当前只借鉴分层思路 |
| tomatome/grdp / nakagami/grdp | Go 协议状态机、能力集、bitmap/input 参考 | 第三方实现已移除,仅保留 /opt 备份用于协议对照 |
第三方源码只能作为协议行为和工程分层参考;涉及 GPL 的项目不得直接搬运实现。// Logan.Li