Files
Nexus/docs/design/specs/2026-06-11-server-browser-chromium-design.md
2026-07-08 22:31:31 +08:00

174 lines
7.2 KiB
Markdown
Raw Permalink 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.
# 服务端远程浏览器(Playwright Chromium)— 设计说明
> **取代** iframe 渲染方案([2026-06-11-embedded-browser-design.md](./2026-06-11-embedded-browser-design.md) 中的页面加载方式)。
> UI 状态(标签、收藏 `nav_links`、窗口位置)仍走 `/api/browser/state`,不变。
## 背景与目标
运维需要在 Nexus 浮动面板内**完整浏览任意公网站点**,不受 `X-Frame-Options` / CSP `frame-ancestors` 限制,且使用**服务端独立浏览器内核**,而非用户本机标签页内的 iframe。
### 用户确认(2026-06-11
| 决策项 | 选择 |
|--------|------|
| 可访问范围 | **任意公网** `http` / `https` |
| 内核 | **Chromium**Playwright),不要求 Firefox |
| 交互 | 在 Nexus 面板内看画面 + 键鼠操作(非「新标签打开」为主) |
### 与 iframe 方案对比
| | iframe(现状) | 服务端 Chromium(目标) |
|--|----------------|---------------------------|
| 渲染位置 | 用户浏览器 | Nexus 服务器 |
| 站点嵌入限制 | 常被拒绝 | 无(顶层窗口) |
| SSRF 风险 | 无 | **有,必须缓解** |
| 资源 | 几乎无 | 每会话 ~200500MB RAM |
| Cookie / 登录 | 用户本机 | **服务器浏览器 Profile** |
## 方案概述
类比 RDPguacd → WebSocket → 前端画布),但 Nexus **拥有**浏览器进程与协议:
```
管理员浏览器 (Vue SPA)
│ JWT WebSocket + REST
Nexus API (FastAPI) — 桥接,不跑 Chromium
│ Worker Key + 内网 HTTPS
Browser Worker VPS — Playwright Chromiumheadless + CDP screencast
公网目标站点
```
| 组件 | 职责 |
|------|------|
| **Playwright Chromium** | 运行在 **Browser Worker**Nexus 桥接会话 |
| **URL 安全网关** | 导航前 DNS 解析 + IP 段校验,阻断私网/元数据 SSRF |
| **`/ws/browser/{session_id}`** | 推送 JPEG 帧;接收 navigate / 键鼠 / 滚动 |
| **`POST/DELETE /api/browser/sessions`** | 创建、销毁会话 |
| **`GlobalBrowserPanel`** | `<canvas>` 替代 `<iframe>`;保留地址栏、标签、收藏、最小化 |
| **`/api/browser/state`** | 不变 — tabs、visits、nav_links、rect |
**刻意不做(首版)**
- 不做 HTTP 反向代理(HTML 改写 / Cookie 注入)
- 不做浏览内容审计或页面录制落库
- 不做多管理员并发压测级扩展(单人运维,全局并发 ≤2)
- 不做本机 Cookie 与服务器 Profile 双向同步
## WebSocket 协议(草案)
鉴权:与终端/RDP 一致,query `?token=` = 常规 access JWT`sub` = admin_id),且 `session` 归属该校验通过的管理员。
### 服务端 → 客户端
| type | 字段 | 说明 |
|------|------|------|
| `BROWSER_INIT` | `session_id`, `viewport_w`, `viewport_h` | 连接就绪 |
| `SCREENCAST_FRAME` | `data_b64`, `metadata` | CDP screencast JPEG |
| `PAGE_INFO` | `url`, `title` | 导航完成或标题变化 |
| `ERROR` | `message`, `code` | URL 拒绝、超时、Playwright 错误 |
| `PONG` | — | 心跳 |
### 客户端 → 服务端
| type | 字段 | 说明 |
|------|------|------|
| `NAVIGATE` | `url` | 经 SSRF 校验后 `page.goto` |
| `RELOAD` / `GO_BACK` / `GO_FORWARD` | — | 历史导航 |
| `MOUSE` | `event`, `x`, `y`, `button` | move / down / up / wheel |
| `KEY` | `event`, `key`, `code`, `text` | down / up / type |
| `VIEWPORT` | `width`, `height` | 面板尺寸变化 |
| `PING` | — | 心跳 |
坐标:`x/y` 为相对 viewport 的 CSS 像素,与 screencast 帧尺寸一致。
## SSRF 防护(任意公网)
导航与每次 redirect 后 **重新校验** 最终 URL
1.`http:` / `https:`;拒绝凭据、`javascript:``data:``file:`
2. 解析 hostname → `getaddrinfo` → **所有**解析 IP 必须不在阻断段:
- `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`
- `169.254.0.0/16`, `0.0.0.0/8`, `100.64.0.0/10`
- IPv6: `::1`, `fc00::/7`, `fe80::/10`
3. 阻断常见 metadata 主机名(如 `169.254.169.254``*metadata*` 字面)
4. 导航超时 30s;单页资源不限制由 Chromium 自身处理
5. 失败时 WS 返回 `ERROR`**不**静默降级
> 单人运维下 residual riskDNS rebinding 需 TTL 短重查或 redirect 链逐跳校验;首版采用 **redirect 监听 + 每跳校验**。
## 会话与资源
| 策略 | 值 |
|------|-----|
| 每管理员活跃会话 | 1 |
| 全局活跃会话 | ≤2 |
| 空闲超时 | 30 min 无 WS 活动 → 关闭 browser context |
| 视口默认 | 1280×720(可随面板 resize |
| Screencast | CDP `Page.startScreencast`, JPEG quality ~80 |
进程模型:单例 Playwright `Browser` 进程 + 每会话独立 `BrowserContext`(隔离 Cookie)。
## 部署(选定:独立 Worker 服务器)
> **购机与装机计划**[`2026-06-11-browser-worker-server-procurement.md`](../plans/2026-06-11-browser-worker-server-procurement.md)
Playwright Chromium **不装入 Nexus 主站镜像**,而在**单独 VPSBrowser Worker**运行;Nexus 主站仅桥接 WebSocket。
```
管理员浏览器 → Nexus APIJWT)→ Browser WorkerWorker Key)→ 公网
```
| 组件 | 部署位置 |
|------|----------|
| `browser-worker/` 服务 + Chromium | **Worker VPS**2C4G 起) |
| `/api/browser/sessions``/ws/browser/*` 桥接 | **Nexus 主站** |
| MySQL UI 状态 | Nexus 主站(不变) |
**Nexus 主站 `.env`**
```env
NEXUS_BROWSER_ENABLED=1
NEXUS_BROWSER_WORKER_URL=https://<worker-internal-or-vpn>:8443
NEXUS_BROWSER_WORKER_SECRET=<shared-secret>
NEXUS_BROWSER_MAX_SESSIONS=2
NEXUS_BROWSER_IDLE_TIMEOUT_SEC=1800
```
**Worker 防火墙**8443 **仅** Nexus 主站 IP;不对公网开放 Worker API。
**降级**`NEXUS_BROWSER_ENABLED=0` 或 Worker 不可达 → `POST /api/browser/sessions` **503**;前端提示「服务端浏览器未启用」。
~~`Dockerfile.prod` 增加 Chromium~~**Worker 模式不修改主站镜像体积**
## 前端变更
- `GlobalBrowserPanel.vue``<iframe>``<canvas>` + `useRemoteBrowserSession.ts`
- 保留:浮动/最小化/不可关闭/重启、标签、历史、收藏(nav_links 待做 UI
- 移除或隐藏:「新标签打开」可保留作 **导出 URL** 辅助,非主路径
- `useGlobalBrowser.ts``navigate` 改为 WS `NAVIGATE``frameKey` 不再需要
## 安全与隐私
- 页面内容经 JPEG 帧过 Nexus,**不持久化**截图
- 服务器 Chromium 内 Cookie 存于容器 ephemeral context(重启会话丢失)
-`get_current_admin` 可创建/连接会话
- CUD:创建/销毁会话可选写 audit(首版 **不做**,与 RDP 无连接审计一致)
## 验收标准
- [ ] 打开 `https://example.com` 等公网站,面板 canvas 有画面且可点击链接
- [ ] 曾 iframe 白屏的站点(如带 `X-Frame-Options: DENY` 的站)可正常显示
- [ ] 访问 `http://127.0.0.1` / `http://10.x` / metadata → 明确 ERROR,不发起请求
- [ ] 刷新页面后会话内 Cookie 保留;重启浏览器会话 Cookie 清空
- [ ] 标签、收藏、窗口位置仍持久化到 `/api/browser/state`
- [ ] `NEXUS_BROWSER_ENABLED=0` 时有可读降级提示
- [ ] Worker VPS 部署完成,防火墙仅 Nexus 主站可连
## 回滚
-`NEXUS_BROWSER_ENABLED=0` → 503,前端可回退 iframe 分支(保留 feature flag 一段过渡期)
- 卸载 Playwright 依赖仅当确认不再使用