跳到主要内容

dsh-remote-access

已验证

@greenonion/dsh-remote-access · v0.2.0 · MIT · Web 界面

DSH web plugin for LAN, Tailscale, and Cloudflare Tunnel remote access through Caddy with API access policies.

安装

dsh plugin add @greenonion/dsh-remote-access

dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南

源码

标签

作者

说明文档

dsh-remote-access

DSH Web 插件,为本地 DSH 提供局域网访问、Tailscale 私有远程入口和可选 Cloudflare Tunnel 公网入口。Tunnel-only 是匿名公网传输,不提供用户身份认证。

设计

局域网客户端 ── HTTPS:3081 ──► Caddy ──┬─ 所有页面/API/WS ─► API Gateway:3083 ─► DSH:3080
                                      └─ /ca.crt(兼容下载路径)

Tailscale ── Serve/Funnel ──► Caddy:3082 ──┬─ 所有页面/API/WS ─► API Gateway:3083 ─► DSH:3080
                                           └─ 无身份/无凭据请求拒绝

cloudflared ── Tunnel ──► CloudflareIngress:127.0.0.1:3084
                                  └─ Host/Origin (+ optional JWT) ─► API Gateway:3083 ─► DSH:3080

DSH 原始服务只监听 127.0.0.1:3080。远程流量不得绕过 API Gateway;网关统一处理 Host、Origin、WebSocket、API 策略和上游 loopback 规范化。配置、证书和运行状态保存在 DSH 数据目录。

访问模式

模式 入口 认证 API 策略
本机 127.0.0.1:3080 DSH 自身 完整权限
LAN trusted-lan https://<LAN_IP>:3081 用户明确选择无认证 lan
LAN protected-lan https://<LAN_IP>:3081 Basic Auth lan
Tailscale Serve Tailscale 域名 tailnet policy,可选 Basic Auth serve
Tailscale Funnel 公网 Tailscale 域名 强制 Basic Auth funnel,仅基础 API
Cloudflare Tunnel 外部 cloudflared → 插件 loopback 适配器 推荐 Tunnel Basic Auth;也可显式匿名或可选 Access cloudflare

LAN、Serve、Funnel 各自使用 API 白名单,默认只开放会话、消息、模型列表、技能列表等基础功能。设置、凭据、主机文件和模型探测等接口默认关闭。API 方法白名单不是工具能力沙箱,获得 session.prompt 的远程主体仍应视为拥有完整 DSH 控制权。LAN 可通过 lanAllowedCidrs 或本机“配置来源网段”操作进一步限制来源网段;配置了该字段后,Caddy 会拒绝不在列表中的来源,包括 /ca.crt。留空表示不增加来源 CIDR 限制,仍由绑定地址、系统防火墙和网络边界负责隔离。

设置页可以按访问模式勾选 API、配置 Basic Auth,并为 LAN / Serve 开启“允许全部 API”或“可信远程设置”;Cloudflare Tunnel Basic Auth 也在“访问控制”中单独开关。两项高权限能力不适用于 Funnel;插件自身的写操作只接受精确的本机 Host + Origin 请求,不再使用注入到页面中的 action token。状态 GET 在没有 Origin 时也只接受精确 loopback Host,以兼容浏览器同源轮询。

设置页为 Caddy 开放的每个监听端口提供独立开关:LAN HTTPS 端口与 Tailscale loopback 端口可单独关闭,Cloudflare 适配器端口也单独控制。关闭端口会从生成的 Caddyfile 中移除对应 site(或停止 Cloudflare loopback 适配器),并由 Caddy --watch 热应用,不需要停止整个反代进程;关闭最后一个 Caddy 端口时插件才会停止 Caddy。端口开关保存在 v2 状态中,重启后仍保持关闭,避免“忘关”。 风险操作(关闭 Basic Auth、切换匿名 Tunnel、开放匿名端口、重置共享凭据、保存高权限 API 策略)会先弹出确认/取消确认框;未配置访问凭据时开启 Basic Auth 会弹出只读提示并引导生成凭据。非风险操作不弹成功提示,失败仍会用窄弹窗提示。刷新按钮位于页面右上角,更新时间显示在刷新按钮左侧。提示/确认弹窗使用更高层级,不会被 API 白名单等二级窗口遮挡。访问控制卡也有凭据状态条,可直接看到共享凭据、Tunnel 凭据和 Funnel 保护是否已配置。页面左上角不再显示操作小字。 每种访问方式下方有状态条:绿点表示运行/可达,红点表示异常或配置冲突,灰点表示未运行/未启用。访问地址旁提供二维码图标;二维码由客户端内置编码器生成 SVG,不会把地址发送到外部服务。

安装

dsh plugin --profile web add @greenonion/dsh-remote-access

手动安装:

git clone <repo-url> dsh-remote-access
cd dsh-remote-access
node install.js

安装后重启 DSH,在“设置 → 远程访问”中操作 Caddy、LAN 策略和 API 策略。Tailscale 登录、Serve/Funnel 和自启动由系统客户端管理,插件只读检测状态并展示命令,不执行 tailscale up/down。设置页会额外显示 Serve 路由是否未配置、已指向插件 loopback 入口、指向其他目标或暂时无法确认;发现外部路由时不会覆盖它。Cloudflare Tunnel 卡片只控制插件自己的 loopback 适配器;Tunnel、DNS、Access Application 和策略仍由 cloudflared/Cloudflare 控制。 LAN TLS 默认使用 lanTlsMode: auto:能找到 OpenSSL 时保持旧版本地 CA 兼容路径;Windows 找不到 openssl.exe 时自动改用 Caddy tls internal,不会再因为 spawnSync openssl ENOENT 阻止启动。也可显式指定 openssl(兼容/严格模式)或 caddy-internal(完全不依赖 OpenSSL)。

Cloudflare Tunnel loopback 适配器

Tunnel 和 Access 是两套独立能力。本项目的公网访问主路径是 Cloudflare Tunnel;适配器默认关闭,启用时需要配置 HTTPS externalOrigins,并让外部 cloudflared 的 ingress 只指向 http://127.0.0.1:3084。推荐在设置页“访问控制”打开 Tunnel Basic Auth;对应配置为 cloudflare-tunnel-basic

cloudflare:
  enabled: true
  authMode: cloudflare-tunnel-basic
  listenHost: 127.0.0.1
  listenPort: 3084
  externalOrigins:
    - https://dsh.example.com
  upstreamTimeoutMs: 120000
  upstreamConnectTimeoutMs: 10000

cloudflare-tunnel-basic 会复用“访问控制”中生成的 Basic Auth 凭据,同时保护 HTTP 和事件 WebSocket;密码只在生成响应中显示一次。cloudflare-tunnel 是显式匿名模式:请求以无用户身份 principal 进入插件,API 被网关强制限制为只读集合(agentPreset.listhost.describellm.modelssession.list 等),事件流、allApis 和可信远程设置均不可用。需要更多接口时必须先开启 Tunnel Basic Auth 或改用 Access。

已有 Basic Auth 只保存 Caddy bcrypt 时,首次打开 Tunnel Basic Auth 需要重新点击一次“生成 / 重置访问凭据”,以创建本地校验值;重置会同时轮换 LAN、Serve、Funnel 和 Tunnel Basic Auth 使用的共享凭据。

首次配置可写入 profile config;已有 v2 状态中保存过 Cloudflare 选项后,状态文件优先,需从本机设置页/setCloudflare action 修改。仅有默认的空 disabled 状态不会覆盖 profile 中的显式 Cloudflare 配置。

设置页显示的 Cloudflare 地址来自 cloudflare.externalOrigins,不是从 cloudflared 自动发现的公网 hostname。请把这里配置成 Cloudflare/cloudflared 实际暴露的 HTTPS origin;本机设置页可以点击“配置公网地址”修改,每行一个 origin。保存后会更新 Host/Origin 白名单;若适配器正在运行会自动重启适配器。远程页面不会显示这些 origin。

插件不启动或停止 cloudflared,也不写入 Tunnel/DNS/Access 配置。Tunnel Basic Auth 模式先验证本地 Basic Auth,再通过 HTTPS externalOrigins、Host/Origin、请求体大小和上游超时边界进入 API Gateway;匿名模式不验证 Access JWT。上游 HTTP 无活动超时可配置为 100–600000 毫秒(默认 120 秒),WebSocket 连接握手超时可配置为 100–120000 毫秒(默认 10 秒),超时会 fail closed。适配器只监听 loopback,外部公网暴露、域名和 Cloudflare policy 由用户自行决定。

如需使用 Access,可将 authMode 改为 cloudflare-access,再配置 HTTPS issuer 与 Access audience;该模式保留用于兼容和纵深防御,但不属于 Tunnel-only 的默认验收范围。

外部 E2E 只读诊断

安装包附带 scripts/edge-diagnostics.mjs。它只执行 HTTPS GET、tailscale statustailscale serve statustailscale funnel status,不会登录、创建、修改或停止任何 Tunnel/Serve/Funnel。Tunnel-only 探测不需要 JWT;只有选择可选 Access 模式时才通过环境变量传入短期 JWT,结果会去除 query、响应体、Serve target 和凭据:

$env:DSH_REMOTE_URL = 'https://dsh.example.com/api/session.list'
node scripts/edge-diagnostics.mjs --tailscale --json

只检查本机 Tailscale 状态时无需公网 URL:

node scripts/edge-diagnostics.mjs --tailscale --json

诊断结果会区分 edge-login-requirededge-challengeorigin-auth-requiredorigin-unavailableorigin-ok 等状态。没有测试账号或公网入口时,也可以只运行 --tailscale,不会产生外部副作用。

公网链路较慢时可用 --timeout-ms 10000 调整单次探测超时(范围 100–30000 毫秒);超出范围会直接拒绝,不会发起请求。

运行环境

  • Node.js >=20
  • Caddy 2.8+
  • OpenSSL(可选;lanTlsMode: auto 未找到时使用 Caddy internal CA,显式 openssl 模式才要求它;Windows 可通过 OPENSSL_BIN 指定 openssl.exe 的绝对路径)
  • Tailscale(使用 Serve / Funnel 时需要)

核心功能支持 Linux、macOS 和 Windows;证书安装提示使用 mDNS 检测,在 Linux 和 macOS 上启用。默认端口为 LAN 3081、Tailscale 3082、API Gateway 3083、Cloudflare loopback 3084,可在 cordis.patch.yml 中调整。

如果新版 DSH profile 同时启用了 dsh-pocket,它默认也会监听 3081。插件不会停止或覆盖该 listener;启动前会检测到端口冲突并 fail closed,在页面和 action 响应中提示用户为其中一个组件选择空闲端口。测试/迁移时可把 dsh-pocket 改到例如 3091,再让本插件继续使用 3081

运行时目录默认为 ~/.dsh/dsh-remote-access/,可用 DSH_HOME 更改,主要包含动态生成的 Caddyfile、证书、认证信息和运行日志。POSIX 使用 0700/0600;Windows 会关闭继承并用当前用户 SID + SYSTEM 的显式 DACL 保护目录、配置、认证信息和私钥。Caddy internal CA 的根证书路径由插件独立的 XDG_DATA_HOME 管理,不会读取用户全局 Caddy 数据。

LAN TLS 选择

lanTlsMode 可在 cordis.patch.yml 的插件配置中设置:

  • auto(默认):探测 OpenSSL;探测失败时回退 caddy-internal
  • openssl:只使用旧版本地 CA 生成器;缺少 OpenSSL 时返回可操作的 OPENSSL_BIN 诊断。
  • caddy-internal:由 Caddy 生成本机 CA 和叶证书,不调用 OpenSSL。Caddy 可能在首次启动时将本机根证书安装到当前 Windows 用户的信任存储;如果不希望有这个系统级副作用,请使用 openssl 模式并手动安装 /ca.crt

安全边界

  • 远程请求先经过 Caddy 和 API Gateway,未知 API 默认拒绝;页面、API 和 WebSocket 不再旁路到 DSH,本机 loopback 保留 DSH 完整权限。

  • API Gateway 的 loopback hop 使用每次进程启动生成的未持久化共享密钥;仅伪造 X-DSH-Access-Mode 的本机请求不会被当作受信入口,密钥不会出现在状态、日志或 DSH 上游。

  • 网关转发前会移除客户端提供的 ForwardedX-Forwarded-*CF-*Tailscale-*X-DSH-*Authorization 身份头,并重写上游 Host/Origin。

  • Cloudflare 适配器在网关之前完成 Tunnel Host/Origin 边界;Tunnel Basic Auth 请求先验证插件凭据,匿名 Tunnel 请求使用无用户身份的 transport principal,Access 模式额外验证 JWT。三种模式都只把受控 mode/subject 摘要放在适配器到网关的 loopback hop,JWT、CF-* 和客户端身份头不会到达 DSH。

  • LAN trusted-lan 可以不启用 Basic Auth,但同网段设备拥有完整控制权;公网入口默认应使用强认证。若显式选择 Cloudflare Tunnel-only,则它是匿名公网传输,必须依赖 API allowlist 和外部网络策略,不能视为身份认证。

  • Tailscale 生命周期由用户和系统客户端管理;插件不关闭用户已有的 Tailscale 连接或修改全局 Serve 配置。

  • Caddy 使用插件独立的配置和 PID;关闭时只停止插件创建或确认属于插件的 Caddy 资源。

  • Caddy 以 --watch 运行,只监视插件私有目录中经过校验、原子替换的 Caddyfile;不开放 Caddy admin API。端口开关通过重写 Caddyfile 由 Caddy 热应用。

  • Caddy 仍只保存 bcrypt hash;Tunnel Basic Auth 另保存不可逆的 scrypt 派生校验值,不保存明文密码。/ca.crt 用于设备安装本地 CA,保持免认证访问。

开发状态

已完成第一批安全基础:

  • 删除页面 action token。

  • 管理接口增加精确 loopback Host/Origin 校验。

  • API POST 和 WebSocket 增加跨站 Origin 拒绝。

  • 入口身份头不会转发到 DSH。

  • v2 网关为 LAN、Tailscale、Funnel 和 Cloudflare 入口生成统一的进程内 Principal;身份上下文不进入 DSH 上游、状态文件或日志。

  • LAN ingress adapter 只允许私有/链路本地地址,拒绝公网或通配绑定;显式 CIDR 限制由 Caddy 在入口处执行。

  • 页面/API/WS 统一经过 API Gateway。

  • LAN 配置迁移为 trusted-lan / protected-lan

  • Cloudflare Tunnel loopback 适配器已接入:固定 loopback 监听、Tunnel Basic Auth/显式匿名 Tunnel/可选 Access JWT 模式、Origin allowlist、HTTP/WS 代理、请求体限制和远程状态脱敏;Tunnel/DNS/Access 生命周期仍由外部 cloudflared 管理。

  • Cloudflare 适配器的上游 HTTP/WS 超时已经进入 profile/state/status 模型,设置页会显示当前值;非法值会回退到安全默认值,运行中的适配器配置变更会先停止旧 listener 再应用。

  • 匿名 Cloudflare Tunnel 的 API 被网关强制为只读集合;事件流、allApis 和可信远程设置必须启用 Tunnel Basic Auth 或 Access 后才可用。设置页风险操作统一改为确认弹窗。

  • 兼容新版 DSH client-modules:客户端 bundle 使用 npm 包名 @greenonion/dsh-remote-access 作为 ModuleLoader 注册 ID。

v2 已开始落地:state-store.js 负责 schema v2 入口状态、0.1.x 平面状态迁移、迁移前备份以及临时文件 + fsync + 原子替换;运行时已接入该状态层,识别到旧状态后会自动备份并转换,非法 JSON 不会被覆盖。当前切片不改变现有监听器拓扑,Tailscale/Cloudflare 状态只保留配置且不会被旧运行时自动启用。

正在开发:真实 tailnet/Cloudflare 外部入口和 cloudflared 故障恢复验证。Windows ACL、Caddy internal 无 OpenSSL LAN 路径、Tailscale 只读入口适配器、Cloudflare loopback 适配器已接入运行时状态和设置页;本地 Tunnel-only HTTP/WSS 与 RSA JWT → Cloudflare verifier → adapter → API Gateway 链路已覆盖。当前测试套件在受支持环境中全部通过。

License

MIT