feat: 完善堡垒机与 Agent 智能运维能力
- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
This commit is contained in:
1 parent
a9513c2056
commit
e0d652d211
169 files changed
+12192
-9116
No files matched your search
@@ -0,0 +1,537 @@
|
||||
# AIOps 终端体系设计说明
|
||||
|
||||
> 更新:2026-07-03
|
||||
> 范围:SSH WebShell、Kubernetes Pod exec、kubectl 集群终端、流水线 Workspace 终端、审计实时查看与历史回放。RDP 仅在本文中说明边界,完整实现见 `docs/rdp/`。
|
||||
> 本文用于记录当前实现、设计取舍与后续重构方向。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 0. 文档阅读路径
|
||||
|
||||
本目录描述“字符终端统一底座”,RDP 图形桌面只在总体架构中对齐边界,具体实现见 [docs/rdp](../rdp/README.md)。
|
||||
|
||||
| 文档 | 适合读者 | 内容 |
|
||||
|------|----------|------|
|
||||
| [README.md](./README.md) | 产品、架构、联调人员 | 终端体系总体目标、各类终端现状、审计和重构方向 |
|
||||
| [implementation-spec.md](./implementation-spec.md) | 前端、后端、测试工程师 | WebSSH、Pod exec、kubectl、Workspace 终端的可复刻实现规格 |
|
||||
|
||||
另一个项目要复刻 AIOps 终端能力时,建议先实现 `implementation-spec.md` 中的统一 xterm/WebSocket/PTY 闭环,再按业务类型接入 SSH、K8s、Workspace 和审计。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体目标
|
||||
|
||||
AIOps 的终端体系不是单一功能,而是一组共享基础能力:
|
||||
|
||||
1. **实时交互**:浏览器通过 WebSocket 与后端建立交互式会话。
|
||||
2. **尺寸自适应**:前端终端容器变化后执行 `fit()`,并同步 `cols/rows` 到远端 PTY。
|
||||
3. **审计留痕**:会话起止、用户、资产、输入动作、命令、输出大小、录制数据可追溯。
|
||||
4. **历史回放**:SSH 终端使用文本流回放,RDP 使用 Canvas 图形帧回放。
|
||||
5. **统一体验**:SSH、Pod、kubectl、Workspace 终端共享 xterm.js 适配策略;RDP 使用 Canvas 独立渲染策略。
|
||||
|
||||
核心原则:
|
||||
|
||||
- 终端输入只走实时链路,审计只记录有价值的离散事件。
|
||||
- `mousemove` 等高频低价值事件不持久化。
|
||||
- RDP 历史回放采用节流图形帧,不保存每个实时帧,避免存储爆炸。
|
||||
- 前端终端窗口不手写分散 resize 逻辑,统一走 `frontend/src/lib/xterm-utils.ts`。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 2. 统一链路
|
||||
|
||||
### 2.1 SSH / Pod / kubectl / Workspace
|
||||
|
||||
```
|
||||
Browser xterm.js
|
||||
├─ onData → WebSocket JSON {status:0,data}
|
||||
├─ ResizeObserver + FitAddon
|
||||
└─ resize → WebSocket JSON {status:1,cols,rows}
|
||||
│
|
||||
▼
|
||||
Backend WebSocket Handler
|
||||
├─ SSH: ssh.Session + PTY
|
||||
├─ Pod exec: Kubernetes channel.k8s.io WebSocket
|
||||
├─ kubectl: local kubectl process + PTY
|
||||
└─ Workspace: pipeline workspace shell + PTY
|
||||
│
|
||||
▼
|
||||
Audit / Recording
|
||||
├─ terminal session metadata
|
||||
├─ asciicast text stream
|
||||
└─ command records
|
||||
```
|
||||
|
||||
### 2.2 Windows RDP
|
||||
|
||||
```
|
||||
Browser Canvas
|
||||
├─ mouse/key/clipboard input → WebSocket JSON
|
||||
└─ bitmap frames ← WebSocket binary RDPR
|
||||
│
|
||||
▼
|
||||
controller/rdp.go
|
||||
├─ load RDP credential
|
||||
├─ create RDPSession
|
||||
├─ start Engine.Run()
|
||||
├─ broadcast live audit frames
|
||||
└─ throttle snapshot frames for replay
|
||||
│
|
||||
▼
|
||||
pkg/rdp Engine
|
||||
└─ AIOps RDP protocol engine
|
||||
```
|
||||
|
||||
RDP 与 xterm 终端不同:它没有 PTY 的 `cols/rows` 概念,主要优化点是输入事件合并、图形帧编码、Canvas 绘制和录屏抽样。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 3. 前端终端窗口适配
|
||||
|
||||
公共工具:`frontend/src/lib/xterm-utils.ts`
|
||||
|
||||
| 方法 | 作用 |
|
||||
|------|------|
|
||||
| `fitTerminal()` | 调用 FitAddon,并在 WebSocket 打开时发送 `{status:1,cols,rows}` |
|
||||
| `mountXterm()` | 等容器渲染完成后挂载 xterm,延迟多次 fit,减少右侧黑边 |
|
||||
| `attachTerminalResize()` | 通过 ResizeObserver 监听容器尺寸,节流 fit 和 resize 同步 |
|
||||
|
||||
已接入或应接入的组件:
|
||||
|
||||
| 组件 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `TerminalDialog.tsx` | SSH 单会话终端 | 资产列表打开 SSH |
|
||||
| `WebShell.tsx` | SSH 多标签终端 | WebShell 页面 |
|
||||
| `PodExecTerminal.tsx` | Pod exec | K8s Pod 容器终端 |
|
||||
| `KubectlTerminal.tsx` | kubectl | 集群级 kubectl shell |
|
||||
| `WorkspaceTermDialog.tsx` | Workspace shell | 流水线构建目录终端 |
|
||||
| `LiveMonitorDialog.tsx` | SSH 实时审计 | 只读 xterm |
|
||||
| `ReplayDialog.tsx` | SSH 历史回放 | asciicast 回放 |
|
||||
|
||||
终端容器统一使用:
|
||||
|
||||
```tsx
|
||||
<div ref={containerRef} className="xterm-terminal-host" />
|
||||
```
|
||||
|
||||
全局样式位置:`frontend/src/index.css`
|
||||
|
||||
```css
|
||||
.xterm-terminal-host {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: #0a0e14;
|
||||
}
|
||||
.xterm-terminal-host .xterm {
|
||||
height: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- Dialog 动画会影响 xterm 首次测量,因此终端弹框禁用过渡。
|
||||
- `fit()` 后必须同步后端 PTY 尺寸,否则只是前端铺满,远端程序仍按旧尺寸输出。
|
||||
- `term.open()` 只能对同一个 xterm 实例执行一次,复用场景需要检查 `term.element`。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 4. SSH 终端实现
|
||||
|
||||
主要文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `backend/controller/webshell.go` | SSH WebSocket、PTY、录制、命令审计 |
|
||||
| `backend/model/terminal_session.go` | SSH 会话模型 |
|
||||
| `backend/model/session_command.go` | 命令审计模型 |
|
||||
| `frontend/src/components/server/TerminalDialog.tsx` | 单资产 SSH 终端 |
|
||||
| `frontend/src/components/server/WebShell.tsx` | 多标签 SSH 终端 |
|
||||
|
||||
协议:
|
||||
|
||||
```json
|
||||
{ "status": 0, "data": "ls -la\n" }
|
||||
{ "status": 1, "cols": 120, "rows": 34 }
|
||||
```
|
||||
|
||||
审计策略:
|
||||
|
||||
- 会话记录:开始时间、结束时间、用户、资产、状态、录制大小。
|
||||
- 录制数据:asciicast 文本流。
|
||||
- 命令记录:按回车提交后提取命令,不记录每个按键。
|
||||
- 输出统计:下一条命令前累计 stdout 字节数写入上一条命令。
|
||||
|
||||
优化方向:
|
||||
|
||||
- 命令列表按会话聚合展示,减少碎片行。
|
||||
- 对连续输入、粘贴、多行脚本增加摘要视图。
|
||||
- 高危命令识别只用于展示和告警,不阻塞正常录制。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 5. Kubernetes Pod Exec
|
||||
|
||||
主要文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `backend/controller/k8s_pod.go` | Pod exec WebSocket 代理 |
|
||||
| `frontend/src/components/kubernetes/PodExecTerminal.tsx` | Pod 终端 UI |
|
||||
|
||||
链路:
|
||||
|
||||
```
|
||||
xterm.js
|
||||
└─ WebSocket JSON
|
||||
└─ backend
|
||||
└─ Kubernetes API WebSocket channel.k8s.io
|
||||
```
|
||||
|
||||
K8s channel 映射:
|
||||
|
||||
| Channel | 方向 | 说明 |
|
||||
|---------|------|------|
|
||||
| 0 | client → pod | stdin |
|
||||
| 1 | pod → client | stdout |
|
||||
| 2 | pod → client | stderr |
|
||||
| 3 | pod → client | error/status |
|
||||
| 4 | client → pod | resize |
|
||||
|
||||
前端 resize JSON:
|
||||
|
||||
```json
|
||||
{ "status": 1, "cols": 120, "rows": 34 }
|
||||
```
|
||||
|
||||
后端转换为 K8s resize frame:
|
||||
|
||||
```json
|
||||
{ "Width": 120, "Height": 34 }
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- Pod exec 不应进入 SSH 命令审计表,除非未来设计 K8s 独立审计模型。
|
||||
- 容器切换、shell 切换会重连 WebSocket。
|
||||
- ESC 必须传给终端,Dialog 不能用 ESC 自动关闭。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 6. kubectl 集群终端
|
||||
|
||||
主要文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `backend/controller/kubectl_shell.go` | kubectl shell WebSocket |
|
||||
| `frontend/src/components/kubernetes/KubectlTerminal.tsx` | kubectl 终端 UI |
|
||||
|
||||
设计目标:
|
||||
|
||||
- 给运维提供集群级 kubectl shell。
|
||||
- 使用与 SSH 相同的 xterm 自适应策略。
|
||||
- 后端负责 kubeconfig/集群上下文隔离。
|
||||
|
||||
风险点:
|
||||
|
||||
- kubectl shell 权限大,必须依赖 RBAC、审计和操作权限控制。
|
||||
- 后续应对危险命令做二次确认或审计标记。
|
||||
- 多集群切换时不能复用旧进程上下文。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 7. Workspace 终端
|
||||
|
||||
主要文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `backend/controller/workspace_files.go` | 流水线工作区文件/终端入口 |
|
||||
| `frontend/src/components/deploy/WorkspaceTermDialog.tsx` | 工作区终端 UI |
|
||||
|
||||
用途:
|
||||
|
||||
- 进入流水线构建目录查看文件。
|
||||
- 辅助排查构建产物、日志、临时文件。
|
||||
|
||||
设计约束:
|
||||
|
||||
- 默认目录必须限制在 pipeline workspace 内。
|
||||
- 后端不能允许用户通过路径逃逸进入任意系统目录。
|
||||
- 终端尺寸适配与 SSH 共用 `xterm-utils`。// Logan.Li
|
||||
|
||||
---
|
||||
|
||||
## 8. RDP 实现
|
||||
|
||||
主要文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `backend/controller/rdp.go` | RDP WebSocket、会话、审计、实时帧推送 |
|
||||
| `backend/controller/rdp_live.go` | RDP 实时审计广播 |
|
||||
| `backend/controller/rdp_session.go` | RDP 会话列表、录制、终止 API |
|
||||
| `backend/pkg/rdp/engine.go` | 引擎选择 |
|
||||
| `backend/pkg/rdp/session_engine.go` | 自研 RDP 引擎主循环 |
|
||||
| `frontend/src/components/server/RdpDialog.tsx` | RDP 实时操作窗口 |
|
||||
| `frontend/src/components/system/RdpAuditViewer.tsx` | RDP 实时审计与历史回放 |
|
||||
|
||||
### 8.1 自研 RDP 引擎策略
|
||||
|
||||
当前后端固定使用自研 `SessionEngine`,不再通过 `RDP_ENGINE` 切换第三方兜底。已删除的 grdp 目录备份到:
|
||||
|
||||
```text
|
||||
/opt/rdp/third-party/grdp-aiops-backup-20260703
|
||||
```
|
||||
|
||||
仓内 `backend/third_party` 已清理,Dockerfile 不再复制本地 replace 模块。// Logan.Li
|
||||
|
||||
### 8.2 WebSocket 帧协议
|
||||
|
||||
服务端到浏览器:
|
||||
|
||||
```json
|
||||
{ "type": "session", "message": "123", "width": 1024, "height": 800 }
|
||||
{ "type": "status", "message": "正在连接 RDP ..." }
|
||||
{ "type": "error", "message": "..." }
|
||||
```
|
||||
|
||||
图像帧优先走二进制:
|
||||
|
||||
```text
|
||||
RDPR + x:uint32 + y:uint32 + width:uint32 + height:uint32 + rgba:[]
|
||||
```
|
||||
|
||||
历史回放帧走节流 PNG:
|
||||
|
||||
```json
|
||||
{ "type": "bitmap", "x": 0, "y": 0, "width": 300, "height": 200, "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 }
|
||||
{ "type": "clipboard", "text": "..." }
|
||||
```
|
||||
|
||||
### 8.3 输入优化
|
||||
|
||||
- 前端 `mousemove` 16ms 合并,只保留最新坐标。
|
||||
- `mouse_down`、`mouse_up` 前先 flush 最新坐标,保证拖拽起止准确。
|
||||
- 后端输入队列满时丢弃移动事件,关键事件优先。
|
||||
- RDP 引擎处理输入时不打印每个 `mousemove`,避免日志拖慢实时操作。
|
||||
|
||||
### 8.4 图形优化
|
||||
|
||||
- 实时图形帧使用 RGBA 二进制直传,历史回放使用节流 PNG 快照。
|
||||
- 前端 Canvas 使用 `putImageData()` 绘制二进制帧。
|
||||
- 右下角帧统计节流刷新,避免每帧 React 重渲染。
|
||||
- RDP 录屏只保存节流快照,不保存每个实时帧。
|
||||
|
||||
### 8.5 审计策略
|
||||
|
||||
持久化:
|
||||
|
||||
- 会话元数据:用户、资产、开始/结束时间、状态、录制大小。
|
||||
- 离散操作:点击、滚轮、剪贴板、文件上传下载、输入摘要。
|
||||
- 历史回放:节流图形快照。
|
||||
|
||||
不持久化:
|
||||
|
||||
- 高频 `mousemove`。
|
||||
- 每个实时 RGBA 帧。
|
||||
- 无业务含义的连续坐标变化。
|
||||
|
||||
拖拽建议记录:
|
||||
|
||||
```text
|
||||
[RDP拖拽] button=left start=(x1,y1) end=(x2,y2) duration=850ms
|
||||
```
|
||||
|
||||
普通点击建议记录:
|
||||
|
||||
```text
|
||||
[RDP点击] button=left x=100 y=200
|
||||
```
|
||||
|
||||
键盘输入建议合并:
|
||||
|
||||
```text
|
||||
[RDP输入] abc*** # 敏感字段后续可脱敏
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 审计页设计
|
||||
|
||||
当前菜单:`/audit/terminal`
|
||||
|
||||
建议子页签:
|
||||
|
||||
| 页签 | 内容 |
|
||||
|------|------|
|
||||
| 在线会话 | SSH 在线会话 + RDP 在线会话 |
|
||||
| 历史会话 | SSH 历史 asciicast 回放 + RDP Canvas 回放 |
|
||||
| 会话命令 | SSH 命令 + RDP 操作摘要,按会话聚合展示 |
|
||||
|
||||
### 9.1 在线会话
|
||||
|
||||
SSH:
|
||||
|
||||
- 使用 `LiveMonitorDialog`
|
||||
- 后端 `/ws/audit/:sessionId`
|
||||
- 只读 xterm
|
||||
|
||||
RDP:
|
||||
|
||||
- 使用 `RdpAuditViewer mode="live"`
|
||||
- 后端 `/ws/rdp-audit/:sessionId`
|
||||
- 只读 Canvas
|
||||
|
||||
### 9.2 历史会话
|
||||
|
||||
SSH:
|
||||
|
||||
- 使用 `ReplayDialog`
|
||||
- 数据源:`terminalSessionApi.getRecord()`
|
||||
- 格式:asciicast 文本
|
||||
|
||||
RDP:
|
||||
|
||||
- 使用 `RdpAuditViewer mode="replay"`
|
||||
- 数据源:`rdpSessionApi.getRecord()`
|
||||
- 格式:节流 PNG bitmap JSON
|
||||
|
||||
### 9.3 会话命令 / 操作摘要
|
||||
|
||||
问题:
|
||||
|
||||
- SSH 命令记录适合逐条展示。
|
||||
- RDP 如果按键、点击、剪贴板都逐条展示,会出现大量低价值行。
|
||||
|
||||
建议:
|
||||
|
||||
1. 后端写入时尽量合并:
|
||||
- 连续键盘输入合并为 `[RDP输入] ...`
|
||||
- 拖拽只记录开始/结束。
|
||||
- 点击和滚轮按短时间窗口合并。
|
||||
2. 前端展示时按会话聚合:
|
||||
- 默认展示会话摘要。
|
||||
- 展开后看详细事件。
|
||||
- 高危动作、文件传输、剪贴板单独标记。
|
||||
|
||||
---
|
||||
|
||||
## 10. 当前架构质量评估
|
||||
|
||||
### 10.1 做得好的地方
|
||||
|
||||
- `backend/pkg/rdp/domain` 定义了引擎接口,控制器不直接依赖协议细节。
|
||||
- `transport/`、`credssp/`、`rle/`、`display/` 已经有基础分层。
|
||||
- RDP 依赖已收敛到自研协议栈,构建不再需要 `backend/third_party`。
|
||||
- xterm 终端已抽出公共适配工具,后续维护成本低。
|
||||
- RDP 实时帧和历史回放帧开始分离,避免审计存储被实时帧撑爆。
|
||||
|
||||
### 10.2 需要优化的地方
|
||||
|
||||
| 文件 | 问题 | 建议 |
|
||||
|------|------|------|
|
||||
| `backend/pkg/rdp/mcs_probe.go` | 1664 行,严重超过红线 | 拆成 `mcs_connect.go`、`gcc_caps.go`、`confirm_active.go`、`client_info.go`、`probe.go` |
|
||||
| `backend/controller/rdp.go` | 约 460 行,承担会话、审计、帧编码、快照 | 拆出 `rdp_sink.go`、`rdp_audit.go`、`rdp_input_audit.go` |
|
||||
| `RdpDialog.tsx` | 实时渲染、输入、工具面板入口集中 | 后续拆出 `useRdpSocket()`、`useRdpCanvas()`、`useRdpInput()` |
|
||||
| `PodExecTerminal.tsx` | 连接、UI、resize 混合 | 可抽 `useXtermWebSocket()` |
|
||||
| `KubectlTerminal.tsx` | 与 Pod exec 重复 | 与 Pod/Workspace 共用 hook |
|
||||
|
||||
### 10.3 建议目标结构
|
||||
|
||||
后端 RDP:
|
||||
|
||||
```text
|
||||
backend/pkg/rdp/
|
||||
├── domain/
|
||||
├── engine.go
|
||||
├── session_engine.go
|
||||
├── input/
|
||||
│ ├── fastpath.go
|
||||
│ └── keymap.go
|
||||
├── graphics/
|
||||
│ ├── framebuffer.go
|
||||
│ ├── codec.go
|
||||
│ └── rle/
|
||||
├── mcs/
|
||||
│ ├── connect.go
|
||||
│ ├── gcc.go
|
||||
│ ├── caps.go
|
||||
│ └── active.go
|
||||
├── credssp/
|
||||
└── transport/
|
||||
```
|
||||
|
||||
控制器:
|
||||
|
||||
```text
|
||||
backend/controller/
|
||||
├── rdp.go # 路由入口和会话编排
|
||||
├── rdp_sink.go # WebSocket frame sink
|
||||
├── rdp_live.go # 实时审计广播
|
||||
├── rdp_input_audit.go # 点击/拖拽/输入摘要
|
||||
├── rdp_session.go # 会话 REST API
|
||||
├── rdp_staged.go # RDPDR 下载暂存 API
|
||||
└── rdp_cred.go
|
||||
```
|
||||
|
||||
前端:
|
||||
|
||||
```text
|
||||
frontend/src/components/terminal/
|
||||
├── useXtermSocket.ts
|
||||
├── XtermDialogShell.tsx
|
||||
└── terminalTheme.ts
|
||||
|
||||
frontend/src/components/rdp/
|
||||
├── useRdpSocket.ts
|
||||
├── useRdpCanvas.ts
|
||||
├── useRdpInput.ts
|
||||
├── RdpDialog.tsx
|
||||
└── RdpAuditViewer.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 验证清单
|
||||
|
||||
终端类修改后建议验证:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npx tsc --noEmit
|
||||
npm run build
|
||||
```
|
||||
|
||||
后端修改后建议验证:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go build -o aiops .
|
||||
```
|
||||
|
||||
端到端:
|
||||
|
||||
```bash
|
||||
./run.sh docker up
|
||||
python3 tests/integration_test.py --host localhost --port 8080
|
||||
```
|
||||
|
||||
手动检查:
|
||||
|
||||
- SSH 终端打开、缩放、全屏、输入、回放。
|
||||
- Pod exec 打开、切 shell、缩放、全屏。
|
||||
- kubectl 终端打开、缩放、全屏。
|
||||
- Workspace 终端打开、缩放、全屏。
|
||||
- RDP 打开、拖动窗口、文件夹拖拽、实时审计、历史回放。
|
||||
- 会话命令页筛选、导出、从命令跳转回放。// Logan.Li
|
||||
@@ -0,0 +1,487 @@
|
||||
# AIOps 终端体系实现规格
|
||||
|
||||
> 更新:2026-07-05
|
||||
> 目标:让另一个项目仅阅读本文档,也能实现 WebSSH、Pod exec、kubectl、Workspace 终端和审计回放。
|
||||
> 范围:xterm.js 前端底座、WebSocket 协议、后端 PTY/SSH/K8s 代理、会话录像、命令审计、实时查看。
|
||||
|
||||
---
|
||||
|
||||
## 1. 终端类型和职责
|
||||
|
||||
| 类型 | 前端渲染 | 后端执行 | 典型入口 | 审计方式 |
|
||||
|------|----------|----------|----------|----------|
|
||||
| SSH WebShell | xterm.js | `ssh.Session + PTY` | 服务器管理、WebShell | asciicast + 命令 |
|
||||
| Pod Terminal | xterm.js | K8s `pods/exec` WebSocket | Pod 详情 | 预留 K8s 审计 |
|
||||
| kubectl Terminal | xterm.js | 本地 `kubectl` 进程 + PTY | 集群管理 | 建议命令审计 |
|
||||
| Workspace Terminal | xterm.js | 本地 shell + PTY,限制目录 | 流水线工作区 | 建议命令审计 |
|
||||
| RDP | Canvas | RDP 协议引擎 | Windows 资产 | 独立图形录像 |
|
||||
|
||||
本文只定义字符终端。RDP 详见 [RDP 实现规格](../rdp/implementation-spec.md)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 统一前端底座
|
||||
|
||||
### 2.1 组件结构
|
||||
|
||||
```text
|
||||
Terminal Dialog/Page
|
||||
├── create Terminal()
|
||||
├── load FitAddon
|
||||
├── mount into .xterm-terminal-host
|
||||
├── open WebSocket
|
||||
├── onData → ws.send({ status: 0, data })
|
||||
├── ResizeObserver → fit() → ws.send({ status: 1, cols, rows })
|
||||
└── ws.onmessage → terminal.write(data)
|
||||
```
|
||||
|
||||
公共工具应提供:
|
||||
|
||||
| 方法 | 职责 |
|
||||
|------|------|
|
||||
| `mountXterm()` | 延迟挂载、打开 terminal、执行多次初始 fit |
|
||||
| `fitTerminal()` | 调用 FitAddon,返回 `cols/rows` |
|
||||
| `attachTerminalResize()` | 监听容器 resize,节流 fit 并同步后端 |
|
||||
| `disposeTerminal()` | 关闭 WebSocket、移除 observer、销毁 xterm |
|
||||
|
||||
### 2.2 DOM 和样式要求
|
||||
|
||||
```tsx
|
||||
<div className="xterm-terminal-host" ref={containerRef} />
|
||||
```
|
||||
|
||||
```css
|
||||
.xterm-terminal-host {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #0a0e14;
|
||||
}
|
||||
|
||||
.xterm-terminal-host .xterm {
|
||||
height: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 终端容器必须有稳定高度,不能依赖内容撑开。
|
||||
- Dialog 动画会影响 xterm 测量,终端弹窗建议禁用打开/关闭缩放动画。
|
||||
- `term.open()` 同一实例只能调用一次。
|
||||
- `fit()` 后必须把 `cols/rows` 发给后端,否则远端 PTY 仍按旧尺寸输出。
|
||||
|
||||
### 2.3 推荐 xterm 配置
|
||||
|
||||
```ts
|
||||
const terminalOptions = {
|
||||
cursorBlink: true,
|
||||
convertEol: true,
|
||||
fontFamily: 'Menlo, Monaco, Consolas, monospace',
|
||||
fontSize: 13,
|
||||
lineHeight: 1.15,
|
||||
scrollback: 5000,
|
||||
theme: {
|
||||
background: '#0a0e14',
|
||||
foreground: '#d8dee9',
|
||||
cursor: '#f8fafc',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
交互要求:
|
||||
|
||||
- ESC、Ctrl+C、Ctrl+D 必须传给终端,不应被 Dialog 或页面快捷键抢走。
|
||||
- 粘贴走 xterm 默认粘贴或 `navigator.clipboard.readText()` 后写入 WebSocket。
|
||||
- 多标签终端切换时,不要销毁非当前标签的 WebSocket,除非用户主动关闭。
|
||||
|
||||
---
|
||||
|
||||
## 3. WebSocket 协议
|
||||
|
||||
### 3.1 通用 JSON 协议
|
||||
|
||||
浏览器到后端:
|
||||
|
||||
```json
|
||||
{ "status": 0, "data": "ls -la\n" }
|
||||
{ "status": 1, "cols": 120, "rows": 34 }
|
||||
```
|
||||
|
||||
后端到浏览器:
|
||||
|
||||
```json
|
||||
{ "status": 0, "data": "total 8\r\n" }
|
||||
{ "status": 2, "message": "connected" }
|
||||
{ "status": -1, "message": "connection failed" }
|
||||
```
|
||||
|
||||
字段语义:
|
||||
|
||||
| 字段 | 方向 | 说明 |
|
||||
|------|------|------|
|
||||
| `status=0` | 双向 | stdin/stdout 数据 |
|
||||
| `status=1` | 浏览器到后端 | resize,包含 `cols/rows` |
|
||||
| `status=2` | 后端到浏览器 | 状态提示,可选 |
|
||||
| `status=-1` | 后端到浏览器 | 错误提示 |
|
||||
|
||||
实现要求:
|
||||
|
||||
- 后端读取 WebSocket 后按 `status` 分流。
|
||||
- stdout/stderr 可以合并写回 `status=0`,也可以扩展 `stream` 字段。
|
||||
- 输入数据必须原样写入 PTY,不要按行缓存,否则 vim/top 等交互程序不可用。
|
||||
- resize 事件必须节流,建议 100-200ms。
|
||||
|
||||
### 3.2 鉴权
|
||||
|
||||
WebSocket 无法稳定设置自定义 Header,因此支持两种方式:
|
||||
|
||||
```text
|
||||
/ws/terminal/:serverId?token=<JWT>
|
||||
/ws/pod/exec?...&token=<JWT>
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 后端优先读 `Authorization`,其次读 `?token=`。
|
||||
- 鉴权通过后仍要做资源权限校验。
|
||||
- token 不能写入审计命令和终端录制。
|
||||
|
||||
---
|
||||
|
||||
## 4. SSH WebShell
|
||||
|
||||
### 4.1 后端流程
|
||||
|
||||
```text
|
||||
GET /ws/terminal/:serverId
|
||||
→ JWTAuth
|
||||
→ load server asset
|
||||
→ load credential
|
||||
→ ssh.Dial
|
||||
→ session.RequestPty(term, rows, cols, modes)
|
||||
→ session.Shell()
|
||||
→ create sys_terminal_sessions
|
||||
→ goroutine: ws input → ssh stdin
|
||||
→ goroutine: ssh stdout/stderr → ws output + recorder + audit hub
|
||||
→ on close: update duration, status, record_data, record_size
|
||||
```
|
||||
|
||||
PTY 参数:
|
||||
|
||||
```go
|
||||
ssh.TerminalModes{
|
||||
ssh.ECHO: 1,
|
||||
ssh.TTY_OP_ISPEED: 14400,
|
||||
ssh.TTY_OP_OSPEED: 14400,
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 命令审计
|
||||
|
||||
基础策略:
|
||||
|
||||
- 按回车提取命令。
|
||||
- 不记录每个按键。
|
||||
- 下一条命令出现前,统计上一条命令后的输出字节数。
|
||||
- 粘贴多行脚本应合并或标记为多行命令。
|
||||
|
||||
命令表:
|
||||
|
||||
```text
|
||||
sys_session_commands
|
||||
session_id
|
||||
session_type = terminal
|
||||
user_id / username
|
||||
server_id / server_name
|
||||
command
|
||||
output_size
|
||||
timestamp
|
||||
```
|
||||
|
||||
敏感信息建议:
|
||||
|
||||
- 对 `password=...`、`token=...`、`AKIA...`、私钥片段做脱敏。
|
||||
- 命令审计保存脱敏结果,原始录制可按安全策略决定是否保存。
|
||||
|
||||
---
|
||||
|
||||
## 5. asciicast 录制和回放
|
||||
|
||||
### 5.1 录制格式
|
||||
|
||||
使用 asciicast v2:
|
||||
|
||||
```json
|
||||
{"version":2,"width":120,"height":34,"timestamp":1783131042,"env":{"TERM":"xterm-256color"}}
|
||||
[0.120,"o","Last login: ...\r\n"]
|
||||
[0.450,"o","$ "]
|
||||
[1.200,"o","ls -la\r\n"]
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- 第一行是 header。
|
||||
- 后续每行是 `[timeOffset, "o", output]`。
|
||||
- 只录制后端输出,不录制用户输入的独立事件,避免重复。
|
||||
- `RecordData` 建议限制大小,超过上限后截断并标记。
|
||||
|
||||
### 5.2 回放
|
||||
|
||||
前端回放流程:
|
||||
|
||||
```text
|
||||
GET /api/v1/terminal-sessions/:id/record
|
||||
→ parse asciicast
|
||||
→ create read-only xterm
|
||||
→ 按 timeOffset 调度 terminal.write()
|
||||
→ 支持播放、暂停、倍速、拖动进度
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- 回放画面和原终端输出顺序一致。
|
||||
- 支持命令书签跳转。
|
||||
- 删除会话后录制不可访问。
|
||||
|
||||
---
|
||||
|
||||
## 6. Pod Exec
|
||||
|
||||
### 6.1 后端链路
|
||||
|
||||
```text
|
||||
Browser xterm
|
||||
→ AIOps backend WebSocket
|
||||
→ Kubernetes API /api/v1/namespaces/{ns}/pods/{pod}/exec
|
||||
→ channel.k8s.io WebSocket
|
||||
→ container shell
|
||||
```
|
||||
|
||||
K8s channel:
|
||||
|
||||
| Channel | 方向 | 内容 |
|
||||
|---------|------|------|
|
||||
| 0 | client to pod | stdin |
|
||||
| 1 | pod to client | stdout |
|
||||
| 2 | pod to client | stderr |
|
||||
| 3 | pod to client | error/status |
|
||||
| 4 | client to pod | resize |
|
||||
|
||||
resize frame:
|
||||
|
||||
```json
|
||||
{ "Width": 120, "Height": 34 }
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- shell 可选 `/bin/bash`、`/bin/sh`。
|
||||
- namespace、pod、container 必须做权限校验。
|
||||
- 容器切换时必须重连。
|
||||
- Pod exec 不应写入 SSH 的 `sys_terminal_sessions`,建议后续建 K8s 独立会话表。
|
||||
|
||||
---
|
||||
|
||||
## 7. kubectl 终端
|
||||
|
||||
### 7.1 后端链路
|
||||
|
||||
```text
|
||||
GET /ws/kubectl?cluster_id=1
|
||||
→ JWTAuth + cluster permission
|
||||
→ create temp kubeconfig
|
||||
→ exec kubectl with PTY
|
||||
→ ws input/output bridge
|
||||
→ cleanup temp kubeconfig and process
|
||||
```
|
||||
|
||||
安全要求:
|
||||
|
||||
- kubeconfig 必须按用户和集群隔离。
|
||||
- 临时文件使用随机名,关闭后删除。
|
||||
- 禁止复用上一次集群上下文。
|
||||
- kubectl 权限由 AIOps RBAC 和集群 RBAC 双重约束。
|
||||
|
||||
建议审计:
|
||||
|
||||
- 记录输入命令。
|
||||
- 标记 `delete`、`scale`、`rollout restart`、`patch`、`apply` 为高风险。
|
||||
- 后续接入审批或二次确认。
|
||||
|
||||
---
|
||||
|
||||
## 8. Workspace 终端
|
||||
|
||||
### 8.1 后端链路
|
||||
|
||||
```text
|
||||
GET /ws/workspace/:pipelineRunId/terminal
|
||||
→ JWTAuth + pipeline permission
|
||||
→ resolve workspace dir
|
||||
→ ensure dir is under workspace root
|
||||
→ start shell with PTY
|
||||
→ ws input/output bridge
|
||||
```
|
||||
|
||||
路径安全:
|
||||
|
||||
- 所有路径必须 `filepath.Clean`。
|
||||
- `realpath` 后必须仍位于 workspace root 下。
|
||||
- 禁止用户通过 `../../` 进入任意系统目录。
|
||||
- Shell 的工作目录固定为本次 pipeline workspace。
|
||||
|
||||
---
|
||||
|
||||
## 9. 实时审计
|
||||
|
||||
### 9.1 SSH 实时查看
|
||||
|
||||
```text
|
||||
active SSH session
|
||||
→ AuditHub broadcast output
|
||||
→ /ws/audit/:sessionId
|
||||
→ LiveMonitorDialog read-only xterm
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 审计端只读,不允许发送 stdin。
|
||||
- 审计连接不能影响主会话性能。
|
||||
- 活动会话结束后,审计连接收到关闭提示。
|
||||
|
||||
### 9.2 统一审计页
|
||||
|
||||
建议页面分区:
|
||||
|
||||
| 页签 | 内容 |
|
||||
|------|------|
|
||||
| 在线会话 | SSH active + RDP active |
|
||||
| 历史会话 | SSH asciicast + RDP bitmap replay |
|
||||
| 会话命令 | SSH 命令 + RDP 操作摘要 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 数据模型
|
||||
|
||||
### 10.1 SSH 会话
|
||||
|
||||
```text
|
||||
sys_terminal_sessions
|
||||
id
|
||||
user_id / username
|
||||
server_id / server_name / server_ip / server_port
|
||||
client_ip
|
||||
status: active | closed | failed
|
||||
duration
|
||||
record_data
|
||||
record_size
|
||||
start_time / end_time
|
||||
```
|
||||
|
||||
### 10.2 命令审计
|
||||
|
||||
```text
|
||||
sys_session_commands
|
||||
id
|
||||
session_id
|
||||
session_type: terminal | rdp
|
||||
user_id / username
|
||||
server_id / server_name
|
||||
command
|
||||
output_size
|
||||
timestamp
|
||||
```
|
||||
|
||||
### 10.3 策略配置
|
||||
|
||||
```text
|
||||
sys_audit_settings
|
||||
retention_days
|
||||
record_all
|
||||
session_watermark_enabled
|
||||
session_record_enabled
|
||||
command_policy_enabled
|
||||
approval_required
|
||||
```
|
||||
|
||||
字符终端第一版至少需要:
|
||||
|
||||
- `record_all`
|
||||
- `session_record_enabled`
|
||||
- `session_watermark_enabled`
|
||||
- `retention_days`
|
||||
|
||||
---
|
||||
|
||||
## 11. 错误处理
|
||||
|
||||
| 场景 | 前端表现 | 后端处理 |
|
||||
|------|----------|----------|
|
||||
| 鉴权失败 | 显示未登录或无权限 | 关闭 WebSocket |
|
||||
| 凭据错误 | 显示连接失败 | 会话状态 `failed`,保存错误 |
|
||||
| SSH 超时 | 显示超时 | 释放 session/client |
|
||||
| PTY resize 失败 | 显示警告或忽略 | 继续会话 |
|
||||
| WebSocket 断开 | 显示已断开 | 关闭远端 session |
|
||||
| 用户关闭窗口 | 清理资源 | 更新 `end_time`、`duration` |
|
||||
|
||||
实现要求:
|
||||
|
||||
- 任何退出路径都必须关闭 PTY、进程、SSH client、WebSocket。
|
||||
- 后端 goroutine 需要 context 取消,避免泄露。
|
||||
- 会话状态更新要放在 `defer` 中兜底执行。
|
||||
|
||||
---
|
||||
|
||||
## 12. 验收清单
|
||||
|
||||
### 12.1 WebSSH
|
||||
|
||||
1. 打开服务器 SSH 终端成功。
|
||||
2. `ls`、`top`、`vim`、Ctrl+C、Ctrl+D 可用。
|
||||
3. 拖动窗口或最大化后,远端 `stty size` 与前端一致。
|
||||
4. 会话关闭后 `sys_terminal_sessions` 状态为 `closed`。
|
||||
5. 回放可播放完整输出。
|
||||
6. 命令审计能看到命令和输出大小。
|
||||
|
||||
### 12.2 Pod Exec
|
||||
|
||||
1. 选择 namespace/pod/container 后能进入 shell。
|
||||
2. stdout/stderr 都能显示。
|
||||
3. resize 后 `stty size` 正确。
|
||||
4. 容器切换会重连。
|
||||
5. 无权限 namespace 无法连接。
|
||||
|
||||
### 12.3 kubectl
|
||||
|
||||
1. 打开指定集群 kubectl 终端。
|
||||
2. `kubectl get pods -A` 输出正常。
|
||||
3. 切换集群不会复用旧上下文。
|
||||
4. 关闭窗口后后端 kubectl 进程退出。
|
||||
|
||||
### 12.4 Workspace
|
||||
|
||||
1. 打开流水线工作区终端。
|
||||
2. 默认目录在 workspace root。
|
||||
3. `pwd`、`ls` 正常。
|
||||
4. 不能通过路径逃逸访问系统目录。
|
||||
|
||||
### 12.5 审计回放
|
||||
|
||||
1. 在线会话能实时只读查看。
|
||||
2. 历史会话能按时间播放。
|
||||
3. 删除会话后录制和命令记录按策略处理。
|
||||
4. 关闭录制策略后不写 `record_data`。
|
||||
|
||||
---
|
||||
|
||||
## 13. 常见问题
|
||||
|
||||
| 问题 | 原因 | 处理 |
|
||||
|------|------|------|
|
||||
| 终端右侧黑边 | 初始 fit 太早 | mount 后延迟多次 fit |
|
||||
| resize 后远端仍旧宽度 | 只 fit 前端,未发 cols/rows | WebSocket 发送 `status=1` |
|
||||
| Ctrl+C 不生效 | 页面快捷键拦截 | xterm 容器 focus 后透传 |
|
||||
| vim 显示错乱 | PTY 类型或 resize 不正确 | 使用 `xterm-256color` 并同步尺寸 |
|
||||
| 回放速度不对 | time offset 解析错误 | 按 asciicast v2 第一列调度 |
|
||||
| WebSocket 断开后进程残留 | 后端未 cancel context | close handler 统一清理 |
|
||||
| Pod exec 没输出 | K8s channel 未拆分 | 按 channel 1/2 读取 stdout/stderr |
|
||||
Reference in new issue
Block a user