NSD · 架构与功能设计
本页从架构视角描述 NSD(控制中心) 的功能划分与设计特点,以
nsd仓库main(6bce2220,2026-09-30)为准。实施节奏与进行中的计划见 roadmap.md。
1. 角色定位
Section titled “1. 角色定位”NSD 是 NSIO 生态中唯一有状态的强一致组件,担任身份签发者、Access Grant 编译者、配置投影分发者与管理面,但不承载任何业务流量。它以单一静态二进制交付:axum 0.8 HTTP 服务、diesel-async + SQLite 存储、内嵌 React 19 管理台;TLS 由前置 Caddy 终结,Noise IK / QUIC 由两个 sidecar 终结为明文 HTTP 后转发到 nsd:8080。
部署形态有两种:
- 单实例(默认):SQLite 存储,配置推送、认证 nonce、限流、Envelope 防重放与二次验证授权都在进程内。
- 多实例 active-active:编译时启用
nsd-store的postgres特性改用 PostgreSQL(纯 Rust 驱动,与 SQLite 共用迁移);配置[fanout] backend = "redis"后,配置推送经 Redis pub/sub 送达所有实例上的订阅者,认证 nonce、限流、Envelope 防重放缓存与二次验证授权也改为 Redis 共享。事件签名仍在每个实例的出口完成,跨实例只传未签名的载荷。管理会话本来就存于数据库。四个环节均已交付,PLAN-021 尚未关闭。
拓扑上 NSD 与节点是”1 NSD × N 节点”的星型;一台 NS 本地可保存多个 NSD,但任一时刻只有一个 active,因此 NSD 之间既不联邦也不需要节点侧合并。SaaS 形态下一套 NSD 服务多家公司:org 是商业主体,realm(Network / Space)是技术隔离空间,org / realm 的 system-of-record 在 NSD,运营后台只持有指向 org 的指针。
核心不变式(Constitution §17,CI 强制):
- NSD 从不保存或传输节点身份或 WG 私钥。NSD 自有的签名密钥与集中管理的公网边缘证书密钥属于另外的密钥类别。
WgConfig序列化不含private_key。- 每张业务表从第一天起带
realm_id。 - 事件信封 Ed25519 签名。
- JWT 永不接受
alg: none。 - ACL 启动窗口 fail-closed。
- 三种传输共用一份事件解析器,传输 crate 之间互不依赖。
2. 功能模块划分
Section titled “2. 功能模块划分”2.1 领域模型与注册表
Section titled “2.1 领域模型与注册表”| 对象 | 归属 | 说明 |
|---|---|---|
Account(identity_users) | 部署级 | 人类身份;外部 IdP 按精确 (issuer, subject) 绑定,email 只是展示与邀请元数据 |
| Organization | 部署级 | 商业主体;organization_memberships 只持有组织角色与生命周期 |
| Network(Space / realm) | Organization | 技术隔离空间;Network-membership adapter 把一个活跃组织成员映射到每个可访问 Network;人类会话选定 Account + Organization + Network 三元组 |
| Device | Organization | 由长期 Ed25519 机器身份派生的安装身份,不带覆盖网地址;同一机器在同一 Organization 重新登记复用 Device ledger;显示名取自 Organization 内的标签或上报的可用主机名 |
Node(machines) | Network | (Network, Device, Account) 数据面 Profile;Node ID / Node IP / Node DNS 名都是 Network 范围;显示名只取 Network 范围内自己的标签,与 Device 名互不借用 |
| Service | Node | services.report 上报的稳定发布项;compact naming 分配短名 |
| Subnet | Network | 持久 32 位 Subnet ID,多网关 active / standby,personal_auto / policy_required |
| Gateway | 平台 / 租户 | NSGW 机器;平台公网池成员或租户 legacy roster;serves_all_realms 与 gateway_realms |
| Consumer | realm | Edge Auth 外部调用者,携带 API key / Basic / 签名 URL / mTLS 凭据,主体 external:consumer:<realm>:<ref> |
注册表只保存公钥(身份公钥与对等公钥)。地址由 NSD 分配:Node 在 CGNAT 100.64/10 池(PLAN-104 / 115:per-Network 分配、realm-hashed 起点、跨 Network 全局唯一、持久租约、释放后延迟重用),网关在 100.127.0.0/16。
2.2 身份与认证
Section titled “2.2 身份与认证”- 人类:本地账号 + OIDC / SSO(Deployment 级 provider),组织邀请显式指向稳定 Network ID,成员门户按最小权限暴露资源。
- 机器首次登记:设备授权流(RFC 8628 风格
device/code+device/token)由已登录 Account 批准,批准只授权精确的请求设备公钥(PLAN-112 / 113);一次性 invite 复用同一路径。租户 auth key 的创建 / 列表 / 分享路由与登记入口已移除(PLAN-111);auth_key字段只保留给网关的平台 enrollment token。 - 无头引导:
nsd bootstrap-platform-key与nsd bootstrap-approver-token从 stdin 读取调用方提供的秘密,按名字幂等地安装第一个平台 API key 与一个设备批准令牌(--rotate显式轮换;stdout 只输出一个 JSON 结果,退出码 0/1/2/3);每次调用是一个带审计记录的写事务,可与运行中的服务并存。批准令牌属于一个无头member账号,作用域device-approve只允许POST /api/v1/device/approve,用于自动化批准设备登记。 - 网关登记:平台运营方签发 enrollment token(只保留摘要 / 前缀、限次、过期、撤销、审计),仅对首次 gateway 登记有效;登记后成为未发布候选,进入公网池才可被投影(PLAN-105)。
- 运行期认证:Envelope v1(Ed25519 签名、nonce 滑动窗、±60 s 时钟);机器 JWT RS256(
{data_dir}/keys/jwt_signing.pem,默认 30 min),token_refresh事件滚动;WSS 相关 leg token 与 Exit 承载 token(aud=nsgw-wss)由 NSD 签发。 - 信任发现:
GET /api/v1/info只发布control_trust.v1(端点顺序、Noise X25519 key、QUIC SPKI current/next、Envelope thumbprint current/next);公开响应不再携带 legacy 顶层 key,authenticatedinfo.get在迁移窗口保留旧字段以兼容旧 NSGW。
2.3 策略:Access Grant 与 ACL 编译
Section titled “2.3 策略:Access Grant 与 ACL 编译”- 归一化模型:一条逻辑 Access Grant = 一行、一个 ID、修订、审批、审计、启用态;主体集合(user / group / machine / all / consumer / group-of-consumers)× 资源集合,集合内
any_of,一条 Grant 只含一个资源族。legacy 标量行归一化为单元素集合,未知 schema fail-closed。 - 资源族:私有 Service(声明协议 + 虚拟 listener)、Node(显式流量范围)、Subnet(只授权被路由子网,不授权路由 Node)、公网应用、Gateway Egress、Internet Exit(Exit / Egress 从不授权网关主机本身);legacy Site 与 NetworkSegment 只读保留。
- 系统级同 Account Grant:每个 Network 可见的关系型 Grant(
source.account == target.owner_account),授权同 Account Node 之间全双向 L3 与私有 Service。 - 跨 Account 默认拒绝:只有两端都证明安装了精确 enforced 快照后 pair 才可用;不支持、降级或过期的 Node 与跨 Account pair 隔离,不影响同 Account L3。
- 编译:
nsd-policy把身份与授权编译为自包含acl_config(bindings连接 → 主体,groups,hosts别名 → CIDR,rules仅accept);只有被某条规则引用的机器才进入bindings;空策略投影ExplicitEmpty而不是”全放”。同一模型也服务私有 Service 可见性、Node / Service ACL 投影、公网入口、Egress、Exit、审批、快照 / 回滚、模拟与引用校验。 - 策略版本:
policy_version事件下发,NS / NSGW 在上报中回显。
2.4 配置投影与分发
Section titled “2.4 配置投影与分发”- 订阅者分包:按机器种类(connector = NS,gateway = NSGW)与已协商能力选择事件子集;additive 投影(
NodeL3Config、SubnetRouteConfig/SubnetL3RouteConfig/SubnetLanDnsConfig、Short DNS v2、pair transport 权威、GatewayExitCandidates)只投给声明了对应能力的节点,旧节点保持 legacy 路径并 fail-closed。 - 签名与传输:每条
ControlMessage带 Ed25519 签名;SSE 是默认外壳,Noise / QUIC 只是解帧 sidecar;config.stream在 Envelope 中保留。 - 触发源:订阅建立、
services.report/gateway.report/ns subnet/ Exit 选择上报、管理员变更、Grant 修订、JWT 临近过期。 - relay 配对投影:为 realm 内每个授权 pair 生成
gateway_wg_relay/gateway_wss_relay,pair 折叠为确定的 opaque-splice 朝向;relay placement 由显式可运营组件决定(PLAN-124,见 §2.5 中继位置测量),O(N²) 广播正在有界化 / 分片 / 增量化(PLAN-125)。 - 多 realm 网关:网关 home realm 走配置流;served 的其他 realm 通过
gateway.realm_config_snapshot(9 类 per-realm body)拉取,per-realm 配置隔离(PLAN-099)。
2.5 网关编排
Section titled “2.5 网关编排”- 公网网关池:候选健康 = 近期签名
gateway.report+ 非 drain + 非空 WG 端点 + 已注册 WG 公钥;只有健康池成员参与 realm 网关选择与 served 配置展开。 - 中继位置测量:声明
relay_gateway_rtt.v1的节点收到gateway_config.probe_roster:最多四个可承担中继且正在接纳的网关,先列节点当前的 wg-relay 载体,再按 realm 顺序补足,最后一个名额每个 UTC 日轮换一次。声明relay_probe_set.v1的网关收到对应的gateway_wg_relay.probe_set,据此应答纯测量的反射探测;发给节点的副本不带它。NSD 在/api/v1/info与心跳响应中确认gateway_rtt.v1,宽松解析心跳中的gateway_rtt,忽略不在该节点当前名单中的网关,其余写入按 realm 隔离的machine_gateway_rtt(超过 15 分钟视为未知)。按 realm 的开关realms.relay_placement_rtt(默认关闭,nsd relay-placement-rtt --realm-id <ULID> --enabled <true|false>)打开后,pair 放置按”可达优先、档位成本之和、再 HRW”对接纳中的网关排序;realm 级排序与 realm 主网关回退不变。每次 realm 重建刷新nsd_relay_placement_rtt_*指标。 - Gateway Exit:候选投影(含运营可见名)、machine-default selection、fenced withdrawal(TASK-252:认证 clear API、幂等回执、opaque selection identity 防延迟清除误删新选择)、
GatewayExitNftConfig与 WSS 承载能力(exit-wss-carrier)与 token(PLAN-100)。 - Gateway Egress:显式 ACL 资源(PLAN-045)、realm-native 资源(PLAN-054)、
GatewayEgressWgConfig。 - 公网入口:inline 域名声明、HTTP 路由、公网 L4 端口、共享源 IP 准入、集中证书签发与
GatewayCertificateBundle分发(多网关共享主机证书)、客户端 CA bundle 与 CRL、Edge Auth 配置(GatewayAuthConfig)、匿名公网应用与 Edge Auth 互斥。证书默认用 DNS-01 签发,也可对能把公网 80 端口挑战路径转发到 NSD 的域名(包括 sslip.io 主机名)显式使用edge_certs.challenge = "http-01";HTTP 挑战只在 CA 签发成功后确认,域名释放后再次声明必须重新证明控制权。托管客户端 CA 轮换时使用不同的签发者名并在新证书中携带 AKI,使新旧客户端证书在过渡期共存;启用吊销检查时为每个受信签发者下发已签名的 CRL。 - 运营:
GatewayDrain预告、gateway.services_report接入本地 floor、NSGW 版本上报展示。 - 计量:接收
gateway.usage_report,写入不挂外键的计量账本(机器撤销不会级联删除计费记录);用量读数按网关给出新鲜度(NSD 最后一次接受上报的时间与所覆盖窗口的结束时间),租户GET /api/v1/usage与平台GET /platform/orgs/{id}/usage共用同一契约;/api/v1/platform/usage-ledger/orphans预览、…/purge带确认与审计地清理既无机器也无平台网关的孤立上报者记录。
2.6 Edge Auth(公网入口主体模型)
Section titled “2.6 Edge Auth(公网入口主体模型)”认证是绑定到 consumer 的凭据(API key / Basic / 签名 URL / mTLS,一个 consumer 可同时持有多种),授权永远是 group / consumer → resource;主体字符串 external:consumer:<realm>:<consumer_ref> 由 NSD 派生并投影,管理员不手写。OIDC 走 identity provider + IdP org / role 到 ACL 的名字映射(PLAN-093)。显式 JWT / Bearer 工作负载认证已从 Edge Auth 移除(PLAN-051)。凭据有完整生命周期(签发一次性返回、轮换、撤销),错误信息本地化且可执行。
2.7 管理面
Section titled “2.7 管理面”- 管理台(React 19 + Vite + Bun):登录、总览、Quick Start(下载客户端、两条独立首次成功路径、能力地图)、Devices(所有者、平台、客户端版本、活跃度、生命周期与 Network 成员资格筛选)、Nodes(节点清单,以及节点发布的子网路由:active / standby 发布者、映射前缀诊断、撤回状态)、Services、Access Grants / 策略编辑器、Groups、Users、SSO、Edge Auth、Gateways(NSGW 基础连接 + Internet exit + Gateway egress)、Topology、Usage、Plans 与订单历史、Developer → API access、Audit、Webhooks、Settings、成员 Portal;Device 与 Node 可分别改名;
en/zh双语,手机端有专门的资源摘要与导航;文档入口只从签名的docs_url解析。 - Admin API:RESTful + 少量 RPC 后缀,RFC 7807 错误,OpenAPI 3.1 导出并 CI 比对;完整 OpenAPI 文档需要管理员认证(匿名请求返回 401);游标分页;全局 / per-IP / per-subject 限流;所有管理操作经
nsd-admin自动审计。 - 自动化:Developer → API access 页面创建个人访问令牌(一次性显示秘密、按角色限定作用域、过期、撤销),并给出当前部署的 base URL 与不含秘密的验证命令;NSD 管理令牌与公网入口的工作负载凭据(API key、Basic、mTLS、签名 URL)在界面与文档中分开。另有 Webhook(HMAC、重试、运营硬化)、
nsdctlREST 客户端、由 OpenAPI 生成的 Go / Python / TypeScript SDK,以及基于 Go SDK 的terraform-provider-nsd(以nsda_管理令牌认证,管理 realm 范围的资源)。 - 凭据撤销:机器、管理 API 令牌与 Device 的撤销不要求二次验证,仍需 Bearer 认证、realm 守卫与审计。
- SaaS Platform API:orgs / realms / platform_users / platform_api_keys / platform_audit;后台运营门户只存申请漏斗与 org 指针,org 真相永远问 NSD。
- 发布分发:签名的 Quick Start release targets、短安装命令、移动端签名分发 consumer、tag 绑定的运行时 profile。
2.8 Network 套餐与容量
Section titled “2.8 Network 套餐与容量”- 套餐权威:商业套餐归属 Network,分 Free、Standard、Pro 三档;价格与容量由不可变、可配置的目录决定,目录发布有事务审计。Free 是默认套餐,无需开通,也不产生零元订单;组织级的 Trial / Team / Business / Enterprise 限制已退役。
- 容量:在资源写入的同一事务中按套餐限制成员、节点(不含网关设备)、Service、自定义用户组与保存的 ACL 数量,以及审计可查询天数;直接、批量、审批与恢复路径一致执行。邀请在创建链接或发信前就检查每个目标 Network 的成员容量。超出容量时只阻止新增,已有资源保留,撤销与删除始终可用;超限与临界状态在 Plans 与资源视图中给出可操作的提示。
- 订单:订单不可变、可重试;确认、权益与审计在一个事务内完成。Pro 按固定周期续期并保留已购权益,到期回落为永久 Free。中继与出口资源有单独的目录,与套餐购买分开。
- 结算:目前只有开发用的模拟结算(运营开关
commercial_simulated_checkout,默认关闭,从不实际扣款);真实支付与网关资源的结账尚未开放。
2.9 存储、可观测与安全
Section titled “2.9 存储、可观测与安全”- 存储:默认 SQLite(
libsqlite3-sys::bundled,外键默认开启),可选 PostgreSQL(见 §1 部署形态);时间戳迁移,软删除deleted_at,强类型 ID(ULID),主键加密 sealed 模式,每小时VACUUM INTO快照。keys/与数据库都是 load-bearing。启动时报告外键约束状态与按(child, parent)汇总的完整性检查结果。 - 可观测:
tracing+ OTLP(gRPC)+ Prometheus;结构化审计事件;性能基线。 - 安全:威胁模型、cargo-fuzz 目标、Dependabot、
cargo deny(禁 openssl / native-tls)。审计导出、告警与 Stripe 等出站客户端统一使用带超时的 rustls 客户端;平台网关的机器 Bearer 回退绑定到其注册身份密钥;X-Forwarded-For从右向左解析并跳过可信代理,管理 IP 允许名单与按 IP 认证限流使用真实客户端地址。
3. 与 NS / NSGW 的契约
Section titled “3. 与 NS / NSGW 的契约”3.1 机器侧端点
Section titled “3.1 机器侧端点”| 端点 | 用途 |
|---|---|
GET /api/v1/info | 未认证发现:类型、realm、auth methods、版本、control_trust.v1、control_endpoints、envelope_versions,以及对 gateway_rtt.v1 等能力的确认 |
POST /api/v1/control | Envelope v1 分发器(application/cbor),见下表 |
POST /api/v1/machine/register / auth / heartbeat、POST /api/v1/services/report、GET /api/v1/config/stream | legacy HTTP / SSE rail,与 Envelope 共享同一应用服务 |
POST /api/v1/device/code / token / approve | 设备授权流;approve 也接受 device-approve 作用域的 API 令牌 |
GET /.well-known/jwks.json | JWT 公钥 |
Envelope v1:请求签名域 nsio-ctrl-v1\0、响应签名域 nsio-ctrl-v1-resp\0;时间戳允许 ±60 s 偏差,nonce 防重放窗口 120 s(缓存上限 10 万条)。config.stream 的订阅 Envelope 放在请求头或 ?request= 查询参数中(≤ 16 KiB)。机器 JWT 默认有效 30 min。legacy POST /api/v1/machine/auth 的时钟偏差容忍为 300 s。
| 操作 | 调用方 | 内容 |
|---|---|---|
info.get | NS / NSGW | 签名的发现信息 |
machine.register / machine.auth / machine.disconnect | NS / NSGW | 登记、换取机器 JWT、主动下线 |
machine.heartbeat | NS / NSGW | 心跳;可携带 gateway_rtt、pair transport 快照、policy_version |
machine.posture / machine.events / machine.cert.sign.v1 | NS | 终端姿态、事件、用 CSR 换取短期客户端证书(私钥不离开节点) |
config.stream | NS / NSGW | 配置流订阅 |
services.report | NS | 服务白名单快照 |
peer_candidates.report | NS | direct P2P 候选 |
client.exit_node_selection.v1 / client.exit_node_selection.clear.v1 | NS | 选择 / 撤销 Gateway Exit |
short_dns.ack.v2 / node_l3.ack.v1 | NS | Short DNS 与 Node L3 投影的应用回执 |
subnet.create_or_join.v1 / subnet.list.v1 / subnet.leave.v1 | NS | 子网映射发布、列出、退出 |
gateway.report / gateway.services_report / gateway.usage_report | NSGW | 健康上报、本地 floor、计量 |
gateway.realm_config_snapshot(旧名 gateway.served_config 仍接受) | NSGW | served realm 配置快照 |
网关在最近 150 s 内有心跳才算在线(中继故障切换用);机器心跳间隔超过 300 s 后再次上报视为重新上线,触发一次对端重投影。
3.2 投影子集
Section titled “3.2 投影子集”| 订阅者 | 事件 |
|---|---|
| NS | wg_config(peer + wg_relay / wss_relay 指令)、proxy_config、acl_config、services_ack、gateway_config(含 probe_roster)、routing_config、dns_config、gateway_wg_relay / gateway_wss_relay、gateway_exit_candidates.v1、peer_candidates、node_l3_config.v1、subnet_route_config.v1 / subnet_l3_route_config.v1 / subnet_lan_dns_config.v1、Short DNS v2 快照、token_refresh、gateway_drain;能力门控的 policy_version、ca_bundle_update |
| NSGW(配置流) | 基础集 gateway_config、gateway_wg_relay(含 probe_set)、gateway_wss_relay;公网入口能力另加 gateway_http_config.v1、gateway_l4_map.v1、gateway_certificate_bundle.v1、gateway_client_ca_bundle.v1、gateway_auth_config.v1;公网入口 / Exit / Egress 角色另加 routing_config、acl_config、wg_config、gateway_exit_config.v1、gateway_egress_wg_config.v1;token_refresh、policy_version |
NSGW(gateway.realm_config_snapshot) | 每个 served realm 九类 body:wg_config、acl_config、routing_config、dns_config、gateway_config、gateway_egress_wg_config.v1、gateway_exit_config.v1、gateway_wg_relay、gateway_wss_relay |
兼容性硬规则:新字段 Option<T> + #[serde(default)];永不删除字段,只 #[deprecated] 并注明 sunset;register 时协商 protocol_version。
4. 设计特点
Section titled “4. 设计特点”4.1 控制面与数据面解耦
Section titled “4.1 控制面与数据面解耦”NSD 不在请求路径上;ACL 在端侧本地求值,从不回调 NSD。NSD 故障域被压缩到”无法变更策略 / 无法新建登记 / 机器 token 过期后无法续签”。
4.2 上报驱动的合成
Section titled “4.2 上报驱动的合成”管理员只编辑高层意图(Grant、域名、Exit / Egress 资源);Service 目录、路由、Short DNS、relay 配对、nft 投影由 NSD 据 NS / NSGW 的上报自动展开。NSD 不能凭空生成节点没有声明的能力。
4.3 能力门控的 additive 投影
Section titled “4.3 能力门控的 additive 投影”每类新投影都以能力协商为前提,旧节点保持 legacy 行为并 fail-closed;schema 只增不删,跨版本 rollout 是 server-first。
4.4 单一权威数据 + 逐事件签名
Section titled “4.4 单一权威数据 + 逐事件签名”配置真相在一个数据库,通过一个签名通道下发;传输外壳可换,事件签名不变。
4.5 多 realm 网关与 per-realm 隔离
Section titled “4.5 多 realm 网关与 per-realm 隔离”一台网关服务多个 realm 时,每个 realm 的配置独立成桶、独立签名;公网路由必须与所属 realm 一致才可激活。
4.6 私钥不可见性
Section titled “4.6 私钥不可见性”节点登记、登记存储与配置投影从不接受节点身份或 WG 私钥;WgConfig 没有私钥字段,并由 CI 测试守护。NSD 持有自己的控制面 / JWT 签名密钥,并可以管理公网边缘证书密钥;这些密钥需要按文档规定的受保护存储、分发、轮换与备份流程处理。
5. 与其他模块的边界
Section titled “5. 与其他模块的边界”NSD 不做的事情:
- 不承载业务流量、不参与包转发。
- 不持有节点身份或 WG 私钥,也不代表节点为业务报文签名;NSD 只为自己的控制消息与认证令牌签名。
- 不在连接路径上做实时 ACL 求值:只编译与下发。
- 不探测网关健康:健康只来自签名的控制面上报。
- 不创建或探测后端 Service:Service 只能由 NS 上报。
- 不镜像 org 真相到运营后台:后台只存申请与指针。
6. 协议层组件清单
Section titled “6. 协议层组件清单”| crate | 职责 |
|---|---|
nsd | 主二进制,装配 API、telemetry、存储;运维子命令(bootstrap-platform-key、bootstrap-approver-token、relay-placement-rtt、restore 等) |
nsd-core | 强类型 ID、Secret<T>、策略文档模型、基础错误 |
nsd-proto-bridge | 上游 wire 类型再导出,CI 无漂移快照 |
nsd-store | diesel-async 仓储与迁移;默认 SQLite,postgres 特性启用 PostgreSQL |
nsd-auth | JWT / JWKS、设备授权、enrollment token 生命周期 |
nsd-registry | Account / Organization / Network / Device / Node / Service / Gateway CRUD |
nsd-policy | Access Grant 归一化、ACL 编译、各类投影合成(含 Exit / Egress / 公网入口) |
nsd-distribute | 配置流总线(进程内或经 Redis 跨实例扇出)、Ed25519 签名信封、公网入口投影 |
nsd-api | axum 路由、Envelope 分发器、SSE、Admin / Platform API、OpenAPI 导出 |
nsd-admin | audit_wrap 管理操作层 |
nsd-telemetry | tracing + OTLP + Prometheus 初始化 |
nsd-cli | nsdctl |
nsd-noise-proxy / nsd-quic-proxy | Noise IK / QUIC 控制传输 sidecar |