一个 WebDAV 入口,两个后端
本文目录
一个域名已指向家庭 NAS 上的 WebDAV,经内网穿透隧道从公网可达,已有客户端在用;新需求是把另一份数据放到公网服务器上,同样以 WebDAV 提供。加子域要追加 DNS 与证书,改入口会打断已配置的客户端,目标因此定为:一个域名、一套证书,服务两台机器。
账号是唯一的区分维度
WebDAV 的身份只由 Authorization 头内的凭证判定,路径不参与识别,协议也没有任何位置可以表达”将请求转交另一个 WebDAV”。要在单一入口下区分两类使用者,可用的维度只剩登录所用的那个用户名,这不是实现偏好,而是协议留给方案的全部空间。
现成方案为何接不上
直觉是找一个能以 WebDAV 为存储后端的 WebDAV 服务端,但成熟实现——hacdias/webdav、SFTPGo 等——的后端只有本地文件系统、对象存储与 SFTP 三类,无一指向 WebDAV,它们擅长把一个存储映射给多个账号,而非把多个 WebDAV 合成一个。另一条路是让 rclone 以服务模式充当代理,可它要经过虚拟文件系统层,读写都经由缓存,LOCK 与 UNLOCK 这类依赖连接状态的语义无法保持。需求因此只能在 HTTP 层解决,由一层不改写语义的透传反向代理承担。
三层架构
宿主机 nginx 终止 TLS,把请求交给只做路由的反向代理,由后者依据用户名选择上游。
flowchart TB
C1["客户端 · NAS 侧账号"] --> N
C2["客户端 · 本地侧账号"] --> N
N["宿主机 nginx :443<br/>TLS 终止 · 证书自动续期"]
N -->|"回环地址"| R["分流器(OpenResty)<br/>只解析用户名 · 不校验口令"]
R -->|"用户名在 NAS 名单内"| P["frps 虚拟主机端口"]
P <-->|"反向隧道"| F["frpc · 家庭网络内"]
F --> NAS["GoWebDav<br/>NAS 内网端口"]
R -->|"其余用户名"| L["hacdias/webdav<br/>127.0.0.1 回环端口"]
L --> D[("服务器存储目录")]
分流器的职责被压到最小:解出用户名、选择上游,口令由两端各自的 WebDAV 服务判定,这一层不保存也不校验凭据。让它承担鉴权确能在入口挡住未知用户,代价是把凭据同步进一个纯路由组件,并在后端改密时多出一处易被遗忘的义务;不鉴权的代价,只是一个未知用户多产生一次注定失败的请求。
被反向代理放大的三处协议细节
真正消耗时间的不是分流逻辑,而是反向代理会把 WebDAV 中平时不被触碰的细节放大成故障。
Destination 头是其一。COPY 与 MOVE 的目标地址写在这个头里,两个后端对它的期望不一致:一侧要完整 URL,另一侧只收纯路径。改写因此无法统一,只能按目标后端分别处理,否则其中一侧的移动操作会直接失败,报错还通常不指向头部格式。
401 缺少 WWW-Authenticate 是其二。客户端收到不带该头的 401 时不弹认证框,直接报错;隧道另一端的服务恰好在”携带错误口令”时不返回它,需要代理按条件补齐,而它在未带凭据时反而会返回,所以补齐必须严格限定为”401 且该头缺失”,否则会覆盖后端自身的 realm,把本可正确提示的失败变成静默错误。
Host 头是其三。隧道服务依据 Host 匹配域名判定请求属于哪条隧道,一旦代理按常规做法把它改写成上游地址,请求就会被判为未知域名并返回 404,表面像后端不存在,实际是路由依据被换掉了。
分层反向代理中的安全边界
新服务刻意只监听回环,公网入口收敛到 443 一处。以此为前提去做反向检查,发现隧道服务自身的虚拟主机端口仍对公网开放:带上正确的 Host 头即可绕过 443 上的全部策略,直达被隧道暴露的内网服务,按账号分流在这条路径上完全失效。收回该端口后链路未受影响,因为 nginx 与隧道服务之间走的是回环。结论的适用范围超出本例:分层反向代理中,任何一层的内层端口若仍可从公网直达,上层策略就只是约定而非约束。
结语
这类需求本质上是在协议没有扩展点的地方补一层足够薄的 HTTP 代理。复杂度并未消失,只是从每个客户端的配置搬到了服务端的分层里,代价是协议间的细微差异从隐式默认变成了必须逐条显式处理的清单。