docs: tighten wording, soften node capability claims, link architecture to code
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/e2e Pipeline was successful

- Remove the node capability paragraph from README; soften "pure collector"
  and "no self-update" statements in docs, AGENTS.md and READMEs
- architecture.md: drop scalability goals, design decisions and open items;
  add a component-to-code table and code links
- Replace rhetorical contrasts in security docs with direct statements
- AGENTS.md: add project mental model, change map and writing style rules
This commit is contained in:
justme committed 2026-10-21 06:52:01 +08:00
1 parent a9bf849697
commit d5ebf29e57
11 files changed
+89 -100

No files matched your search

+9 -1
View File
@@ -17,7 +17,7 @@ MoShiro is a self-hosted network path quality monitor. Probes (home broadband) a
## Invariants (violations fail the build or review)
- Nodes are pure collectors: no process start, dynamic loading, `HttpClient`, or file writes outside `Fleet.Hosting.ConfigStore` (`backend/BannedSymbols.Node.txt`). Do not add RPC methods that trigger non-measurement actions.
- Node processes are kept to measurement and reporting by build-time restrictions: process start, dynamic loading, `HttpClient`, and file writes outside `Fleet.Hosting.ConfigStore` are banned (`backend/BannedSymbols.Node.txt`). Capabilities such as updates or command execution are not part of the core; adding or changing one needs a design review against `docs/security/security.md`.
- `backend/fleet/**` must not reference `MoShiro.*` (`ArchitectureTests`). `MoShiro.Measurement` contains no service concepts such as REALITY or Hysteria2.
- Every RPC method is declared in a method table (`FleetMethodTable`, `MoShiroMethodTable`) with direction, type and roles. Add the declaration before the handler.
- `Backend.Api` must not reference `Storage`. Error-code to HTTP mapping happens once, in the Api layer.
@@ -43,6 +43,14 @@ MoShiro is a self-hosted network path quality monitor. Probes (home broadband) a
Index: [`README.md`](README.md#文档). Prefer the code and `backend/openapi/v1.json` when they disagree with a document, and fix the document. Update docs in the same change as behaviour: `docs/design` (architecture, protocol, measurement, analysis, storage, query), `docs/security`, `docs/api`, `docs/ops`, `docs/dev`, and `docs/roadmap.md` for unimplemented work. Do not record dates or change history in docs.
Writing style for docs, READMEs, comments and commit messages (the `stop-slop` skill lists the patterns in detail; it is written for English, the same rules apply to Chinese):
- State the fact directly. Drop "not X but Y" contrasts, negation lists, rhetorical setups, and aphorisms that read like pull-quotes.
- Name the actor. Prefer "the Agent rejects the task" over "the task is rejected".
- No filler openers ("it is worth noting"), no closing summaries, no adverbs doing emphasis. Avoid bold for emphasis.
- Do not claim absolutes ("never", "always", "cannot") unless the code or a build-time check enforces it. Describe current behaviour and mark planned work as planned.
- Give concrete names, paths, values and commands instead of general statements.
## Workspace safety
- Inspect `git status` before editing. Preserve existing uncommitted changes; do not reset, clean, stash, or commit unrelated files.
+1 -3
View File
@@ -28,8 +28,6 @@ MoShiro 旨在解决跨网络线路质量难以量化的问题:
测量会话:Probe → Agent
```
Probe 与 Agent 只做测量与上报,没有终端、命令执行和自更新,更新由外部服务管理器完成。节点与 Backend 之间的身份、任务和更新均基于签名校验,详见[安全文档](docs/security/security.md)。
## 快速开始
环境:.NET SDK 10(版本见 `global.json`)、Node.js 与 npm。
@@ -53,7 +51,7 @@ npm run build --prefix web
| 主题 | 文档 |
| --- | --- |
| 架构与设计决策 | [architecture](docs/design/architecture.md) |
| 架构与数据链路 | [architecture](docs/design/architecture.md) |
| 线协议与方法 | [protocol](docs/design/protocol.md) |
| 采集引擎与任务 | [measurement](docs/design/measurement.md) |
| 分析、存储、查询 | [analysis](docs/design/analysis.md) · [storage](docs/design/storage.md) · [query](docs/design/query.md) |
+1 -1
View File
@@ -2,7 +2,7 @@
Agent 是部署在 VPS 或其他可达网络节点上的采集进程。它向 Backend 注册并保持连接,接受 Probe 的测量会话,执行线路任务中的 Agent 侧工作,再将两端数据合并后上报。
Agent 仅开放配置的控制和数据端口。它不提供远程终端、命令执行或文件管理;本地配置中的网络范围、速率和并发限制由节点自行执行,Backend 下发的任务不能越过这些限制。
Agent 仅开放配置的控制和数据端口。当前版本不提供远程终端、命令执行或文件管理;本地配置中的网络范围、速率和并发限制由节点自行执行,Backend 下发的任务不能越过这些限制。
## 工作流程
+2 -2
View File
@@ -20,10 +20,10 @@
## 设计原则
1. 与业务无关:产品通过扩展点接入:元数据类型、RPC 方法、发件箱消息类型、期望状态文档类型。
2. 安全默认值:Fleet 永远不提供 exec、shell、文件读写、远程下发代码这类方法;节点只响应本地注册过的方法(本地策略 `allow_methods` 还能收窄);调用对方不支持的方法在本地直接返回 `fleet_unsupported`。
2. 安全默认值:Fleet 本身不提供 exec、shell、文件读写、远程下发代码这类方法;节点只响应本地注册过的方法(本地策略 `allow_methods` 还能收窄);调用对方不支持的方法在本地直接返回 `fleet_unsupported`。
3. 权限分离:注册(`fleet-enroll`,一次性)和常驻业务进程是两个进程;常驻进程不含注册代码,也不使用 `HttpClient`。
4. NativeAOT:节点侧只依赖 BCL,泛型扩展点通过产品传入的 `JsonTypeInfo<T>` 序列化,不使用反射。
5. 更新不在 Fleet 里:节点进程没有自更新能力。Quadlet 装的节点和 Backend 由退出后 systemd 重新拉镜像、脚本装的节点由宿主机上的更新器验签更新,见 [`docs/ops/deployment.md`](../../docs/ops/deployment.md);纯二进制节点的更新见 [路线图](../../docs/roadmap.md)。
5. 更新不在 Fleet 里:Fleet 不包含更新逻辑,更新由节点外部的服务管理器或更新器完成。Quadlet 装的节点和 Backend 由退出后 systemd 重新拉镜像、脚本装的节点由宿主机上的更新器验签更新,见 [`docs/ops/deployment.md`](../../docs/ops/deployment.md);纯二进制节点的更新见 [路线图](../../docs/roadmap.md)。
## 身份
+1 -1
View File
@@ -129,7 +129,7 @@ chunk 入库 ─┬─> 流式路径:按 1s 桶计算累加器 → 热层环
每个模型的输出都附带:各指标的样本量、`estimated` 标记,以及局限说明,例如:
- 模拟的只是流量特征,不是真实协议;
- 只模拟流量特征,不实现真实协议;
- 测量端口与真实服务端口不同(`port_differs_from_service`,默认端口 7443/7444 时总会出现)。
## 9. 项目结构
+54 -71
View File
@@ -8,7 +8,7 @@
- 分析:Backend 的分析层推算在某条线路上部署 REALITY 类、Hysteria2 类或 Minecraft 服务的体验,并对多条线路排序和对比。
- 主机状态:Agent 和 Probe 读取 `/proc`,上报 CPU 占用、内存、根分区、网卡累计流量与瞬时网速、运行时长这类汇总数字。不采集 CPU 型号、主机名、网卡名、IP 和进程。Probe 的上报可在 `probe.json` 中用 `reporting.host_stats: false` 关闭。展示页以卡片呈现每个节点的主机状态,线路在卡片下方的面板中。
- 构建信息:节点连接面板时自报系统、架构和构建信息(版本、tag、提交、构建时间),用于展示和更新。国家和运营商由 Backend 依据节点来源地址查询,节点不访问任何外部服务,展示页不含任何 IP。
- 节点边界:Agent 和 Probe 是纯采集器,没有 SSH、远程执行、终端、文件读写和自更新。更新由节点宿主机上的更新器完成,节点进程只把面板转来的发布清单和更新请求写入数据目录。
- 节点能力:当前 Agent 和 Probe 只包含测量与上报,不提供 SSH、远程执行、终端和文件读写;更新由节点宿主机上的更新器或服务管理器完成,节点进程只把面板转来的发布清单和更新请求写入数据目录。后续能力的规划见 [security.md](../security/security.md) 第 9 节。
## 2. 拓扑
@@ -36,6 +36,27 @@
更新(Agent、Probe、Backend):面板登记 CI 签名的清单 → 管理端确认 → 节点(Backend 先备份数据库)有序退出 → systemd Pull=always 拉最新镜像重启
```
各组件的代码位置:
| 组件 | 代码 |
| --- | --- |
| Backend 宿主与组合根 | [`MoShiro.Backend`](../../backend/src/MoShiro.Backend) |
| Api(REST、SSE、OpenAPI) | [`MoShiro.Backend.Api`](../../backend/src/MoShiro.Backend.Api),实时推送 [`LiveEndpoints.cs`](../../backend/src/MoShiro.Backend.Api/Public/LiveEndpoints.cs)
| Auth(会话、passkey、API 令牌、审计) | [`Application/Auth`](../../backend/src/MoShiro.Backend.Application/Auth)、[`Application/Audit`](../../backend/src/MoShiro.Backend.Application/Audit) |
| Trust(操作员集合、服务器身份) | [`TrustService.cs`](../../backend/src/MoShiro.Backend.Application/Auth/TrustService.cs)、[`ServerIdentityService.cs`](../../backend/src/MoShiro.Backend.Application/Auth/ServerIdentityService.cs);节点侧验证 [`OperatorTrust.cs`](../../backend/fleet/src/Fleet.Identity/OperatorTrust.cs)、[`TrustAnchor.cs`](../../backend/fleet/src/Fleet.Hosting/TrustAnchor.cs)
| Registry(Agent、Probe 注册与状态) | [`AgentService.cs`](../../backend/src/MoShiro.Backend.Application/Agents/AgentService.cs)、[`ProbeService.cs`](../../backend/src/MoShiro.Backend.Application/Agents/ProbeService.cs)、[`MoShiroFleetHooks.cs`](../../backend/src/MoShiro.Backend.Application/Agents/MoShiroFleetHooks.cs)
| Planner(有效任务集) | [`TaskPlanner.cs`](../../backend/src/MoShiro.Backend.Application/Tasks/TaskPlanner.cs)、[`EffectiveTasks.cs`](../../backend/src/MoShiro.Backend.Application/Tasks/EffectiveTasks.cs)
| Links(哪些 Probe 连哪些 Agent) | [`LinkService.cs`](../../backend/src/MoShiro.Backend.Application/Agents/LinkService.cs)
| Releases(发布清单、可信发布密钥) | [`Application/Updates`](../../backend/src/MoShiro.Backend.Application/Updates),轮询 [`ReleasePoller.cs`](../../backend/src/MoShiro.Backend.Application/Updates/ReleasePoller.cs)
| Gateway(Fleet 网关、状态提供者、收件箱) | [`MoShiro.Backend.Gateway`](../../backend/src/MoShiro.Backend.Gateway),入库 [`RawChunkInbox.cs`](../../backend/src/MoShiro.Backend.Gateway/RawChunkInbox.cs)、[`IngestService.cs`](../../backend/src/MoShiro.Backend.Application/Ingest/IngestService.cs)
| Analysis(算子、指标、模型) | [`MoShiro.Backend.Analysis`](../../backend/src/MoShiro.Backend.Analysis),调度 [`AnalysisScheduler.cs`](../../backend/src/MoShiro.Backend.Application/Analysis/AnalysisScheduler.cs)
| LiveHub(热层与 SSE 主题) | [`LiveHub.cs`](../../backend/src/MoShiro.Backend.Application/Live/LiveHub.cs)
| Store(段文件 + SQLite) | [`MoShiro.Backend.Storage.Sqlite`](../../backend/src/MoShiro.Backend.Storage.Sqlite),原始块 [`SqliteRawStore.cs`](../../backend/src/MoShiro.Backend.Storage.Sqlite/SqliteRawStore.cs)
| Agent | [`AgentApp.cs`](../../backend/agent/src/MoShiro.Agent/AgentApp.cs)、任务 [`TaskManager.cs`](../../backend/agent/src/MoShiro.Agent/Tasks/TaskManager.cs)、Probe 会话 [`PeerHub.cs`](../../backend/agent/src/MoShiro.Agent/Peer/PeerHub.cs)
| Probe | [`ProbeApp.cs`](../../backend/probe/src/MoShiro.Probe/ProbeApp.cs)、Agent 连接 [`Link/AgentConnection.cs`](../../backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs)
| 采集引擎与协议 | [`MoShiro.Measurement`](../../backend/shared/MoShiro.Measurement)、[`MoShiro.Protocol`](../../backend/shared/MoShiro.Protocol) |
| Fleet 节点框架 | [`backend/fleet`](../../backend/fleet),注册工具 [`fleet-enroll`](../../backend/fleet/tools/fleet-enroll) |
- 线路(line):一对(Probe,Agent)。可以有多个 Probe(身份由 Probe 自己生成并用加入令牌注册,可以吊销、删除),每个 Probe 连接多个 Agent,每个 Agent 同时服务多个 Probe;一条线路的数据按 `line:<probe_id>` 单独存放,互不混合。对外的线路 id 是 `<agent_id>~<probe_id>`。
- 目标任务(target):某个 Agent → 指定的 IP:端口(ICMP、TCP/TLS 建连),只能访问 Agent 本地允许的网段。
@@ -53,23 +74,27 @@
| 角色 | 职责 | 明确不做 |
| --- | --- | --- |
| Probe | 连接 Backend(身份、Agent 列表、系统与构建信息、更新);连接面板指定的 Agent;执行线路任务中 Probe 一侧的角色;记录本端原始列并交给对应的 Agent | 统计;落盘测量数据;任何主机操作 |
| Agent | 注册;接受 Probe;执行 Agent 一侧的角色和目标任务;合并两侧的列并上传;上报主机资源的汇总数字 | 统计;落盘测量数据;exec/终端/文件/自更新(更新由宿主机上的更新器完成);采集主机的详细信息 |
| Agent | 注册;接受 Probe;执行 Agent 一侧的角色和目标任务;合并两侧的列并上传;上报主机资源的汇总数字 | 统计;落盘测量数据;exec / 终端 / 文件操作(当前未提供);更新由宿主机上的更新器完成;采集主机的详细信息 |
| Backend | 注册与身份;任务编排;决定哪些 Probe 连哪些 Agent;持久化原始数据;分析;实时汇聚;API;分发 CI 签名的发布清单 | 主动连接 Probe 或 Agent(都是节点拨进来);验签和安装更新(在节点宿主机上) |
角色对应的代码:Probe 见 [`ProbeApp.cs`](../../backend/probe/src/MoShiro.Probe/ProbeApp.cs);Agent 见 [`AgentApp.cs`](../../backend/agent/src/MoShiro.Agent/AgentApp.cs),上传块由 [`ChunkAssembler.cs`](../../backend/agent/src/MoShiro.Agent/Chunks/ChunkAssembler.cs)合并;Backend 接收见 [`FleetGateway.cs`](../../backend/src/MoShiro.Backend.Gateway/FleetGateway.cs)。
## 4. 关键设计(详见各专题文档)
| 主题 | 要点 | 文档 |
| --- | --- | --- |
| 节点框架 | 注册、身份、连接、RPC、可靠上报、状态同步、宿主都由与业务无关的 Fleet 提供;MoShiro 只写业务,其他产品也能复用 | [Fleet 文档](../../backend/fleet/README.md) |
| 注册与身份 | 使用 `fleet-enroll` 和加入令牌自动注册,之后用签名认证;Probe 用自己的身份直连面板,Agent 列表由面板下发 | [enrollment.md](../security/enrollment.md) |
| 更新 | 只接受发布密钥签名的清单,更新策略由节点本地决定,失败自动回滚。CI 对镜像 digest 签名;Backend 和一行脚本装的节点由宿主机上的 `moshiro-update` 验签、按 digest 拉取、失败回滚;Podman Quadlet 装的 Agent、Probe 更简单:节点有序退出,systemd 用 `--pull always` 重新拉最新镜像,节点自己不下载、不验证清单;纯二进制节点的更新见[路线图](../roadmap.md) | [deployment.md](../ops/deployment.md) |
| 任务模型 | `Task = {kind, target, params, schedule}`;持续采集、按需测试、目标任务统一建模;下发“期望任务集”,由节点自行调和 | [measurement.md](measurement.md) |
| 采集扩展 | Kind 由积木组合声明;采集密度靠参数调整;原始块自描述 | [measurement.md](measurement.md) |
| 统计扩展 | 指标注册制:少数几种累加器 + 统计算子;一行 `MetricDef` 就是一个新指标 | [analysis.md](analysis.md) |
| 协议 | 控制链路使用 Fleet(JSON-RPC 2.0 + 二进制通道);MoShiro 只定义 `moshiro.*` 方法、`TaskSet` 状态文档和 QUIC Datagram/TCP 数据面 | [protocol.md](protocol.md) |
| 存储 | 原始块追加写入段文件,SQLite 记录偏移量;指标窗口通用存储并降采样 | [storage.md](storage.md) |
| 展示查询 | 任意时间段(相对或绝对);自动步长(每条曲线最多约 720 个点);1s 内存热层(1 小时)加上 10s/1m/5m/1h 各层;累加器现场合并,汇总精确;列式响应;SSE 实时跟随 | [query.md](query.md) |
| API | `/api/public`(只读,可设为公开)、`/api/admin`(登录)、`/api/agent`(注册与 RPC);实时数据用 SSE | [api.md](../api/api.md) |
| 主题 | 要点 | 文档 | 代码 |
| --- | --- | --- | --- |
| 节点框架 | 注册、身份、连接、RPC、可靠上报、状态同步、宿主都由与业务无关的 Fleet 提供;MoShiro 只写业务,其他产品也能复用 | [Fleet 文档](../../backend/fleet/README.md) | [`backend/fleet`](../../backend/fleet) |
| 注册与身份 | 使用 `fleet-enroll` 和加入令牌自动注册,之后用签名认证;Probe 用自己的身份直连面板,Agent 列表由面板下发 | [enrollment.md](../security/enrollment.md) | [`NodeIdentity.cs`](../../backend/fleet/src/Fleet.Identity/NodeIdentity.cs)、[`fleet-enroll`](../../backend/fleet/tools/fleet-enroll) |
| 更新 | 只接受发布密钥签名的清单,更新策略由节点本地决定,失败自动回滚。CI 对镜像 digest 签名;Backend 和一行脚本装的节点由宿主机上的 `moshiro-update` 验签、按 digest 拉取、失败回滚;Podman Quadlet 装的 Agent、Probe 更简单:节点有序退出,systemd 用 `--pull always` 重新拉最新镜像,节点自己不下载、不验证清单;纯二进制节点的更新见[路线图](../roadmap.md) | [deployment.md](../ops/deployment.md) | [`moshiro-update.sh`](../../packaging/update/moshiro-update.sh)、[`RestartUpdater.cs`](../../backend/shared/MoShiro.Measurement/Update/RestartUpdater.cs)、[`packaging/quadlet`](../../packaging/quadlet) |
| 任务模型 | `Task = {kind, target, params, schedule}`;持续采集、按需测试、目标任务统一建模;下发“期望任务集”,由节点自行调和 | [measurement.md](measurement.md) | [`TaskSet.cs`](../../backend/shared/MoShiro.Protocol/Tasks/TaskSet.cs)、[`TaskPlanner.cs`](../../backend/src/MoShiro.Backend.Application/Tasks/TaskPlanner.cs) |
| 采集扩展 | Kind 由积木组合声明;采集密度靠参数调整;原始块自描述 | [measurement.md](measurement.md) | [`Kinds.cs`](../../backend/shared/MoShiro.Protocol/Catalog/Kinds/Kinds.cs)、[`BlockCatalog.cs`](../../backend/shared/MoShiro.Measurement/Blocks/BlockCatalog.cs) |
| 统计扩展 | 指标注册制:少数几种累加器 + 统计算子;一行 `MetricDef` 就是一个新指标 | [analysis.md](analysis.md) | [`MetricRegistry.cs`](../../backend/src/MoShiro.Backend.Analysis/Metrics/MetricRegistry.cs) |
| 协议 | 控制链路使用 Fleet(JSON-RPC 2.0 + 二进制通道);MoShiro 只定义 `moshiro.*` 方法、`TaskSet` 状态文档和 QUIC Datagram/TCP 数据面 | [protocol.md](protocol.md) | [`MoShiroMethodTable.cs`](../../backend/shared/MoShiro.Protocol/Rpc/MoShiroMethodTable.cs)、[`MethodTable.cs`](../../backend/fleet/src/Fleet.Rpc/MethodTable.cs) |
| 存储 | 原始块追加写入段文件,SQLite 记录偏移量;指标窗口通用存储并降采样 | [storage.md](storage.md) | [`Schema.cs`](../../backend/src/MoShiro.Backend.Storage.Sqlite/Schema.cs) |
| 展示查询 | 任意时间段(相对或绝对);自动步长(每条曲线最多约 720 个点);1s 内存热层(1 小时)加上 10s/1m/5m/1h 各层;累加器现场合并,汇总精确;列式响应;SSE 实时跟随 | [query.md](query.md) | [`QueryService.cs`](../../backend/src/MoShiro.Backend.Application/Query/QueryService.cs)、[`HotLayer.cs`](../../backend/src/MoShiro.Backend.Application/Live/HotLayer.cs) |
| API | `/api/public`(只读,可设为公开)、`/api/admin`(登录)、`/api/agent`(注册与 RPC);实时数据用 SSE | [api.md](../api/api.md) | [`v1.json`](../../backend/openapi/v1.json)、[`Api`](../../backend/src/MoShiro.Backend.Api) |
Kind 目录编译在二进制中,节点不接受远程下发代码。节点在 `fleet.hello` 的 capabilities 中上报支持的 Kind,Backend 只下发它支持的类型。
### 4.0 节点的更新
@@ -80,7 +105,7 @@ Podman Quadlet(Agent、Probe、Backend,推荐)
节点比对清单和自己的版本 ──▶ 心跳报「有新版本」──▶ 管理端显示
自动(默认,运行满 10 分钟)或管理员点「更新」──▶ 节点有序退出
──▶ systemd 重启单元(Pull=always)──▶ 新镜像、新容器
节点不下载、不替换任何东西,也不验证清单;最多被请求一次有序重启
该路径下节点本身不下载、不替换文件,也不验证清单;最多被请求一次有序重启
Backend:不自动,只有管理员点「更新并重启」;退出之前先把数据库备份到 backups/
@@ -89,6 +114,8 @@ Backend:不自动,只有管理员点「更新并重启」;退出之前先
──▶ 状态写进只读的 update-status/ ──▶ 节点随心跳上报 ──▶ Backend ──▶ 管理端(有新版本时点「更新」)
```
实现:宿主机更新器 [`moshiro-update.sh`](../../packaging/update/moshiro-update.sh)(安装 [`install-updater.sh`](../../packaging/update/install-updater.sh)),节点退出逻辑 [`RestartUpdater.cs`](../../backend/shared/MoShiro.Measurement/Update/RestartUpdater.cs),Backend 更新与备份 [`BackendUpdates.cs`](../../backend/src/MoShiro.Backend.Application/Updates/BackendUpdates.cs),清单登记 [`ReleaseService.cs`](../../backend/src/MoShiro.Backend.Application/Updates/ReleaseService.cs),Quadlet 单元 [`packaging/quadlet`](../../packaging/quadlet)。
细节见 [deployment.md](../ops/deployment.md)「Podman(Quadlet)的更新」(一行脚本装的节点另见「宿主机更新器」)。
### 4.1 回显的四个时间戳
@@ -97,78 +124,34 @@ Backend:不自动,只有管理员点「更新并重启」;退出之前先
### 4.2 NAT / CGNAT 适配
所有连接和 UDP 流都由 Probe 发起;Agent 把回复发往请求的实际源地址。会话以 `session_id` 和 MAC 认证,不绑定五元组,NAT 映射变化后自动跟随新地址。持续的 UDP 回显本身就起到保活作用。
所有连接和 UDP 流都由 Probe 发起;Agent 把回复发往请求的实际源地址。会话以 `session_id` 和 MAC 认证,不绑定五元组,NAT 映射变化后自动跟随新地址。持续的 UDP 回显本身就起到保活作用。准入逻辑(重放窗口、令牌桶、源地址学习)见 [`Registries.cs`](../../backend/agent/src/MoShiro.Agent/DataPlane/Registries.cs),数据报路由见 [`MeasurementDatagramRouter.cs`](../../backend/shared/MoShiro.Measurement/Net/MeasurementDatagramRouter.cs)。
## 5. 安全边界:纯采集
## 5. 安全边界
> 本节描述现状。Backend 或节点被攻破后的威胁模型、现有缺口(例如 Probe 会拨 Backend 给的任意地址、测量数据没有产生者签名)及状态见 [security.md](../security/security.md)。
```text
Agent / Probe 能做的 执行编译在二进制中的 Kind;监听和访问本地配置允许的端口与网段;受本地硬上限约束
不能做的 exec / shell / 终端 / 文件读写(配置目录除外)/ 自更新 / 加载外部代码 / 采集主机标识(型号、主机名、IP、进程)
当前未提供的能力 exec / shell / 终端 / 文件读写(配置目录除外)/ 加载外部代码 / 采集主机标识(型号、主机名、IP、进程);更新在节点进程之外完成
即使 Backend 被攻破 攻击者最多能在本地硬上限内发测量流量,拿不到主机权限,也无法把 Agent 当成扫描器或放大器;
也推不出恶意更新(更新清单必须由发布密钥签名,节点宿主机上的更新器用本机公钥验签,并且只认本机配置的镜像仓库)
```
- 本地硬上限写在节点本机的配置文件中,Backend 下发的任务如果超出,节点直接拒绝:允许访问的目标网段(默认为空,即不开放目标任务)、总码率和包速率上限、允许监听的端口。
- 构建期强制:常驻节点进程使用 `BannedSymbols.Node.txt`,禁用 `System.Diagnostics.Process`、`Assembly.Load*`、`NativeLibrary.Load`、`HttpClient`,以及 `ConfigStore` 以外的文件写入 API,详见 [project-layout.md](../dev/project-layout.md) 第 7 节。
- 本地硬上限写在节点本机的配置文件中([`AgentConfig.cs`](../../backend/agent/src/MoShiro.Agent/AgentConfig.cs)),由 [`BudgetGuard.cs`](../../backend/shared/MoShiro.Measurement/Execution/BudgetGuard.cs) 检查,Backend 下发的任务如果超出,节点直接拒绝:允许访问的目标网段(默认为空,即不开放目标任务)、总码率和包速率上限、允许监听的端口。
- 构建期强制:常驻节点进程使用 [`BannedSymbols.Node.txt`](../../backend/BannedSymbols.Node.txt),禁用 `System.Diagnostics.Process`、`Assembly.Load*`、`NativeLibrary.Load`、`HttpClient`,以及 `ConfigStore` 以外的文件写入 API,详见 [project-layout.md](../dev/project-layout.md) 第 7 节。
- 权限分离:注册(`fleet-enroll`,一次性运行)、采集(`moshiro-agent` / `moshiro-probe`)、更新是不同的进程。容器节点里,采集进程在容器内以 UID 1654 运行(只读文件系统、不带额外能力),更新由宿主机上 root 运行的 `moshiro-update` 完成:它只读取节点放进 `update/` 的普通文件、复制到自己的临时目录后验签,状态写在节点只读的 `update-status/`;采集进程改写不了自己的二进制,更新器也读不到节点身份私钥。纯二进制节点的更新见[路线图](../roadmap.md)。
- 容器运行:Agent、Probe 的镜像基于 Alpine(musl,NativeAOT),宿主机用 rootful podman 加 systemd 运行:UID/GID 1654、只读文件系统、`--cap-drop all`、`no-new-privileges`、`/tmp` 用 tmpfs;配置和身份放在挂载的数据目录里。
- 属主检查:[`OwnerGuard.cs`](../../backend/fleet/src/Fleet.Hosting/OwnerGuard.cs);预握手敲门:[`KnockGate.cs`](../../backend/fleet/src/Fleet.Identity/KnockGate.cs)。
- 最小权限运行:非 root 用户;只在需要时授予 `CAP_NET_RAW`(ICMP);默认端口都高于 1024,不需要 `CAP_NET_BIND_SERVICE`,只有在 Agent 本地显式开启 443 时才授予。
- 身份:节点使用本地生成的 ECDSA P-256 密钥对签名认证(Agent 和 Probe 都自己生成;Probe 有两把独立的密钥,一把连面板、一把连 Agent),Backend 只存公钥,数据库泄露也无法冒充节点。
- 防放大:未认证的包静默丢弃;回复长度与请求相等;每个流按声明速率限速;使用重放窗口。
- 容器里的主机指标:容器里的 Agent 通过只读挂载宿主机的 `/proc`、`/sys`、根目录和 `/etc/os-release`(环境变量 `HOST_PROC`、`HOST_SYS`、`HOST_ROOT`)读取宿主机的资源占用和系统名;读不到的项返回空值,不会退回去读容器自己的数据。Probe 的 compose 也一样挂载这几项。
- 自身状态:节点上报进程内的计数(会话数、包速率、队列、丢弃数);Agent 另外每隔几秒用 `moshiro.host` 上报一份主机资源占用的汇总数字(可以在 `agent.json` 里用 `reporting.host_stats = false` 整个关掉),最新值实时推给展示页,Backend 每分钟存一个样本(默认保留 7 天,`retention.host_days`)。
- 身份([`NodeIdentity.cs`](../../backend/fleet/src/Fleet.Identity/NodeIdentity.cs)):节点使用本地生成的 ECDSA P-256 密钥对签名认证(Agent 和 Probe 都自己生成;Probe 有两把独立的密钥,一把连面板、一把连 Agent),Backend 只存公钥,数据库泄露也无法冒充节点。
- 防放大([`Registries.cs`](../../backend/agent/src/MoShiro.Agent/DataPlane/Registries.cs)):未认证的包静默丢弃;回复长度与请求相等;每个流按声明速率限速;使用重放窗口。
- 容器里的主机指标:容器里的 Agent 通过只读挂载宿主机的 `/proc`、`/sys`、根目录和 `/etc/os-release`(环境变量 `HOST_PROC`、`HOST_SYS`、`HOST_ROOT`)读取宿主机的资源占用和系统名(采样见 [`HostSampler.cs`](../../backend/shared/MoShiro.Measurement/Platform/HostSampler.cs));读不到的项返回空值,不会退回去读容器自己的数据。Probe 的 compose 也一样挂载这几项。
- 自身状态:节点上报进程内的计数(会话数、包速率、队列、丢弃数);Agent 另外每隔几秒用 `moshiro.host` 上报一份主机资源占用的汇总数字(可以在 `agent.json` 里用 `reporting.host_stats = false` 整个关掉),最新值实时推给展示页,Backend 每分钟存一个样本(默认保留 7 天,`retention.host_days`),见 [`HostStatusService.cs`](../../backend/src/MoShiro.Backend.Application/Agents/HostStatusService.cs)。
## 6. 可扩展性目标
| 变更 | 需要改动的地方 |
| --- | --- |
| 调整采集密度(例如 TCP 20 Hz → 100 Hz,`tcp_info` 100 ms → 10 ms) | 在 API 中修改任务参数,立即下发生效 |
| 新增采集方法(现有积木可以组合出来) | 在 Kind 目录中加 1 条声明,然后更新 Probe/Agent 版本 |
| 新增采集方法(需要新积木) | 1 个积木类 + 1 条 Kind 声明 |
| 新增统计指标 | 1 行 `MetricDef`(如需新算子,再加 1 个算子类);自动回填 30 天历史 |
| 调整服务评分 | 修改模型的评分函数;评分在查询时计算,立即对全部历史生效 |
Kind 目录编译在二进制中,节点不接受远程下发代码(安全边界)。节点在 `fleet.hello` 的 capabilities 中上报自己支持的 Kind,Backend 只下发它支持的类型。
## 7. 可靠性与资源
## 6. 可靠性与资源
- Probe:每个 Agent 未交付的原始列 ≤ 8 MB;Agent:上传队列 ≤ 64 MB,满时丢弃最旧的块,并通过 `moshiro.event(gap)` 上报缺口。
- 至少一次投递:Backend 持久化之后才回复 ACK,按 `chunk_id` 去重。
- 至少一次投递:Backend 持久化之后才回复 ACK,按 `chunk_id` 去重([`RawChunkInbox.cs`](../../backend/src/MoShiro.Backend.Gateway/RawChunkInbox.cs))。
- 节点重启会丢失未上传的数据,并体现为数据缺口。
- 窗口时间以 Agent 时钟为准;时钟偏差超过 5 秒时记录告警事件。
- 规模:10+ 个 Agent × 2~4 种持续 Kind × 每 10 秒一块,总计约每秒 2~6 块。
## 8. 设计决策记录
| # | 决策 | 理由 |
| --- | --- | --- |
| D1 | 只关注网络层;节点是纯采集器,没有远程执行能力 | 防止权限泄露 |
| D2 | 拓扑为多个 Probe 连接多个 Agent(10+);线路 = (Probe, Agent),数据按 Probe 隔离 | 拓扑需求 |
| D3 | Agent 用加入令牌自动注册,之后用密钥对签名认证 | 批量部署省事;Backend 不保存可用于冒充节点的秘密 |
| D4 | Probe 直连 Backend:身份、Agent 列表(期望状态 `probe_agents`)、状态和更新都走这条链路,测量仍然只走 Probe → Agent。哪些 Probe 连哪些 Agent 由面板决定,`fleet.auto_link` 打开时两两连,也可以手动选择,Probe 始终主动拨出 | 新增 Agent 后自动覆盖,不需要改 Probe 配置;Probe 的版本、更新和线路都能在面板上管理 |
| D5 | 只采集逐包、逐事件的原始数据,解释全部放在 Backend | 模型和指标可以事后重算;采集端最轻 |
| D6 | 统一的任务模型 + 期望任务集调和 | 新增任务类型时协议不变 |
| D7 | Kind 由积木组合声明;原始块自描述 | 调整密度或新增采集方法只改一处 |
| D8 | 指标注册制,使用固定的少数几种累加器 | 新增统计只改一行;存储、降采样、API 全部通用 |
| D9 | JSON-RPC 2.0 信封 + 二进制帧 | 新增能力只需要加方法 |
| D10 | 原始数据每 10 秒切一块,追加写入段文件,保留 30 天;指标长期保留 | 同一条链路同时服务实时和历史;避免海量小文件 |
| D11 | API 分为 public / admin / agent 三层;实时数据用 SSE | 前端只读场景足够;反向代理和 CDN 更友好 |
| D12 | 单仓库,三个独立项目加 Web(展示端、管理端);契约以 C# 代码为真源 | 改一处,编译期即可发现不一致 |
| D13 | Probe/Agent 运行时只依赖 BCL;.NET 10 NativeAOT | 轻量 |
| D14 | 节点机制抽成与业务无关的 Fleet 框架,放在同一仓库的 `backend/fleet/` 目录,暂不独立(不设拆分时间表,需要时再评估;不使用 PublicApiAnalyzers) | 可复用;不引用任何 `MoShiro.*` 的约束由 `ArchitectureTests` 在构建时检查 |
| D15 | 注册独立为 `fleet-enroll` 工具;常驻进程不包含注册代码 | 常驻进程去掉 HttpClient,边界更纯 |
| D16 | 更新与业务进程分离:发布密钥签名、本地策略、健康确认与回滚。已实现:宿主机 `moshiro-update`(Backend、脚本装的节点,清单里是镜像 digest),Quadlet 节点则靠退出后由 systemd 重新拉镜像;纯二进制节点的更新见[路线图](../roadmap.md) | 面板被攻破时也无法推送恶意二进制 |
| D17 | Probe 支持 OpenWrt:正式支持 `linux-musl-x64`、`linux-musl-arm64`;armv7 视工具链验证结果决定;MIPS 不支持,改为在局域网设备上运行 Probe | NativeAOT 不支持 MIPS |
| D18 | 默认测量端口不使用 443:控制 7443/tcp,QUIC Datagram 数据 7443/udp,TCP 数据 7444/tcp;443 只在 Agent 本地显式开启时才允许使用 | 避免与 VPS 上的真实服务冲突;结果会记录实际端口 |
| D19 | 展示查询最细 1s:最近 1 小时来自内存热层,更早的数据来自 10s/1m/5m/1h 各层,现场合并;时间段内的汇总由累加器精确合并得出 | 5 分钟、15 分钟这类视图足够精细且快 |
| D20 | 时序响应采用列式格式(`t[]` + `values{}`,时间轴使用 Unix 毫秒) | 体积小,可以直接交给图表库 |
| D21 | 国家和运营商只由 Backend 向 IP 归属服务查询(缺省 `https://ip.007995.xyz`,可关闭),只查公网地址;节点不访问任何外部服务;公开接口不返回 IP、城市、ASN | 客户的防火墙常常要审计节点的出站连接;访客拿不到敏感信息(有测试保证) |
| D22 | 构建信息(版本、tag、提交、构建时间)由节点自报,访客是否可见由设置 `site.show_build` 控制(默认关);提交号只在管理端 | 展示页可以选择不暴露版本;管理员能看出哪个节点没升级 |
| D23 | 零信任方向:信任锚点从 Backend 移到操作员 passkey(操作员集合,节点安装时固定创世指纹和属主);Probe 自己生成密钥;Agent 的 TCP 端口预握手敲门;敏感能力以后放进独立 extension,用属主 passkey 签名的能力令牌授权。不做进程沙箱(节点是 1 核 1 GB 的小机器),代码风险靠 PR 评审 | 做法和各缺口的状态见 [security.md](../security/security.md)、[access.md](../security/access.md) |
## 9. 未决事项
- 告警:首版不包含,是否预留 Webhook 见[路线图](../roadmap.md)。
- 与真实服务共用 443(例如 SNI 分流):默认使用非 443 端口,需要时再评估。
+3 -3
View File
@@ -73,9 +73,9 @@
- 令牌、`session_key` 和 MAC 不能出现在日志里;IP 地址可以记录。
- 每个会话或连接的生命周期事件记录为 Info;逐包事件不记录,只累加计数。
## 9. 纯采集边界(Probe/Agent 与 Fleet 节点侧)
## 9. 节点能力边界(Probe/Agent 与 Fleet 节点侧)
- 由 `BannedSymbols.Node.txt` 在编译期强制:禁止启动进程、动态加载程序集或本机库、使用 `HttpClient`、在配置目录以外写文件。唯一的豁免点是 `Fleet.Hosting.ConfigStore`,豁免必须写 `#pragma` 并附上理由注释。注册在 `fleet-enroll` 中完成,更新由节点宿主机上的更新器完成,常驻进程不需要这些能力。
- 由 `BannedSymbols.Node.txt` 在编译期强制:禁止启动进程、动态加载程序集或本机库、使用 `HttpClient`、在配置目录以外写文件。唯一的豁免点是 `Fleet.Hosting.ConfigStore`,豁免必须写 `#pragma` 并附上理由注释。注册在 `fleet-enroll` 中完成,更新由节点宿主机上的更新器完成,常驻进程当前不需要这些能力。
- 节点不能新增任何“由远端触发的非采集动作”对应的 RPC 方法。凡是新增 RPC 方法的 PR,都要在描述中说明它不会扩大权限边界。
- Backend 下发的任何参数都要先经过 `ParamSpec` 校验,再经过本地硬上限(`BudgetGuard`)检查,两道校验都不能省略。
- 日志中不能出现私钥、签名、`session_key`、令牌或 MAC。
@@ -84,7 +84,7 @@
- `backend/fleet/**` 不能引用任何 `MoShiro.*`,也不能出现产品相关的概念、命名或默认值(例如端口 7443、`moshiro` 字样),由 `ArchitectureTests` 和代码评审把关。
- 新增的公开 API 如果只有 MoShiro 使用,应先放在 MoShiro 中,等出现第二个使用者后再下沉到 Fleet。
- Fleet 永远不提供 exec、shell、文件读写或远程下发代码的方法,这条规则不接受例外。
- Fleet 不提供 exec、shell、文件读写或远程下发代码的方法;确需此类能力时走 extension 方案,见 [security.md](../security/security.md) 第 9 节。
- Fleet 公开 API 的变更,PR 描述需说明是否属于破坏性变更(不使用 PublicApiAnalyzers)。
## 10. 声明式扩展的写法
+1 -1
View File
@@ -25,7 +25,7 @@ moshiro/
└─ backend/ # 服务和节点 C# 代码(构建产物在 backend/artifacts/)
├─ Directory.Build.props MoShiro.slnx # 后端编译设置和后端目录解决方案
├─ .editorconfig BannedSymbols.txt # 风格与禁用 API(构建期强制)
├─ BannedSymbols.Node.txt # 仅用于常驻节点进程:纯采集边界
├─ BannedSymbols.Node.txt # 仅用于常驻节点进程:限制节点能力面
├─ fleet/ # 通用节点框架(不认识 MoShiro,以后可以拆成独立仓库)
│ ├─ src/ Fleet.Core Fleet.Identity Fleet.Rpc Fleet.Link Fleet.Hosting
│ │ Fleet.Enrollment.Client Fleet.Server.{Abstractions,Enrollment,Link}
+2 -2
View File
@@ -189,7 +189,7 @@ Linux 产物比 Windows 大,是因为 .NET 的加密、压缩、网络本机
管理端「添加 Probe」提供 OpenWrt · 原生二进制 命令,以 root 执行。支持 ARM64/aarch64 和 x86_64,不需要容器或 .NET;自动安装依赖、验签下载、固定属主并注册、配置 procd。身份在 `/etc/moshiro/`,更新策略在 `/etc/moshiro-native.conf`(`CHANNEL=dev|release`、`POLICY=auto|manual`、`INTERVAL=30`)。动态 musl 正式包仍需 `libopenssl3`,实际设备兼容性需实机验证。
带 `[CI]` 的 main push 发布 dev,稳定 tag 发布 release,沿用 `gitea_token` 和 `release_signing_key`;Gitea generic package `moshiro-openwrt` 需允许匿名下载。制品发布后,在线节点收到通知就唤醒更新器,另有 30 秒轮询兜底,不等待 10 分钟。更新器验签、防重放、原子切换完整版本,健康检查失败回滚;Probe 本身不下载、不执行命令。
带 `[CI]` 的 main push 发布 dev,稳定 tag 发布 release,沿用 `gitea_token` 和 `release_signing_key`;Gitea generic package `moshiro-openwrt` 需允许匿名下载。制品发布后,在线节点收到通知就唤醒更新器,另有 30 秒轮询兜底,不等待 10 分钟。更新器验签、防重放、原子切换完整版本,健康检查失败回滚;这些步骤由独立的更新器完成,不在 Probe 进程内执行。
```sh
logread -e moshiro
@@ -334,7 +334,7 @@ Probe 也上报同样的汇总数字(`probe.json` 的 `reporting.host_stats`
## Podman(Quadlet)的更新(Agent、Probe、Backend,推荐)
节点跑成一个 Podman Quadlet 单元(`packaging/quadlet/moshiro-agent.container`、`moshiro-probe.container`,Podman 4.6 及以上),更新是销毁容器、重新拉最新镜像再启动:节点自己不下载、不替换任何东西,只是有序退出;Quadlet 生成的 systemd 服务用 `podman run --pull always` 启动,所以被拉起时先从镜像仓库拉最新的镜像。默认自动更新:面板发现新版本后,节点运行满 10 分钟(防止重启循环)就自己退出重启;想改成在管理端点「更新」才更新,在 env 文件里写 `MOSHIRO_UPDATE_POLICY=manual`。
节点跑成一个 Podman Quadlet 单元(`packaging/quadlet/moshiro-agent.container`、`moshiro-probe.container`,Podman 4.6 及以上),更新是销毁容器、重新拉最新镜像再启动:该路径下节点本身不下载、不替换文件,只是有序退出;Quadlet 生成的 systemd 服务用 `podman run --pull always` 启动,所以被拉起时先从镜像仓库拉最新的镜像。默认自动更新:面板发现新版本后,节点运行满 10 分钟(防止重启循环)就自己退出重启;想改成在管理端点「更新」才更新,在 env 文件里写 `MOSHIRO_UPDATE_POLICY=manual`。
```text
CI 推镜像到 registry ─▶ 面板收到 CI 签名的清单 ─▶ 转发给节点(只是「建议」)
+6 -6
View File
@@ -63,7 +63,7 @@
- 一个能力只在一个地方声明:`Capability` 常量(`MoShiro.Backend.Application`),端点用 `.Requires(Capability.X)`;`Principal.Has(capability)` 是唯一的判断入口,会话和 API key 都走它。写蕴含读(`fleet.write` 带 `fleet.read`)。
- 对账测试:所有 `/api/admin` 端点必须声明能力,没声明测试就失败;OpenAPI 的每个操作带 `x-moshiro-capability`。
- 信任类是界面上特别标出的(吊销、账户管理),授权和其他能力一样靠 API key 或会话。真正由 passkey 签名保护的是操作员集合,那是节点自己验证的。
- 信任类是界面上特别标出的(吊销、账户管理),授权和其他能力一样靠 API key 或会话。passkey 签名保护的是操作员集合,由节点自己验证。
- 不能越权:创建 API key 时勾选的能力必须是创建者当前拥有能力的子集。
- Backend 更新确认:更新清单由 CI 的发布密钥签名(面板登记时验签),`seq` 防重放;确认只是让 Backend 有序退出并备份数据库,拉哪个镜像由 Quadlet 单元里写死的仓库和标签决定,面板决定不了。
@@ -74,7 +74,7 @@
| 密码 | PBKDF2;认领向导创建第一个管理员时设置 |
| passkey | WebAuthn,只支持 ES256,attestation 一律当 `none`;可以登录,也能给操作员集合签名(节点验证) |
- 两种方式的会话拿同样的能力。passkey 不是 Backend 用来拦操作的一道门,只用在节点自己会验证的事上,见第 5 节。
- 两种方式的会话拿同样的能力。passkey 只用在节点自己会验证的事上,见第 5 节。
- 认领向导(没有默认账户):面板里还没有任何用户时,打开 `/admin/` 进向导——用后端日志里的认领码(或 `MoShiro__ClaimCode`)创建第一个管理员(用户名和密码自己填),注册第一个 passkey,再用它给操作员集合签名建立信任根。谁先打开页面谁就能当管理员,所以要认领码;创建完码作废、入口关闭(`POST /api/admin/auth/setup` 之后返回 409)。后两步是引导不是门槛,可以稍后补;不注册 passkey、不建立信任根,就添加不了 Agent 和 Probe,只能进面板做基础管理,这是架构如此。之后新增的 passkey 不会自动成为操作员:要由现有操作员在信任页看清「新增了哪些」再签新版本,节点才认。
- 建议每个账户至少注册两个 passkey(手机加硬件密钥)。
@@ -89,7 +89,7 @@
## 5. passkey 用在哪里(没有提权窗口)
原则:passkey 不是 Backend 用来拦操作的一道门(Backend 被攻破时,它自己做的拦截也会被一起绕过),而是节点或密钥持有者需要它才能验证、解密的东西。
原则:passkey 只用在节点或密钥持有者自己验证、解密的场合。Backend 自己做的拦截在 Backend 被攻破时一起失效,所以不用 passkey 拦操作。
| 事 | 谁验证 | 要 passkey |
| --- | --- | --- |
@@ -141,18 +141,18 @@ extension(web ssh、文件、抓包,见 [security.md](security.md))现在
已经实现的部分
- 属主 `controllers` 是一组 passkey 凭证 id,必须是操作员集合里的凭证。由安装命令带来,节点注册时写进 `trust.json`,Backend 之后改不了。
- 每个 Agent 和 Probe 都必须有属主,至少一把——而且这是节点自己的要求,不是 Backend 的要求:节点启动时检查本地固定的属主,没有(或者属主的 passkey 已经不在操作员集合里)就不运行(共用 `Fleet.Hosting` 里的 `OwnerGuard`;配置 `fleet.require_owner`,`MOSHIRO_ALLOW_NO_OWNER=1` 放行)。面板被攻破也改不了。
- 每个 Agent 和 Probe 都必须有属主,至少一把。这由节点自己强制:节点启动时检查本地固定的属主,没有(或者属主的 passkey 已经不在操作员集合里)就不运行(共用 `Fleet.Hosting` 里的 `OwnerGuard`;配置 `fleet.require_owner`,`MOSHIRO_ALLOW_NO_OWNER=1` 放行)。面板被攻破也改不了。
- 添加节点时选属主:`POST /api/admin/join-tokens` 的 `controllers: [credential_id…]`,不给就是操作员集合里的全部凭证;给了就必须非空且都在集合里。还没有信任根或一把属主都没有时,面板提前拒绝创建加入码(400,字段 `controllers`),这只是尽早失败,真正的强制在节点;`MoShiro__RequireOwner=false` 可以关掉这个提前检查(只给没有信任根的老部署)。界面里是必选的「属主(passkey)」多选列表,只是选,不用触碰。
- 安装端也强制:`install.sh`、`install.ps1`、容器入口缺了 `MOSHIRO_GENESIS` 或 `MOSHIRO_CONTROLLERS` 就不装;`fleet-enroll --genesis` 不带 `--controllers` 也拒绝。
- 逆推出绑定关系:节点握手时把本地固定的属主(passkey 凭证 id)报给面板(hello 的 `owners`),面板顺着凭证 id 反查出它绑定在哪个账户的哪把 passkey 上,显示在「系统 → 版本」的「属主」列(对不上任何已注册 passkey 的显示「未知凭证」)。上报只是自述,真正的授权由节点自己校验 passkey 签名。
预留的设计(还没做)
- 谁来强制:节点本地,不是 Backend。 web ssh 这类会话要不要加密、要不要验证,由 Agent 和 Probe 自己要求。让 Backend 决定,被攻破的 Backend 一句「这次不用加密」就绕过去了。节点本地强制:没有操作员的现场证明不启动会话;会话密钥只和证明里的那把密钥协商,不和 Backend 转来的公钥协商;加密字段缺失就拒绝,不降级;能开哪些 extension、哪些 passkey 能授权,只看本节点属主。
- 谁来强制:节点本地。web ssh 这类会话要不要加密、要不要验证,由 Agent 和 Probe 自己要求。让 Backend 决定,被攻破的 Backend 一句「这次不用加密」就绕过去了。节点本地强制:没有操作员的现场证明不启动会话;会话密钥只和证明里的那把密钥协商,不和 Backend 转来的公钥协商;加密字段缺失就拒绝,不降级;能开哪些 extension、哪些 passkey 能授权,只看本节点属主。
- 证明的方向是反的:节点先出题(nonce 加它的临时公钥,用节点身份密钥签名),属主的浏览器把 nonce 和自己的临时公钥放进 passkey 断言的挑战里;节点验断言来自本节点属主、要求用户验证、nonce 是自己刚出的且没用过。Backend 仍然管谁有资格发起请求、审计、限流、转发密文。
- 交互式会话(终端、ssh)用现场签名的临时 ECDH 密钥协商,passkey 只签名授权,不参与解密,所以不支持 PRF 的 passkey 也能用,还有前向保密;之后双向是带序号的 AEAD。静态数据(抓包文件、会话录像)需要事后解密,才用属主记录里的接收者公钥加密(来自 WebAuthn PRF 或浏览器里不可导出的密钥)。
- 删除和使用分开:admin 能删节点、卸载 extension,读不到也用不了。改属主要现有属主的 passkey 签名(`ControllerUpdate`),账户被删、角色被改都改不了某个节点的属主。属主是 passkey 凭证,不是账户;一个人丢了全部属主 passkey,这个节点上的 extension 就没人能用了,所以添加时建议绑两把以上。
- 数据边界:监测数据(延迟、丢包、吞吐、事件、主机资源汇总)是明文,Backend 要做分析和展示;extension 会话和原始 payload 端到端加密,Backend 只存转发密文。读取权不是能力,是私钥。
- 数据边界:监测数据(延迟、丢包、吞吐、事件、主机资源汇总)是明文,Backend 要做分析和展示;extension 会话和原始 payload 端到端加密,Backend 只存转发密文。读取权由私钥决定。
## 10. 安装的供应链
+9 -9
View File
@@ -2,7 +2,7 @@
> 实现状态:协议方向声明表和卡口、Probe 自己生成密钥、预握手敲门、操作员集合与创世固定、服务器身份和安全链路、属主固定都已实现;测量数据的产生者签名、入库配额、Probe 拨号地址限制等还没做(第 3 节有逐项状态)。
Agent、Backend、Probe 之间互相不信任。这份文档回答三个问题:现在谁听谁的、被攻破之后最坏会怎样、怎么用密码学把信任从 Backend 身上拿走。账户、能力、passkey 和入网信任链的具体做法见 [access.md](access.md);纯采集的边界、本地硬上限和签名更新见 [architecture.md](../design/architecture.md) 第 5 节,这里不重复。
Agent、Backend、Probe 之间互相不信任。这份文档回答三个问题:现在谁听谁的、被攻破之后最坏会怎样、怎么用密码学把信任从 Backend 身上拿走。账户、能力、passkey 和入网信任链的具体做法见 [access.md](access.md);节点能力边界、本地硬上限和签名更新见 [architecture.md](../design/architecture.md) 第 5 节,这里不重复。
## 1. 范围
@@ -58,9 +58,9 @@ Agent、Backend、Probe 之间互相不信任。这份文档回答三个问题
没有问题的部分:UDP 端口对未认证的包静默丢弃,不回应(`DropInvalid`,有测试);Probe 本身没有 TCP 监听;任务有本地硬上限;更新清单有签名和 `seq` 防回滚。
## 4. 核心思路:信任锚点从 Backend 移走
## 4. 信任锚点
要防 Backend 被攻破,改变信任的内容必须由 Backend 之外的密钥授权。Backend 只搬运、计算和存储,不创造信任:
改变信任的内容由 Backend 之外的密钥授权,Backend 只搬运、计算和存储:
```text
① 发布密钥(CI) ──▶ 「跑什么代码」 清单签名、seq 防重放
@@ -107,7 +107,7 @@ Agent、Backend、Probe 之间互相不信任。这份文档回答三个问题
3. 违规处理:`RpcSession` 和服务端的 `NodeConnection` 对违规计数,超过上限(`MaxViolations`,默认 20)就断开连接;二进制通道也在服务端按声明核对。
4. 响应匹配:只接受对应未决请求 ID 的响应,不接受主动「响应」。
5. 对账测试:`Fleet.Tests/MethodDirectionTests` 覆盖卡口本身;`MoShiro.Backend.Tests/ProtocolDeclarationTests` 核对「每个方法常量都声明了」「文档里写了每个方法」「各角色该处理的方法都注册了处理函数」「每条声明的通道都有收件箱」。
6. 下行消息只是建议,不是命令:节点按本地硬上限执行或拒绝;任何带副作用的敏感能力都不走核心的下行消息,见第 9 节。
6. 下行消息是建议:节点按本地硬上限执行或拒绝;任何带副作用的敏感能力都不走核心的下行消息,见第 9 节。
### 6.3 各类下行消息怎么处置
@@ -118,7 +118,7 @@ Agent、Backend、Probe 之间互相不信任。这份文档回答三个问题
| 成员名册、对端公钥、Agent 地址 | Backend(G3、G6 未解决) | 能把恶意 Probe 加进 Agent 的白名单,或把 Probe 指向恶意 Agent |
| 任务集、测试请求 | Backend 计算;节点本地硬上限兜底 | 只能在本地速率和端口上限内发测量流量 |
| 重启计划(`moshiro.schedule`) | Backend;节点本地启动后 10 分钟内不触发 | 最多让节点每 10 分钟重启一次 |
| 未来的 `extension`(exec 等) | 属主 passkey 签名的一次性能力令牌,见第 9 节 | 什么都做不了 |
| 未来的 `extension`(exec 等,尚未实现) | 属主 passkey 签名的一次性能力令牌,见第 9 节 | 设计目标是没有属主签名就无法调用 |
## 7. 节点自己被攻破时(设计,未实现)
@@ -167,14 +167,14 @@ Agent、Backend、Probe 之间互相不信任。这份文档回答三个问题
## 9. extension:敏感能力的归宿
Agent 和 Probe 的核心永远是纯采集器。所有敏感能力(exec、web ssh 和终端、文件读写、抓包等)都不进核心,做成 extension,用户可选安装,默认没有,现在都没有实现。
当前 Agent 和 Probe 的核心只包含测量与上报。敏感能力(exec、web ssh 和终端、文件读写、抓包等)计划做成 extension,由用户选择安装,默认不启用,目前都没有实现。
- 独立进程,不是插件:单独的二进制和镜像,和核心之间没有进程内接口,核心不加载任何外部代码、不用反射和动态加载,核心的 NativeAOT 和「不能 exec」都不受影响。
- 独立进程:单独的二进制和镜像,和核心之间没有进程内接口,核心不加载外部代码、不用反射和动态加载,核心的 NativeAOT 和构建期限制不受影响。
- 授权只认节点的属主:每次调用的能力令牌必须由这个节点绑定的属主 passkey 签名,账户角色不赋予权力;管理员只能删除节点或卸载 extension。交互式会话端到端加密,Backend 只转发密文。属主和会话密钥的设计见 [access.md](access.md) 第 9 节。
- 独立身份和发布:extension 有自己的密钥、自己向 Backend 注册,在名册里是单独的成员(记录它依附的节点 ID);独立的镜像和清单(`component` 不同,例如 `ext-ssh`),装不装、升不升级都不影响核心。
- 独立命名空间:extension 的方法只能是 `ext.<名字>.*`,同样进方向声明表;核心的声明表里没有 `ext.*`,核心收到一律拒绝。
- 独立命名空间:extension 的方法只能是 `ext.<名字>.*`,同样进方向声明表;核心的声明表里没有 `ext.*`,核心收到会拒绝。
- 本地显式启用:extension 的本地配置里有 `enabled`、允许的操作员凭证、允许的命令集或目标范围,默认关闭。
- 不做的:核心里的任何 exec、文件写、加载 extension;用 extension 绕过核心的本地硬上限。
- 设计约束:不在核心里加入 exec、文件写或加载 extension 的能力;extension 不能绕过核心的本地硬上限。
## 10. 密码学原语