dsh-remote-access
Đã xác minh@greenonion/dsh-remote-access · v0.2.0 · MIT · Giao diện web
DSH web plugin for LAN, Tailscale, and Cloudflare Tunnel remote access through Caddy with API access policies.
Cài đặt
dsh plugin add @greenonion/dsh-remote-access Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Thẻ
Tác giả
Readme
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.list、host.describe、llm.models、session.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 status、tailscale serve status 和 tailscale 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-required、edge-challenge、origin-auth-required、origin-unavailable、origin-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 上游。网关转发前会移除客户端提供的
Forwarded、X-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