feat: v0.12.1 —— Worker 支持 UptimeRobot + 管理端远程访问 + 前后端分离
起因:核实「管理端在 Cloudflare 上是否有效」时发现一个更要紧的问题 —— Worker 完全没有 UptimeRobot 同步能力(runChecks 的探测目标只有 config.monitors)。 而实际配置是 dataFilter=uptimerobot,也就是说直接部署到 Worker 面板会是空的: 它探测 8 个本地条目,而真正在监控的 6 个 UR 目标它根本不认识。 ① Worker 支持 UptimeRobot(Step 1) - edge/cloudflare/uptimerobot.js(新增):Workers 版 UR 只读数据源, 与 prober/node/lib/uptimerobot.js 同一套语义(状态映射/ratios 解析/故障原因) - runChecks 的探测集合扩展为「自建目标 + UR 镜像目标」;UR 目标只拉取不探测, 采用其状态并补齐本地 daily 分桶 - probeScope 与 dataFilter 解耦(与本地探针同语义):config 范围不拉 UR, uptimerobot 范围不探测本地 - 同步失败时标记 sources.uptimerobot.ok=false 并保留上次数据, 绝不把目标判为离线或从面板消失;UR 侧删除的监控自动清理 - 快照新增 probeScope;UR 目标带 meta(面板区分自建/外部)与 uptimeRatios ② 管理端远程访问(Step 2) - settings.admin.allowRemote:探针默认只监听 127.0.0.1,显式打开才监听全网卡, 启动日志就此告警。这是 Cloudflare Tunnel 的安全前提(已实测两种行为) - edge/cloudflare/tunnel.yml(新增):命名隧道配置,只放行 ^/(admin|api/admin/.*|assets/.*)$,其余 404 —— 不把公开面板一起挂到公网 ③ 前后端分离(Step 3) - Worker 侧 /admin 与 /api/admin/* 显式返回 403 + 指向文档的提示, 而非默默落到静态资源变成 404,确保边缘侧没有任何可写面 测试:新增 scripts/verify-worker.cjs(36 项)—— 把 Worker 以 data: URL 动态载入以 绕开 cloudflare:sockets,注入假 KV/静态资源/UptimeRobot 服务器, 不需要 wrangler 也不需要联网。全项目 8 套 310 项全部通过。 过程中抓到两个真 bug: 1. UR 同步失败的降级路径引用了不含 meta 的旧对象,导致 name/url 为 undefined、 UR 目标从面板上消失 2. stats.total 按 config.monitors.length 计数,在 probeScope=uptimerobot 时 与实际参与的目标数不符 另有一条测试夹具教训:内存版 KV 必须实现 get(key, 'json') 的类型参数 (真实 Workers KV 会自动反序列化),否则 state.monitors 恒为 undefined。
This commit is contained in:
@@ -73,9 +73,36 @@ wrangler deploy
|
||||
- 手动触发一轮:`curl https://<worker 域名>/api/refresh`(等待本轮完成后返回最新快照,与本地探针同语义)
|
||||
- cron 表达式在 `wrangler.toml` 的 `[triggers].crons` 中调整(`* * * * *` = 每分钟)
|
||||
|
||||
### 接入 UptimeRobot 数据源
|
||||
|
||||
Worker 与本地探针共用同一套 UptimeRobot 语义(只读,映射逻辑见 `edge/cloudflare/uptimerobot.js`)。API Key 用 secret 传入,**不要写进 `monitors.json`**(它会随静态资源公开):
|
||||
|
||||
```bash
|
||||
wrangler secret put UPTIMEROBOT_API_KEY # 填 ur... 开头的只读 Key
|
||||
```
|
||||
|
||||
同时在 `public/monitors.json` 的 `settings` 里开启(`apiKey` 留空,由 secret 提供):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"settings": {
|
||||
"dataFilter": "uptimerobot", // 只展示 UptimeRobot 目标
|
||||
"uptimeRobot": { "enabled": true, "apiKey": "" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
行为与本地探针一致:
|
||||
|
||||
- `probeScope` 决定真正发请求的范围(`all` / `config` / `uptimerobot`),未显式设置时跟随 `dataFilter`
|
||||
- UptimeRobot 目标只拉取不探测,采用其状态并补齐本地 `daily` 分桶
|
||||
- 同步失败时**保留上次数据**并把 `sources.uptimerobot.ok` 置为 `false`,绝不因此把目标判为离线
|
||||
- UR 侧删除的监控会从面板移除,避免残留僵尸条目
|
||||
|
||||
注意事项:
|
||||
|
||||
- **TCP 出站连接(`cloudflare:sockets`)需要 Workers 付费计划($5/月)**;免费计划下 `http` 类型探测与面板完全正常,`tcp` 类型会报错并记为离线
|
||||
- **banner 类型边缘端不支持**(需原始 socket 读取),会记为离线并给出说明文案;这类目标请用本地探针
|
||||
- KV 为最终一致存储,刚写入后立即读取可能有秒级延迟,对分钟级巡检无影响
|
||||
- 90 天可用率分桶随 state 一起存于 KV,按 **UTC 日期** 分桶(边缘环境无本地时区);每分钟 cron 约 1440 次/天
|
||||
- 免费版 Worker 每日 10 万次请求,面板静态资源走 Assets 不计入 Worker 请求数
|
||||
@@ -126,7 +153,40 @@ EdgeOne Pages 当前以静态托管为主,采用与方案二相同的「静态
|
||||
| TCP 探测 | ✅ | ✅(Actions 内) | 付费版 ✅ / 免费版 ❌ | 同 GH Pages |
|
||||
| 真实状态码 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 内网目标 | ✅(浏览器同网即可) | ❌ | ❌ | ❌ |
|
||||
| UptimeRobot 数据源 | ✅ | ✅ | ✅ | ❌(需改用自建探测) |
|
||||
| 通知 / 维护窗口 / 事件 | ✅ | ❌ | ❌ | ❌ |
|
||||
| **管理端(2FA)** | ✅ | ❌ | ❌ | ❌ |
|
||||
| 国内访问速度 | 本机最快 | 一般 | 一般 | 快 |
|
||||
| 运维成本 | 需保活进程 | 零 | 零 | 零 |
|
||||
|
||||
建议:个人/演示用 **GitHub Pages**;生产公网监控用 **Cloudflare Workers**;服务对象在国内且以 HTTP 监控为主用 **EdgeOne Pages**;内网资产用 **本地 Node**。
|
||||
|
||||
> ⚠️ **管理端只存在于「本地 Node」部署形态。** 其余形态的部署环境只提供只读面板与探测,没有任何写接口 —— Cloudflare Worker 上访问 `/admin` 或 `/api/admin/*` 会得到 403(而非 404,因为那里确实不提供管理能力)。需要管理操作(改配置、维护窗口、通知自检、立即重探)时,在本地 Node 那一侧完成。
|
||||
>
|
||||
> 原因不是「没做」,而是这些能力依赖本地状态:2FA 档案与会话存在 `data/` 目录与进程内存里,而 Workers 无文件系统、会话状态也无法跨 isolate 共享(KV 又是最终一致,无法承载「恢复码一次性」这类语义)。把这个结论说清楚,比在边缘环境里做一个半可靠的管理端更有价值。
|
||||
|
||||
---
|
||||
|
||||
## 远程访问管理端(Cloudflare Tunnel)
|
||||
|
||||
探针在内网/家用主机上时,若要在外网打开 `/admin`,用 Cloudflare Tunnel 让内网**主动出网**建隧道(不需要公网 IP、不需要开放入站端口)。
|
||||
|
||||
前提与步骤:
|
||||
|
||||
1. **确认管理端已绑定 2FA**。Tunnel 只负责把端口暴露出去,真正拦住未授权访问的是管理端的 TOTP + 会话 + 失败限流。
|
||||
2. 打开探针的远程管理开关(**默认关闭**,此时探针只监听 `127.0.0.1`):
|
||||
|
||||
```jsonc
|
||||
// local.monitors.json
|
||||
{ "settings": { "admin": { "allowRemote": true } } }
|
||||
```
|
||||
|
||||
3. 建命名隧道并只回源管理路径,参考 [`edge/cloudflare/tunnel.yml`](../edge/cloudflare/tunnel.yml):
|
||||
|
||||
```bash
|
||||
cloudflared tunnel --no-autoupdate run --token <你的 token>
|
||||
```
|
||||
|
||||
该配置只放行 `^/(admin|api/admin/.*|assets/.*)$`,其余路径一律 404 —— 不要把公开面板与 `/api/status` 一起挂到公网。
|
||||
|
||||
开启 `allowRemote` 后启动日志会明确警告探针正在监听 `0.0.0.0`,提醒你该端口同时伺服公开面板,前置代理必须启用 HTTPS。
|
||||
|
||||
+33
-2
@@ -151,16 +151,46 @@ banner 探测的副作用仅有一次 TCP 连接与读取,与端口扫描器
|
||||
|
||||
v0.12.0 起,所有会改配置的操作都放在 `/admin`,**必须绑定并通过两步验证才能进入**。
|
||||
|
||||
### 部署形态与可写能力
|
||||
|
||||
管理端**只存在于「本地 Node 探针」这一种部署形态**。各形态的可写面:
|
||||
|
||||
| 部署形态 | 面板可读 | 探测 | 管理端(2FA) |
|
||||
| --- | --- | --- | --- |
|
||||
| 本地 Node | ✅ | ✅ | ✅ |
|
||||
| Cloudflare Workers | ✅ | ✅ | ❌ 403 |
|
||||
| GitHub Pages / EdgeOne Pages(纯静态) | ✅ | ❌(仅展示 CI 产出的快照) | ❌ 不存在 |
|
||||
|
||||
边缘形态访问 `/admin` 或 `/api/admin/*` 会得到 **403 + 指向本文的提示**,而不是 404 —— 明确表达「这里不提供写能力」,避免访客把 404 误读成路径写错。
|
||||
|
||||
这不是「没做」,而是这些能力依赖本地状态:2FA 档案与会话存在 `data/` 目录与进程内存中,而 Workers 无文件系统、isolate 之间不共享内存(KV 又是最终一致,无法承载「恢复码一次性使用」这类语义)。
|
||||
|
||||
### 远程访问(Cloudflare Tunnel)
|
||||
|
||||
探针在内网时若需从外网访问 `/admin`,用 Cloudflare Tunnel 让内网主动出网建隧道。
|
||||
|
||||
**探针默认只监听 `127.0.0.1`**,必须显式打开才能被隧道访问:
|
||||
|
||||
```jsonc
|
||||
// local.monitors.json
|
||||
{ "settings": { "admin": { "allowRemote": true } } }
|
||||
```
|
||||
|
||||
开启后启动日志会警告「正在监听 0.0.0.0」。**请注意该端口同时伺服公开面板** —— 也就是说一旦打开这个开关,`/` 与 `/api/status` 也会跟着对外可达,这两项本身是只读的,但你的目标名称、地址与可用率会一并暴露。隧道配置(`edge/cloudflare/tunnel.yml`)默认只放行 `^/(admin|api/admin/.*|assets/.*)$`,其余路径 404,就是为了不把公开面板一起挂出去。
|
||||
|
||||
### 威胁模型与对应措施
|
||||
|
||||
| 威胁 | 措施 |
|
||||
| --- | --- |
|
||||
| 未绑定就被抢先接管管理端 | 绑定需 `SHIROKO_ADMIN_SETUP_KEY`;未绑定时 `/api/admin/*` 除绑定流程外**一律 403**;未设置该环境变量时绑定入口直接关闭(503) |
|
||||
| 未授权者经隧道访问 | 隧道暴露的是同一端口,防护完全依赖 2FA:TOTP 校验 + 会话哈希 + 失败限流三重 |
|
||||
| 隧道配置过宽导致面板外泄 | 示例配置只放行管理路径,并注释说明不要把 `/` 与 `/api/status` 一起暴露 |
|
||||
| 忘记关`allowRemote` | 启动日志显式警告监听地址;默认值为关闭 |
|
||||
| 暴力枚举 6 位码(100 万种) | 按 IP 计数,连续 5 次失败后指数退避锁定(1m → 2m → 4m …上限 30m);锁定期间即使验证码正确也拒绝 |
|
||||
| 会话 id 被窃取 | id 为 32 字节随机数,**服务端只存 SHA-256 哈希**;cookie 为 `HttpOnly`(JS 读不到)+ `SameSite=Strict`;30 分钟空闲失效、最长 2 小时;进程重启清空全部会话 |
|
||||
| 截图复用验证码 | 记录最近使用的 TOTP counter,**同一时间步的码用过即作废** |
|
||||
| 恢复码泄露 | 只存 SHA-256 哈希(带随机盐),一次性使用,用后立刻从档案中移除 |
|
||||
| 伪造来源 IP 绕过限流 | **只信任 socket 对端地址,不解析 `X-Forwarded-For`**(可被任意伪造) |
|
||||
| 伪造来源 IP 绕过限流 | **只信任 socket 对端地址,不解析 `X-Forwarded-For`**(可被任意伪造)—— 经隧道访问时对端地址是 Cloudflare 边缘节点,因此限流是按边缘 IP 而非真实客户端统计的,这是隧道形态下已知的粒度损失 |
|
||||
| 篡改请求 | `Path=/` + `HttpOnly` + `SameSite=Strict`;请求体上限 64KB,超出返回 413;参数逐项白名单校验(如 `probeScope` 只接受三个枚举值) |
|
||||
| 通过管理端把密钥写进仓库 | **所有写操作只落 `local.monitors.json`**(私有、已 gitignore),绝不改 `public/monitors.json` |
|
||||
|
||||
@@ -184,5 +214,6 @@ node scripts/verify-admin.cjs # 102 项:RFC 向量 / 绑定 / 会话 / 防
|
||||
|
||||
- `.gitignore` 已包含 `local.monitors.json` 与 `.dev.vars`(Cloudflare 本地密钥)
|
||||
- Worker 的 `REFRESH_TOKEN` 用 `wrangler secret put REFRESH_TOKEN` 管理,不要写进 `wrangler.toml`
|
||||
- **UptimeRobot API Key 在 Worker 上必须用 `wrangler secret put UPTIMEROBOT_API_KEY`**,写进 `public/monitors.json` 会随静态资源公开。只用 `ur*` 开头的只读 Key
|
||||
- **公开面板**(`/`)仍是只读前端:无 cookie、无写入接口、无服务端会话;渲染配置字段时统一做 HTML 转义(`escapeHtml`),XSS 面仅剩配置自身。管理能力全部隔离在 `/admin` 与 `/api/admin/*`(需会话)
|
||||
- Node 探针同时充当静态服务器,**不要直接暴露公网**;确实需要时在其前方加带认证的反向代理,并设 `allowRefresh: false`
|
||||
- Node 探针同时充当静态服务器,**默认只监听 `127.0.0.1`**;需要远程访问时用 `settings.admin.allowRemote=true` 显式打开,并确保前置代理已启用 HTTPS
|
||||
|
||||
Reference in New Issue
Block a user