Tailscale Serve 与 Funnel:究竟谁能打开链接?
Serve 只在 tailnet 内共享服务,Funnel 则把它暴露到公网。比较两者的受众、身份请求头和 Funnel 允许的端口。
两条命令都通过一个 Tailscale HTTPS 名称发布本地服务,在运行它们的那台机器上看起来几乎一模一样。区别在于之后谁能访问这个 URL,而这个区别决定了你的应用还需要哪些控制措施。Serve 把受众留在 tailnet 内部,Funnel 则把受众搬到公网。先选受众,再选与之匹配的命令。
Serve 回答 tailnet 内部的问题
Serve 代理本地端口,或向 tailnet 内的设备提供本地内容,tailnet 的访问规则仍然适用于每个请求。端口 3000 上的面板会以一个 HTTPS 名称开放,只有你的设备能解析并连接它。对内部工具来说这是正确的默认做法,因为受众已经由 tailnet 认证,你并没有增加公网暴露面。这个 URL 看起来像普通网站,正因如此人们才把它当成普通网站。
Serve 还可以在转发的请求上附加描述 tailnet 用户身份的身份请求头。如果你的应用把这些请求头当作认证来信任,就把后端绑定到 localhost,让其他任何东西都无法直接访问它;监听在可路由地址上的后端可能收到从未经过代理的请求。
Funnel 把受众换成公网
Funnel 把选定的服务发布给任何拿到 URL 的人,访客不需要客户端、账号,也不需要是你 tailnet 的成员。因此这份资源需要你施加于任何公网服务的认证、限流和补丁。公网请求不携带 Serve 的 tailnet 身份请求头,所以依赖这些头的应用在 Funnel 下根本没有任何用户身份。换一条发布命令并不会替你回答认证问题。
Funnel 还有一些旧版教程可能不会提到的限制。它用 Tailscale 为该名称取得的证书终止 TLS,并且只支持特定的监听端口,如 Funnel 文档 所述。如果你的服务期望在这些端口之一上使用 HTTPS 以外的协议,那么 Funnel 就是错误的工具,而不是配置难题。
从客户端读取模式,而不是从端口
在分享任何东西之前先检查当前状态,因为两种模式可能在不同时间针对同一个本地端口配置。状态命令会报告实际发布了什么、对谁发布。不要因为 URL 有响应就推断受众。
tailscale serve status
tailscale funnel status
tailscale status --json | head -40
命令语法随客户端版本而变化,所以照抄的调用可能选中与你意图不同的模式。优先使用你所运行版本文档中给出的形式,并阅读客户端打印的确认信息,而不是自行假设。如果同时存在 Serve 和 Funnel 配置,对暴露而言重要的是公开的那一个。把你不想要的配置重置掉,而不是在它上面再叠一层。
如果输出没有说明受众,就停下来重新读状态。没有声明受众的已发布 URL 只是猜测,而猜测正是内部面板跑到公网上的方式。
用你真正想要的受众来测试
从 tailnet 外部测试,而不是从发布服务的那台机器。来自 tailnet 设备的请求只能证明代理能用,不能证明陌生人被排除在外。请使用没有 Tailscale 客户端、也没有通往 tailnet 路由的网络。
curl -sI https://host.tailnet-name.ts.net
curl -sI https://host.tailnet-name.ts.net --resolve host.tailnet-name.ts.net:443:203.0.113.10
对内部面板来说,外部请求应当连接失败或授权失败,内部请求则应当以你期望的身份成功。对公网服务来说,外部请求应当成功,应用应当要求它自己的凭据。记下你观察到的是这两种结果中的哪一种,因为单看 URL 并不能告诉你。如果结果与意图相反,先移除公开配置,然后再排查。
发布服务也与选择出口节点不是同一个决定,后者控制你的出站流量从哪里离开。Serve 和 Funnel 回答谁能访问你运行的东西;出口节点回答你自己的连接看起来从哪里来。把这两个问题分开,比较就会保持简单。