# JumpServer 自动化演进日志 ## 概述 从 2026-06-21 到 2026-06-25,JumpServer 自动化经历了 **5 种技术路线、17+ 个主要版本** 的迭代,最终选择了 Playwright Web CLI 方案作为生产方案。 ## 时间线 ``` 2026-06-21 ─ 06-22 │ 06-22 ─ 06-23 │ 06-23 ─ 06-25 SSH 路线探索 │ Web CLI 路线验证 │ 生产化 + 部署实战 │ │ • plink.exe (v8) │ • webcli_probe │ • webcli_v4-v7 迭代 • Paramiko (v1-v7) │ • webcli_login_v1 │ • SFTP 文件上传 • wexpect (v11) │ • webcli_inspect │ • 分块 base64 传输 • API auth 探测 │ • webcli_v2-v3 │ • hotfix 实战部署 │ │ • SSH Key 方案探索 ``` ## 各路线详细记录 ### 路线 1: plink.exe (PuTTY CLI) — 已废弃 | 版本 | 文件 | 发现 | |------|------|------| | v8 | `test_plink_v8.py` | plink 可以连接但输出捕获不稳定 | | - | `test_plink_formats.py` | MFA 提示格式有 3 种变体 | | - | `test_plink_verbose.py` | 详细日志模式可看到完整交互 | **废弃原因**:中文编码问题、交互式菜单导航不可靠、plink 不支持 PTY 分配。 ### 路线 2: Paramiko (Python SSH) — 已废弃 | 版本 | 文件 | 关键发现 | |------|------|---------| | v1-v5 | `test_paramiko_v1.py` ~ `v5.py` | 基本连接可行,但 OTP 流程脆弱 | | v6-v7 | `test_paramiko_v6.py` ~ `v7.py` | 交互式 shell 模式可绕过部分菜单 | | v8 | `jumpserver_paramiko_v8.py` | 尝试 channel.recv 非阻塞读取 | | v9 | `jumpserver_paramiko_v9.py` | 尝试 expect 模式(timeout 可控) | | v12 | `jumpserver_paramiko_v12.py` | SSH Gateway 方式(JumpServer → 目标) | | v13 | `jumpserver_paramiko_v13.py` | PTY 分配尝试 | | v14-v16 | `jumpserver_paramiko_v14.py` ~ `v16.py` | 多线程读写分离 | **废弃原因**:JumpServer 堡垒机菜单变化频繁(每次登录可能不同),Paramiko 的 expect 匹配无法适应。OTP 二次认证流程极其脆弱。 ### 路线 3: wexpect (Windows expect) — 已废弃 | 版本 | 文件 | 发现 | |------|------|------| | v11 | `jumpserver_wexpect_v11.py` | 可以 spawn plink 进程并交互 | | v2 | `jumpserver_wexpect_v2.py` | 中文输出匹配不稳定 | | v3 | `jumpserver_wexpect_v3.py` | timeout 设置和重试逻辑优化 | **废弃原因**:wexpect 在 Windows 上不稳定,中文编码问题难以解决,且与 plink 有相同的基础问题。 ### 路线 4: Web CLI (Playwright) — ✅ 当前生产方案 | 版本 | 文件 | 关键里程碑 | |------|------|-----------| | probe | `jumpserver_webcli_probe.py` | 成功探测 Luna 终端结构 | | login | `jumpserver_webcli_login_v1.py` | 实现自动登录(密码+OTP) | | inspect | `jumpserver_webcli_inspect.py` | 分析 Web 终端的 DOM 结构 | | v2 | `jumpserver_webcli_v2.py` | 基本命令执行 | | v3 | `jumpserver_webcli_v3.py` | 增加截图和输出保存 | | v4 | `jumpserver_webcli_v4.py` | 文件上传探索 | | v5 | `jumpserver_webcli_v5.py` | SFTP 界面自动化 | | v6 | `jumpserver_webcli_v6.py` | 超时控制 + 错误重试 | | **v7** | **`jumpserver_webcli_v7.py`** | **生产版本**:完整部署流程 | **v7 核心能力**: - 自动登录 JumpServer(用户名 + 密码 + OTP) - 导航到目标服务器(税友集团 → hz-oa-ai-g-dataquery-90-5-110) - 在 Web 终端中执行任意命令 - 截图保存(用于验证) - 输出日志保存 - SFTP 文件上传(set_input_files 方式) **关键突破**:发现 `OTP 双回车`问题 — 发送验证码后需按 2 次 Enter 触发验证流程。 ### 路线 5: SSH Key 认证 — 开发中 | 文件 | 状态 | |------|------| | `generate_ssh_keys.py` | 生成 RSA 密钥对 | | `jumpserver_auto_v17_key_auth.py` | 尝试用密钥认证连接 | | `ssh_keys/jumpserver_id_rsa` | 已生成的私钥 | **目标**:绕过 OTP 流程,实现完全无人值守的自动化。 ### 路线 6: JumpServer API Token — 探索中 | 文件 | 发现 | |------|------| | `create_connection_token.py` | 可通过 API 创建连接 Token | | `test_connection_token.py` | Token 可用于 SSH 连接 | | `probe_koko_token.py` | Koko(Web 终端)的 Token 格式不同 | | `probe_terminal_api.py` | 终端 API 端点探测 | ### 2026-06-25 (下午): xterm.js 输出捕获分析 + probe_v2 - **创建 `probe_xterm_buffer_v2.py`**:5 种方法探测 xterm.js 缓冲区 - 全局 Terminal 实例搜索 - DOM .xterm-rows 遍历 - helper-textarea 读取 - 渲染器类型检测(DOM vs Canvas) - React Fiber 组件树搜索 - **创建 `ANALYSIS.md`**:完整的能力-限制-优化分析 - 现有能力矩阵 + 已验证部署记录 - 输出捕获不可靠问题分析(3 种失败模式) - v8 结构化捕获设计 + Monkey Patch 兜底方案 - 不适用的 6 种场景 - 短/中/长期优化路线图 - **发现**:v7 的 `inner_html()` 输出捕获依赖 DOM 渲染器,Canvas 渲染器下失效 ### 下一步 - [ ] 运行 probe_xterm_buffer_v2.py 确认 xterm.js 实例暴露方式 - [ ] 根据探测结果实现 webcli_v8.py(结构化缓冲区读取) ### 2026-06-25 (晚间): v10.16 OTP 等待时间修复 **问题**:用户反馈 `ensure_logged_in()` 中 OTP 提交后 `wait_for_timeout(5000)` 只有 5 秒,JumpServer 服务端 OTP 校验通常需要 8-15 秒,导致过早判定超时并进入 URL 跳转检测,进而误判登录失败。 **修复**: 1. **MFA 页面消失轮询**:替代固定 5s 等待,改为 500ms × 40 次轮询(max 20s),检测 `input[name="code"]` 消失 2. **OTP 错误检测**:轮询中检测页面正文是否含 "验证码错误/OTP 错误/已过期/expired/Invalid" 关键字 3. **URL 跳转延长**:workbench URL 检测从 15×1s=15s 延长到 60×500ms=30s 4. **超时截图**:30s 未跳转时自动截图留存(`v10_post_otp_stuck.png`) 5. **代码安全**:截图前确保 `OUTPUT_DIR` 存在 **影响范围**:`jumpserver_webcli_v10_persistent.py` — `ensure_logged_in()` 函数 (lines 209-245) **副作用**:正常流程下 MFA 页面通常在 3-5s 消失,轮询会在第 6-10 次检查(3-5s)时返回,不影响正常速度。仅在网络慢/服务器高负载时才会触发更长的等待。 ## 成功部署记录 ### Hotfix #116: 扫码获取 (2026-06-22) - 使用 webcli v2-v4 - 部署 `auth_qrcode.py` - 5 个部署诊断迭代(diag_v7 ~ diag7_v7) ### Hotfix #120: 扫码自动确认 (2026-06-23) - 使用 webcli v5 + v7 - 部署 `qrcode_service.py`(29KB 源码) - **首次使用 SFTP 分块上传**(8 chunks) - 18 个 webcli 子脚本(part1/2、diag、verify、sub、tail、view) - 从 Part A SFTP 到 Part B 部署到 Part C 验证 ### Hotfix #48: Nginx Upstream (2026-06-24) - 使用 webcli v7 - 修复 nginx upstream 配置 - 包含 HTTPS/SSL 验证 ### Hotfix SSO: 单点登录启用 (2026-06-25) - 使用 webcli v7 - 部署 config.py + 环境变量注入 ## 记录在案的错误与解决方案 | 错误 | 根因 | 解决方案 | 记录 | |------|------|---------|------| | OTP 验证失败 | 发送验证码后未按2次回车 | `test_double_enter.py` 确认需要 `\r\n` | 代码中固定加 `\n` | | Web CLI 命令输入截断 | 输入框字符限制 ~20KB | 分2批次输入(16KB + 12.8KB) | `upload_chunks_sftp.py` | | Docker cp 路径错误 | 容器内路径与宿主机不同 | 使用 `docker exec` 确认后操作 | webcli_v5_part2.sh | | 容器重启后立即 curl 失败 | 服务启动需要时间 | 加 `sleep 8` 等待 | webcli_v5_cmd.sh | | Luna 终端编码乱码 | .sh 文件非 UTF-8 | 使用 `webcli_v3_cmd.utf8.sh` | webcli_v3 | | SFTP 文件选择器不弹 | webkitdirectory 属性过滤 | v9.8 修复 set_input_files 跳过验证 | upload_chunks_sftp.py |