一位朋友想要校对您未完成的博客文章,但其预览仅运行在 localhost:8080。现有若干工具可提供帮助:部分作为商业服务运行,例如 ngrok(ngrok)或 Cloudflare 快速隧道(Cloudflare Quick Tunnels);部分支持自托管但需专用客户端,例如快速反向代理(frp)或本地隧道(localtunnel);部分仅需标准 SSH 客户端但依赖特定 SSH 服务端,例如 sish(sish)。接下来将仅借助 OpenSSH(OpenSSH)与 nginx(nginx)实现自托管方案。
正文
朋友想看你在 localhost:8080 上运行的博客草稿预览。这类工具不少:ngrok 和 Cloudflare Quick Tunnels 是商业服务;frp 和 localtunnel 可自托管但需专用客户端;sish 只需普通 SSH 客户端但依赖特定 SSH 服务端。我们仅用 OpenSSH 和 nginx 来实现自托管方案。
基本配置
先把本地服务转发到远程服务器:执行 ssh -N -R 0:localhost:8080 web02.luffy.cx,远端端口写 0 时,服务器会分配空闲端口(如 41535)。然后配置 nginx,将 https://p41535.ssh.luffy.cx 的请求代理到 http://127.0.0.1:41535。配置中 servername 用正则 ^p(?
访问控制
端口号是保密内容的唯一手段。其他方案在域名加随机串防枚举。借助 nginx 的 ngxhttpsecurelinkmodule 可稍作加固:它对含密钥的值算哈希并比对请求中的哈希。因域名大小写不敏感不能放 base64 哈希,便将其作 URL 用户名并附过期时间戳,如 https://6J3jK1WmB15c6WmjWX-Wg--1789928654@p41535.ssh.luffy.cx/。客户端用 HTTP Basic 认证发用户名,nginx 在 $remoteuser 取到,用 map 指令提取哈希与时间戳交模块验证。模块返回状态:空为不匹配,"0" 为过期,"1" 为正常。哈希错或缺失返 401 并带 WWW-Authenticate 头,过期返 410;通过则代理,清掉 Authorization 头并加 WebSocket 代理指令。
生成哈希很简单:对“过期时间 端口 密钥”用 openssl md5 再 base64 并替换字符即可。不过手动算不方便,我们写个辅助脚本。
辅助脚本
主要难点是 OpenSSH 分配的临时端口不在环境变量里。脚本遍历祖先进程找 sshd-session 的 pid,再用 sudo ss 查其监听端口。拿到端口后算令牌,输出 https://令牌--过期时间@p端口.ssh.luffy.cx/ 这样的 URL,然后 sleep infinity 保持会话。把脚本装为 http-over-ssh,在 ~/.ssh/config 加 Host 配置设 RemoteCommand。这样只依赖 OpenSSH 和 nginx,一条命令得自托管隧道和分享 URL。完整脚本与 NixOS 的 http-over-ssh.nix 见作者仓库。
评论
社区对自托管文件共享应依赖隧道还是直接暴露服务存在分歧,多数人倾向简单家用服务器或证书访问控制,少数人呼吁去第三方中介隧道并警示安全风险与威胁模型。
认为多数场景直接暴露家庭服务器或简单证书控制就够了
- 长期实践者指出端口映射加静态 nginx 几十年来可靠,隧道对普通人过度复杂(较多人) > 就在家 IPv4 上跑普通服务器,没什么会坏掉(@superkuh)
- 另有人建议用 nginx 加客户端证书做访问限制,比隧道更轻(少数人)
- 亦有声音希望出现更易用的直接公开部署方案,而非折腾隧道(少数人)
认为自托管隧道应力求去第三方中介
- 有开发者批评 SaaS 隧道模糊自托管概念,正打造完全无中介方案(少数人) > 理想是能完全不依赖中间人运行(@aliasxneo)
- 也有利用公共中继信令后直连的思路,免除端口转发与公网 IP(少数人)
- 同时有人质疑若自托管方案仍调用第三方子域名则失去意义(少数人)
质疑文中方案的安全风险与自托管暴露面
- 具体配置被指可致本地端口被外部探测绕过防火墙,属危险陷阱(较多人) > 文章首段 nginx 配置能让攻击者循环扫描本机所有监听端口(@jamiesonbecker)
- 同时有人提醒无代理自托管会暴露端点,且需考虑 URL 泄露、隧道枚举等威胁模型(少数人)
推荐既有开源项目实现安全隧道
- 已有多年开源 SSH 隧道服务支持自动 TLS 与多协议,亦有托管版(少数人)
- 基于 iroh 的代理可实现无公网 IP 直连加密(少数人)
有价值的补充
- iroh 通过公共中继完成信令后两机互相端口敲门建立直连加密,无需特殊代理(@toomim)
- sish 以 SSH 认证提供无状态隧道、请求控制台及 UDP 转发等(@antoniomika)
- 客户端证书鉴权在部分手机浏览器支持不佳,是落地短板(@gonzalohm)