# 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](./README.md) | 产品、架构、联调人员 | 功能范围、调用链、文件清单、现有限制 | | [implementation-spec.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 端口和管理共享 | --- ## 用户体验 ### 入口 1. 进入 `服务器管理`。 2. Windows / RDP 资产点击终端按钮。 3. 弹出 RDP 图形窗口。 4. 顶部工具图标打开 RDP 工具面板。 ### RDP 窗口 | 区域 | 说明 | |------|------| | 顶栏 | 显示资产名称、IP、连接状态、工具、最大化、关闭 | | Canvas | 渲染远端桌面图形帧 | | 水印层 | 显示当前登录用户,例如 `admin` | | 工具面板 | 剪贴板同步、虚拟盘上传说明、上传进度 | 13 寸屏幕默认使用较小窗口尺寸,避免看不到窗口边框和工具栏。最大化时前端只缩放现有 Canvas,不重建 RDP 会话,避免登录阶段闪屏。// Logan.Li ### 虚拟盘体验 远端 Windows 中应看到: ```text 此电脑 └── Go-client 上的 AIOps ├── Upload └── Download ``` | 目录 | 用途 | |------|------| | `Upload` | 浏览器上传的文件会出现在这里,远端可复制到桌面或目标目录 | | `Download` | 远端文件复制/拖入这里后,AIOps 捕获并触发本地浏览器下载 | --- ## 架构与调用链 ```text 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 静态虚拟通道 ``` 文件下载链路: ```text 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 自动下载 ``` 文件上传链路: ```text 浏览器选择/拖拽文件 ▼ 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: ```json { "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/" } ``` Binary frame: ```text magic: "RDPR" x: uint32 little-endian y: uint32 little-endian width: uint32 little-endian height: uint32 little-endian rgba: width * height * 4 ``` 历史回放仍使用 JSON `bitmap` 快照: ```json { "type": "bitmap", "x": 0, "y": 0, "width": 900, "height": 596, "image": "data:image/png;base64,..." } ``` ### 浏览器到服务端 ```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": "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 上传流程: 1. 用户在 RDP 工具面板选择或拖拽本地文件。 2. 前端读取文件并通过 WebSocket 发送到后端。 3. 后端加入 rdpdr upload map。 4. 远端资源管理器进入 `\\Go-client\AIOps\Upload` 后可看到文件。 5. 用户可在远端复制到桌面或目标目录。 当前前端单文件上传限制为 50 MB。上传进度表示浏览器读取和发送到 WebSocket 的进度,不是远端 Windows 复制进度。 ### Download 下载流程: 1. 用户在远端 Windows 中把文件复制/拖入 `\\Go-client\AIOps\Download`。 2. rdpdr 捕获远端写入数据。 3. 后端将文件暂存到 `filestage`,默认 30 分钟有效。 4. 后端向浏览器发送 `rdp_file_download` frame。 5. 浏览器自动访问 `/api/v1/rdp-staged/:token` 下载到本地下载目录。 ### 不再使用 SMB 已移除 SMB 直传和 445 端口依赖。原因: - 外网访问 Windows 445 通常不可达或不安全。 - 管理共享需要额外权限,部署和排障成本高。 - 堡垒机场景更适合走 RDP 自身的设备重定向能力。 --- ## 会话安全策略 入口: ```text 系统管理 → 系统设置 → 安全 → 会话设置 ``` 策略字段存储在 `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` | 已不再参与引擎选择,保留也不会改变行为 | 常用日志过滤: ```bash 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 | --- ## 验证清单 ### 构建 ```bash cd frontend && npx tsc --noEmit cd backend && env GOSUMDB=sum.golang.org GOTOOLCHAIN=auto go test ./pkg/rdp/... ./controller ./run.sh build ``` ### 手动验证 1. 登录 `http://localhost:8080`。 2. 进入 `服务器管理`,打开 Windows 资产 RDP。 3. 确认 RDP 桌面画面稳定,无明显登录黑屏闪烁。 4. 确认远端 `此电脑` 能看到 `Go-client 上的 AIOps`。 5. 工具面板上传文件,远端进入 `\\Go-client\AIOps\Upload` 可打开/复制。 6. 远端文件复制到 `\\Go-client\AIOps\Download`,浏览器触发下载。 7. 本地复制文本后在远端 Ctrl+V 或工具面板粘贴。 8. 远端选中文本 Ctrl+C 后,工具面板复制远端内容并在本地粘贴。 9. 进入 `系统管理 → 系统设置 → 安全 → 会话设置`,关闭剪贴板/上传/下载后验证前端按钮禁用和后端策略拦截。 10. 进入审计页面查看 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