客户机在 Windows、开发机在 Mac,现场排障需要上机。RDP 和商业远控工具都太重——要客户配合打开、有安装门槛、按并发收费。我用 SSH 隧道解决这个问题,核心思路是客户运行一个通用 BAT(自动获取连接码),通过 VPS 反向隧道连我的 Mac,然后我用 ProxyJump 直连到他的 PowerShell。这套工具支持 10 个客户并发,用一次性 ed25519 证书做身份认证,OpenSSH 缓存把首次安装时间从 30-60 秒压到 5-10 秒。
架构:VPS 中继 + 反向隧道 + ProxyJump
整个系统分三端。客户 Windows 上运行 BAT,通过 SSH 建立反向隧道到 VPS(隧道用户只有端口转发权限,无 shell 访问);VPS 负责分配连接码和证书,维护实时会话面板;我的 Mac 上配置好 SSH 别名,一条命令 ssh cust-<code> 就能跳转到客户机。
┌─────────┐ ┌─────────────────────────────────┐ ┌──────────────┐
│ Mac │ │ VPS Relay (中转站) │ │ Customer │
│ │ │ │ │ Windows │
│ ssh │──ProxyJump──►│ Web Panel (Node 20 + Vue 3) │◄── HTTPS ────│ Universal │
│cust-XXX │ │ ↓ /api/allocate (cert) │ │ BAT │
│ │ │ ↓ /api/sessions (kill/list) │ │ │
│ │ │ ↓ /ws (realtime push) │ │ │
│ │ │ sshd reverse port pool │ │ │
│ │ │ 12100~12199 (cert-only auth) │◄ ssh -R 12X ─│ │
│ │ │ nft per-port counters → traffic │ │ │
│ │ │ SQLite (sessions+audit+samples) │ │ │
└─────────┘ └─────────────────────────────────┘ └──────────────┘
流程是这样的:
- 客户双击
远程调试-连接.bat - BAT 生成一次性 SSH 密钥对,调用
POST https://中转站/api/allocate - 服务器返回三位连接码(例如 147)、对应端口(12147)、签发的证书、工程师公钥、VPS 主机公钥
- BAT 自动安装或启动 OpenSSH Server,把工程师公钥写入
authorized_keys - BAT 用证书建立反向隧道:
ssh -i key -o CertificateFile=cert -R 12147:127.0.0.1:22 tunnel@中转站 - BAT 显示连接码给客户("您的连接码: 147")
- 客户告诉支持这个数字,我在 Mac 上运行
ssh cust-<code> - 连接实时出现在 Web 面板上(代码、IP、流量、时长)
- 客户关闭 BAT 窗口或我在面板点击"关闭",隧道断开
设计点一:动态连接码 + 端口池,支持 10 并发
三位数连接码(100199)是为了让客户容易记住和口头传达。服务器维护一个端口池 1210012199,每次客户请求时随机分配,确保 10 个客户可以并发连接而不冲突。
请求 /api/allocate 时,服务器:
- 检查当前空闲端口
- 返回 code(100~199 之间随机)和对应的 port(12100+code)
- 这个分配关系存进 SQLite,关联客户 IP、时间戳、隧道建立状态
如果 10 个端口都满,新客户会得到 HTTP 503 错误和"pool_full"错误码,这是硬限制的立即拒绝。
设计点二:一次性 ed25519 证书,5 分钟有效且绑定源 IP
传统做法是给每个客户一个长期 SSH 密钥,密钥泄露风险大。这里改成 OpenSSH 证书认证:
- 客户 BAT 生成临时 ed25519 密钥对(仅在 BAT 运行时内存中存在)
- 客户把公钥和源 IP 发给服务器的
/api/allocate接口 - 服务器签发一张证书:标明这个公钥只在这次连接有效、5 分钟过期、源 IP 绑定
- BAT 用证书建立隧道
OpenSSH 证书的威胁模型是:
- 能保护的:密钥长期泄露风险消除了(每次用完即弃);隧道被劫持后,拦截者只能在 5 分钟内、同一源 IP 范围内冒充这个客户,其他时间段无法复用
- 不能保护的:如果源 IP 被欺骗(ARP 欺骗、BGP 劫持)、或 VPS 时钟不准(偏差超过 5 分钟),证书校验可能失败或被绕过;BAT 文件本身被篡改(在传输或客户机上被修改),工程师公钥被替换,我连接时会连到恶意机器而不是客户机
证书的有效期使用 OpenSSH ssh-keygen -V +300s 格式,依赖客户机和服务器的系统时钟一致。如果两端时钟相差超过几分钟,OpenSSH 会因时间戳不在有效范围内而拒绝证书。系统没有显式的时钟容差机制,NTP 同步精度需要控制在分钟级以内,IPv6 或 VPN 环境下源 IP 绑定的有效性 待补:取决于 VPN 方案是否保留客户真实 IP。
设计点三:隧道账户仅端口转发,无 shell 权限
VPS 上创建一个专用账户 tunnel,SSH 配置中限制它:
# sshd_config 关键配置
Match User tunnel
AllowAgentForwarding no
AllowTcpForwarding yes
PermitListen 127.0.0.1:12100-12199
PermitOpen 127.0.0.1:12100-12199
PermitTTY no
AllowUsers tunnel
ForceCommand /bin/false
这样即使客户的密钥(或我的长期密钥)泄露,攻击者登陆 tunnel 账户最多只能建立反向端口转映射,无法获得 shell、无法读写文件系统、无法执行命令。双重保护:一是账户本身没有 shell,二是 sshd 配置显式禁止了 TTY 和代理转发。
这里的威胁模型假设:VPS 的 sshd 配置被信任,未被篡改;客户 Windows 机器上没有被植入远控木马(BAT 文件下载、执行、隧道运行的全过程都可能被监控)。如果客户机本身被入侵,这套机制无法保护 VPS 或我的 Mac。
设计点四:OpenSSH 预缓存,5-10 秒装好而不是 30-60 秒
Windows 内置的 OpenSSH Server 需要从 Windows Update 下载,通常要 30-60 秒。方案是在 VPS 上预先缓存一份 OpenSSH-Win64.zip(~50MB),BAT 运行时优先从本地 VPS 的 HTTP 服务(18022 端口)下载,失败才降级到 Windows Update。
VPS 侧的一次性准备:
sudo bash server/prepare-openssh-cache.sh
这个脚本:
- 从 GitHub PowerShell/Win32-OpenSSH releases 官方源下载 OpenSSH-Win64.zip
- 启动 HTTP 服务在 18022 端口,提供这个 ZIP
- 不做显式的哈希校验,而是依赖 Windows 对 ZIP 文件内签名的验证(Windows 的
Add-WindowsCapability内置验证机制)
客户 BAT 的逻辑:
- 尝试从 VPS
http://中转站:18022/OpenSSH-Win64.zip下载(网络好时 5-10 秒) - 下载失败或超时则降级到 Windows Update(30-60 秒)
- 无论哪种方式都自动继续,用户不需要干预
这个优化需要几个前提条件生效:
- VPS 和客户机的网络连接要稳定(下载中断会回源 Windows Update,反而更慢)
- VPS 和客户机在同一地域最佳(跨域 50MB 可能需要 20-30 秒,降级意义不大)
- HTTP 18022 端口要对客户机开放(如果防火墙限制只有 22/443,这个缓存无法使用)
素材里给了性能表:
| 场景 | 时间 (v1.0) | 时间 (v1.1) | 加速 |
|---|---|---|---|
| 首次安装(网络好) | 30-60s | 5-10s | 3-10x |
| 首次安装(网络慢) | 2-3 min | 30-60s | 2-5x |
| 已安装 | 0.5s | 0.5s | 相同 |
测量条件 待补:网络"好"和"慢"的具体定义、客户端机型、测试样本数量。现有数据支撑的结论是:新客户首次连接,这个优化能缩短 60-75% 的等待时间(从 60s 到 10s 这个最好情况);但不能保证 100% 稳定达成(取决于网络波动)。
Web 管理面板:实时掌握会话状态
VPS 上跑的 Node 20 + Vue 3 前端,可以访问 https://中转站(配置 Let's Encrypt 证书)。面板功能:
- 实时会话列表:显示当前在线客户的代码、IP、连接时长、流量统计
- 强制断开:点击"关闭"按钮立即切断隧道
- 历史记录:已结束的所有会话,记录开始/结束时间、流量、断开原因(主动关闭 vs 网络中断 vs 空闲超时)
- 审计日志:Web 面板登陆尝试、谁强制断开了谁、异常操作
数据存储在 VPS 的 SQLite 里,包括 sessions 表(会话元数据:代码、端口、客户 IP、开始/结束时间、流量总计)、audit 表(操作日志)、traffic_samples 表(按时间采样的流量数据:会话 ID、采样时刻、接收字节、发送字节)。
流量统计通过 nft(Linux 内核防火墙规则)的计数器实现,每个端口 12100~12199 对应一个计数规则,实时汇总给 Web 后端。
登陆认证是用户名 + 密码(在 install-panel.sh 时设置)。
部署:4 个步骤完成全套安装
第一步:生成隧道密钥(Mac 侧)
ssh-keygen -t ed25519 -f keys/customer_tunnel_key -N "" -C "customer-tunnel"
第二步:配置 VPS 隧道和 sshd
ssh root@中转站
git clone <仓库地址>
cd sshts
sudo bash server/setup-vps.sh keys/customer_tunnel_key.pub
这会创建 tunnel 账户、配置 sshd、启用反向隧道、写入 authorized_keys。
第三步:安装 Web 面板(v1.2+)
sudo DOMAIN=中转站 bash server/install-panel.sh
脚本会提示输入:
- 工程师 SSH 公钥(会被嵌入所有 BAT)
- Web 面板管理员用户名和密码
然后自动安装 Node 20、编译 Vue 3 前端、配置 systemd 服务、Nginx + Let's Encrypt。
第四步:配置 Mac 的 SSH 别名(每个工程师一次)
WIN_USER=Administrator bash mac/install-ssh-config.sh
这会在 ~/.ssh/config 里添加 100 条记录:cust-100 到 cust-199,每个都用 ProxyJump=中转站,自动登陆指定的 Windows 管理员。
之后,把 client/远程调试-连接.bat 发给客户(通用的,不需要填任何参数),客户一运行就能连接。
限制和取舍
- 支持 10 并发:这是硬限制,对应 VPS 单机性能(待补:性能上限如何测试、是否会因为某个客户的高流量拖累其他客户)。
- 仅支持 Windows 10 1809+ 或 Windows 11:早期 Windows 版本的 OpenSSH 行为不兼容,素材里明确要求 Administrator 账户。
- 需要 Administrator 权限:BAT 要写 SSH 密钥到系统目录、启动 sshd 服务,非 Admin 用户无法运行。BAT 会自动检测管理员账户,优先当前用户,降级到 Administrator(需要 UAC 弹窗确认)。
- 证书有效期 5 分钟:这是为了限制泄露风险,代价是客户机上的系统时钟如果跟服务器相差超过几分钟,连接会失败。系统依赖两端时钟同步,无显式容差机制。
- Web 面板仅支持单一 admin 账户:系统只有一个管理员用户,通过用户名+密码认证。所有认证用户都拥有完整权限(查看所有会话、断开任何连接、查看审计日志),不支持按角色分配权限或只读账户。
关键点
这套工具解决的是客户排障的第一步——快速、轻量地建立远程访问,而不需要客户安装 TeamViewer、配置 RDP、开放额外端口。支持 10 并发、一次性证书,OpenSSH 缓存把安装时间从 30-60 秒压到 5-10 秒,意味着哪怕客户现场网络不稳定、客户 IT 管理严格,也能以最小摩擦快速上机取证。没有这套工具,那些现场的故障根本无法追踪——光是等待客户开 RDP 就要 10 分钟,OpenSSH 缓存的优化才让 5-10 秒的快速连接成为现实。
■