feat: establish NekoNest Cloud control and relay

This commit is contained in:
2026-08-12 23:25:43 +08:00
commit f27606b709
222 changed files with 71456 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# NekoNest Cloud 状态模型
## 账号、租户与席位
- 一个 Cloud 账号拥有一个 `tenant_instances` 记录。
- 一个 `active``slot_state=active` 的主机占一个席位,在线与否不改变计数。
- 未过期的 waiting pairing 只预留容量;取消、过期或成功 claim 后释放预留。
- pairing 创建与 claim 都在写入 SQL 内复核容量。第 N+1 台返回 `device_capacity_exceeded`
- 账户所有者可以轮询自己创建的配对状态,但不能读取其他账户的请求。
- 等待中的请求可由所有者取消;若 daemon 已先认领,则取消失败,不能撤销已提交的设备凭据。
- 主机撤销将主机标记为 deactivated、撤销所有设备凭证、释放席位,并推进 authorization revision。
- 同一身份恢复复用原 host ID;不能因复制公开指纹而获得第二个身份。
席位允许设备成为租户成员,不自动授权任何手机读取该设备。
## Placement
`tenant_placements` 是共享 Relay 的部署权威:
- `home_region_id` 固定租户主区域;
- `relay_node_id` 指向当前唯一可写节点;
- `generation` 只能单调增加;
- `state` 首版使用 `provisioning|active|quiescing|copying|switching|draining|failed`
客户端只看到稳定 Connect origin。`provisioning` 时在同一地址重试,不得到后端节点 URL。
## 授权 revision
每个租户有一条 `tenant_authorization_state`
- `status=active|suspended`
- `revision` 在成功 claim、主机撤销、手机撤销或租户暂停时单调增加;
- 变更只有在相应设备/主机写入已经成功时才推进,避免未提交操作造成虚假撤销。
Relay 获取最长 5 分钟的签名快照,每 60 秒完整刷新、默认每 5 秒检查 revision(delta 与随后的完整刷新各限制为 3 秒)。控制面不可达时不建立新 principal;现有连接最多存活到快照到期。
## 节点与签名密钥
节点状态为 `provisioning|active|draining|offline|retired`。heartbeat 只接受非回退 generation。节点以 D1 中未撤销、未过期的 SPIFFE ID 与证书 SHA-256 指纹记录为身份。所有内部调用必须先通过受信 mTLS 终止层注入绑定 method/path 的短时 HMAC assertion;控制面不要求共享 bearer。
Ed25519 私钥不进入 D1。`relay_signing_keys` 只保存 public JWK、`private_key_ref``kid`、有效期与状态。签名密钥必须至少有效到新快照的 `expires_at`;轮换重叠必须长于 5 分钟最大快照 TTL,旧 public JWK 也必须保留超过最后一份旧 key 快照的 TTL,保证验证窗口不断裂。
## Phone handoff
`phone_handoff_tickets` 生命周期:`issued → consumed``issued → expired`。ticket
- 随机 256 bit
- D1 只保存 SHA-256 摘要;
- 绑定账号、租户、精确 PWA origin;
- 60 秒有效;
- 条件 UPDATE 原子消费一次。
控制面只允许目标 placement 节点原子消费 ticket,并向该节点返回 tenant 与手机公钥。目标 Relay Engine 创建独立 phone principal/token 后,把 routing-only handle 摘要登记回来。控制面不生成或保存 phone token。该流程不创建 device grantroute handle 只负责解析 tenant placement,不能替代 phone token。
每次 phone WS/REST 入口都必须同时校验 route handle 摘要与 phone token 摘要,并要求两者绑定同一未撤销 phone principal 和 tenant;单独持有 route handle 不构成授权。已消费或已过期 ticket 只短暂保留摘要和公开身份字段用于防重放诊断,24 小时后由维护任务删除;D1 从未保存明文 ticket。
## 数据边界
D1 保存账号、权益、主机身份元数据、凭证摘要、placement、授权 revision、公钥、ticket/route 摘要和审计。新增表不得出现 prompt、session、attachment 明文或私钥。
控制面 `last_used_at` 只证明设备凭证最近成功通过授权,不证明租户 relay、WebSocket、Relay Engine 或本地 Agent 在线。
遗留 `grace_days` 不参与资格、设备认证或页面判断。
加载、离线和页面失败是客户端展示状态,不得改写控制面 placement、授权或主机身份事实。