Files
aiops/docs/rdp/README.md
T
Logan.Li e0d652d211 feat: 完善堡垒机与 Agent 智能运维能力
- 支持 Windows Agent 构建、安装脚本和统一上报协议

- 增强 RDP 剪贴板、虚拟盘和文件传输能力

- 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口

- 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
2026-07-05 23:49:36 +08:00

455 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<token>" }
```
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