- compose: 去掉 control 网络的 internal 标记(internal 网络上 Docker 会静默丢弃端口发布,导致局域网无法访问) - docker-gateway: 浏览器网络改为普通 bridge,移除 internal+禁 ICC - AGENTS.md: 新增开发阶段原则(仅业务功能、不做访问限制、安全自管),删除输入信任/风控/敏感日志约束 - docs/README: 同步移除 localhost-only 与网络隔离声明 - 端口改为 8082(8080 被 multica-backend-1 占用)
4.6 KiB
浏览器容器控制面
技术选型
- 前端:React + Vite。阶段 A 只有一个运行环境页面,不引入路由、状态库或组件库。
- 后端:Go 模块化单体,Fiber v3 提供 HTTP 路由,Viper 读取并校验启动配置,Logrus 输出 JSON 结构化日志,Cobra 保持当前两个服务入口。控制面提供同源 API 和静态文件,受限网关单独封装 Docker Engine API。
- 数据:Docker 容器标签仍是运行态事实,Profile 使用命名卷持久化;阶段 A 账号、凭据引用、确认、任务、尝试和审计实体由控制面使用原生 SQL 持久化到 Postgres。
- 部署:Docker Compose 启动控制面和受限网关;浏览器容器由网关动态创建。
调用链与契约
React ──> control-plane ── /api/browsers ──> docker-gateway ──> docker.sock
│ │
│ └─> browser container
└─ /api/phase-a ──> PostgreSQL
POST /api/browsers 接受 {name, seed} 并创建后启动;GET /api/browsers 列出受管容器;POST /api/browsers/{name}/start|stop 改变状态;DELETE /api/browsers/{name} 仅回收容器,保留 Profile 数据卷。名称唯一约束由 Docker 容器名保证,重复创建返回冲突;启停接受 Docker 的幂等响应,不自动重试未知结果。
docker.sock 安全边界
将 socket 以只读文件挂载不会限制 Docker API 的写操作;拥有 socket 等价于拥有宿主机 root 权限。因此:
- 只有
docker-gateway挂载 socket,控制面和浏览器容器均不可见;网关只加入 control 网络,浏览器不能连接网关; - 网关只暴露四个面向领域的路由,不提供通用 Docker 代理;
- 镜像、命令、网络、挂载和资源限制均由网关固定,外部输入只有受校验的名称和数字 seed;
- 启停和删除前必须同时匹配固定名称前缀及
io.creatorhub.managed、io.creatorhub.runtime-id标签; - 动态容器使用只读根文件系统、非 root 镜像、全部 capability drop、
no-new-privileges、CPU/内存/PID 限制,且无宿主机端口和目录挂载; - 控制面发布到宿主机所有网卡;控制网络为固定名称的 Compose 网络;浏览器 bridge 按 ownership、role、driver、Internal 失败关闭校验,且拒绝复用 control 网络;
- 浏览器镜像固定为审核过的 immutable digest;运行时创建请求同时固定非 root
1000:1000与镜像入口,构建阶段和最终基础镜像也均锁定 digest。
网关自身一旦被攻破,socket 仍允许接管宿主机;应用内校验不能消除这个平台级风险。开发阶段控制面不做认证或访问限制,安全由部署者自行把控。
运行
Docker socket 的 GID 因宿主机而异:
docker pull git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7
DOCKER_GID=$(stat -c %g /var/run/docker.sock) docker compose up --build
打开 http://127.0.0.1:8080,局域网内用宿主机 IP 访问同一端口。浏览器镜像固定为 git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7。浏览器容器可访问外网。
创建成功但启动失败时,网关会立即删除失败容器并保留命名 Profile 卷,允许同名请求安全重试。
阶段 A 离线闭环
POST /api/phase-a/accounts 只接受 OS Keyring/Secret Manager 的引用标识,不接受秘密值;账号的 profile_id 全局唯一。POST /api/phase-a/runtimes 通过部分唯一索引保证一个账号和一个运行时都只有一条活动绑定。
草稿经 POST /api/phase-a/confirmations 显式确认后才可投递到 /api/phase-a/tasks。任务由幂等键去重;POST /api/phase-a/mock/execute 使用 FOR UPDATE SKIP LOCKED 领取一分钟租约,执行前统一核对账号、草稿和确认版本。缺少确认或版本不一致会进入 needs_confirmation,暂停账号或 Mock 策略结果会进入 policy_hold,不确定结果与过期租约进入 needs_confirmation;这些状态都不会自动重试。GET /api/phase-a/audit 只导出账号、确认版本、尝试和结果等非秘密证据。
启动时控制面在事务和 advisory lock 下应用前向迁移 internal/phasea/migrations/001_phase_a.sql。本迁移只新建表、索引、约束和追加式审计触发器,不删除或改写现有数据;回滚需停服务后人工删除阶段 A 新表,本阶段不提供自动破坏性回滚。