← 返回笔记

Cloudflare Tunnel 与 mTLS:给家里的 NAS 开一扇有锁的门

2026-09-16
NAS折腾CloudflaremTLS网络安全

我一直想在公网上访问家里的 NAS,但不想每次都先打开 Tailscale。

Tailscale 是好东西——它把手机和 NAS 放进同一个虚拟网络,手机拿到固定的 100.x 地址,改用流量也能连回家里。我之前那套跨网 ADB 方案就是靠它跑通的。但它有个前提:访问方得先装客户端、先登录、先连上。对我自己来说这不算什么,可要是想在朋友的电脑上临时看一眼,或者设备刚换还没配好,这一步就成了门槛。

所以这次我想试另一条路:Cloudflare Tunnel——不用开端口,不用公网 IP,浏览器直接打开域名就能进。

第一版:把隧道拉起来

Cloudflare Tunnel 的基本思路其实很朴素:在家里跑一个叫 cloudflared 的小程序,让它主动向 Cloudflare 建立一条出站长连接,然后把公网域名上的请求,从这条连接送到家里。

好处很直接:

飞牛应用商店里正好有个现成的 Tunnel 应用,装上、填账号、建隧道,十分钟就跑起来了。顺手把域名的 DNS 解析也从 DNSPod 迁到了 Cloudflare。

那一刻我确实有点得意:家里两个服务,现在都有公网域名了。

——然后我意识到一件事。

得意了大概十分钟

我习惯性地查了一下隧道到底把哪些东西挂上去了,结果是:

域名 实际指向
nas.huey314.online 飞牛 NAS 的管理后台
ai.huey314.online 我的 AI Agent 的 Web 界面

没有任何访问控制。

任何人知道这两个域名,就能看到飞牛后台的登录页,就能敲我 Agent 的控制台。这两样东西的分量我心里清楚:NAS 管理后台被拿下,等于全盘数据落在别人手里;Agent 控制台里是会话记录和工具权限。

装隧道的时候,我只想着"能访问了",完全没想"谁能访问"。

这是这次最该记住的一条:暴露面和访问控制应该同时上线,而不是先开门、事后再想装锁。 中间那段时间,它们就是裸着的。

安全方案:先做排除法

接下来是选型。我把能想到的路挨个试了一遍,过程比结论更有意思。

Cloudflare Access 是最顺手的选择,它本来干的就是这个。但我卡在第一步:创建 Zero Trust 组织时必须填付款信息。官方文档写得很直白——"A credit card is required for our user-limited Free Plan",选免费版也一样要填,只是不扣费。我不想为这件事绑定支付方式,排除。

免费版 Rate Limiting 看着挺美,实际窄得超出预期。官方可用性表里,免费版的限制是这样:

项目 免费版
规则可用字段 只有 Path、Verified Bot
计数特征 只有 IP
计数周期 只有 10 秒
缓解时长 只有 10 秒
规则条数 1 条

也就是说,"按 URI Path 限制"不是可选项,而是免费版唯一能用的匹配字段。它是限流器,不是门锁——10 秒的窗口,攻击者把速度放慢就绕过去了。

按省份限制 IP 这个思路我本来挺喜欢:只让江苏的 IP 进来。查了字段文档,ip.src.subdivision_1_iso_code(省级 ISO 码)明确标着 "Requires a Cloudflare Business or Enterprise plan"。省级判断是付费功能,免费版的地理字段只到国家级。

国家级 IP 限制 免费可用,配合 WAF 自定义规则能挡掉绝大多数扫描。但它挡不住有心人——任何人在国内,或者挂个国内代理,就进来了。

绕了一圈,我明白了一件事:这些方案回答的都是"这个人从哪来",没有一个在回答"这个人是谁"。

最终选择:mTLS

mTLS 的思路跟上面几个都不一样。

平时我们上 HTTPS,只验证一边:服务器给你看证书,你确认"这确实是 huey314.online"。你的浏览器在这个过程中没有任何身份。

mTLS(mutual TLS,双向 TLS)加了一步:客户端也要出示证书,服务器反过来验证你。

关键在于,拒绝发生在 TLS 握手阶段——没证书的设备,连一个字节的 HTML 都拿不到,浏览器直接报握手失败。它根本到不了登录页,也就没有机会去猜密码、撞验证码、被限流规则统计。

这跟前面所有方案的本质区别是:

你在南京、在外地、在飞机上连卫星网,只要设备带着证书就能进;别人就算把服务器放在你家隔壁,也进不来。

实施:三张证书

Cloudflare 免费区支持用它的托管 CA 签发客户端证书(每域名 100 张配额,自建 CA 才要企业版)。

签发时我留了个心眼:私钥在 NAS 本地生成,只把 CSR(证书签名请求)交给 Cloudflare。这样私钥从头到尾不离开自己的机器,Cloudflare 只是签个名。

openssl req -new -newkey rsa:2048 -nodes -keyout key.pem -out csr.pem -subj '/CN=honor-phone'
# 把 csr.pem 提交给 Cloudflare,换回证书
openssl pkcs12 -export -out honor-phone.p12 -inkey key.pem -in cert.pem

手机、平板、电脑,一台一张。一台一张不是为了限制数量,是为了吊销粒度——p12 本身没有任何设备绑定,同一张装十台都行,交叉着装也行;但设备丢了的时候,一台一张只吊销那一张,其他设备不受影响。

Cloudflare 的 mTLS 是两步,少一步都不行

这是第一个坑。它的机制分成两截:

  1. 把域名加入 mTLS Hosts 列表——让 Cloudflare 在握手时请求客户端证书。注意是"请求",不是"要求",没证书照样能进;
  2. WAF 规则——这才是真正的门:
(http.host in {"nas.huey314.online" "ai.huey314.online"} and not cf.tls_client_auth.cert_verified)
→ Block

只做第一步,等于门框装好了没装门;只做第二步,Cloudflare 压根不会跟客户端要证书,所有人都过不了验证。

顺序上也有讲究:先把证书签好、发下去、装好、确认能用,最后才上 WAF 规则。反过来做,你自己第一个被关在门外。

装证书踩的坑

三台设备的证书都装好了,结果只有平板能进。

Windows:证书导入位置是对的("个人"存储、带私钥),问题出在浏览器没有完全重启。Chrome 只在启动时读一次系统证书存储——任务栏关窗口不够,得让所有后台进程都结束。另外导入向导默认那个"根据证书类型自动选择证书存储",很容易把证书扔到"受信任的根证书颁发机构"去,那样浏览器根本不会用它。

Android 这边坑更深:

最后手机是靠"重启设备 + 确认证书落在正确的类别"解决的。

现在是什么状态

两个域名现在是:不带证书 → 403,带证书 → 正常访问。

日常使用最直接的变化:不用再打开 Tailscale 了,浏览器直接打域名,证书自动带上。

但代价也得说清楚:

所以这两个方案我没有二选一,而是各管一段:随手看看(AI 界面、NAS 后台)走 Cloudflare,不用开客户端,什么设备都行;要传文件、要 SSH、要速度,把 Tailscale 打开。

最后一点反思

"绝对安全"是不存在的。

mTLS 只解决了一件事——没有钥匙的人进不来。它挡不住钥匙被复制(私钥就躺在设备的文件系统里)、挡不住设备被借用或丢失、挡不住流量在 Cloudflare 那里被解密(TLS 终止在它的边缘)、也挡不住证书到期那天突然失效。

更值得说的是,它把风险从一个地方挪到了另一个地方。为了签发和分发证书,NAS 上就多出了私钥文件、p12 文件和一枚能改 WAF 规则的 API Token。现在整个体系里最弱的一环不是 Cloudflare,而是这几个文件——外面的门焊死了,钥匙却还插在门内的锁孔上。

链条的强度取决于最弱的那一环。这句话今天体会得格外具体。