NSIO Docs
NSIO Docs
首页

NSIO Next · 当前开发基线

统一 L3 + L4 架构
产品能力目录与实现闭环
功能闭环总账
产品闭环与功能实现
实施蓝图与交付契约
发行、升级与卸载
跨组件共享契约
控制资源生命周期
控制投影与 Runtime Protocol
数据面协议
Grant 与策略编译

组件实现规格

ns · 统一节点运行时
Client · App、CLI 与门户
NSD · 身份与控制面
NSGW · 增值数据面
身份、登录与会话
注册、准入与配额协议
商业模型与 Entitlement
采购、计费与发票
服务运营与 SLA
备份与灾难恢复
信任、合规与数据权利

Last Updated: 2026/8/8 22:11:23

Previous Page身份、登录与会话
Next Page商业模型与 Entitlement

#NSIO Next · 注册、准入与配额协议

状态:实现依据。本文定义 NSIO Next 原生 Enrollment v1 的状态机、数据结构、事务、API、错误码和界面投影。Next 从零创建全部对象,不读取、导入或兼容旧 NSIO 的身份、密钥、Auth Key、设备、策略和状态目录。

人类账号、登录方式、PKCE 回调、会话、MFA 和身份撤销以身份、登录与会话实现基线为权威。本文从“取得已认证身份 Proof”开始,登录成功仍不等于设备已获准入。

系统对象与安全边界见统一 L3 + L4 架构,商品与 Entitlement 语义见商业模型与 Entitlement,用户完整操作见产品闭环与功能实现规格。

#0. 协议目标与硬性不变量

Enrollment 只回答“这个设备是否可以成为某个 Network 的节点”,不回答“这个节点可以访问什么”。协议必须同时满足:

  1. Proof、Policy、Grant 分离。 Proof 证明身份或工作负载;Policy 结合目标、组织规则和 Entitlement 作决定;Grant 是短期、一次性的提交凭据。
  2. **注册不创建访问 Grant。**提交只创建 Device、Node Membership 和获批 Capability。已有 Network 模板可以随后匹配新 Membership,但审计必须归因到模板,不得归因到注册动作。
  3. **私钥不出设备。**服务端只接收 Device、Node 和 WireGuard 公钥;私钥不进入请求、日志、审计、诊断或备份。
  4. **Request ID 不是能力。**轮询、取消和提交都需要对应 Device Key 签名;只知道 request_id 不能观察或抢占注册。
  5. 审批决议与 Grant 生命周期分离。approved 可以跨周末保持,Grant 只在客户端在线轮询时惰性签发五分钟。
  6. **所有有效期由服务端判断。**客户端倒计时只用于提示,不决定 Request 或 Grant 是否有效。
  7. **读取失败不等于拒绝或额度不足。**身份源、Entitlement、存储或投影故障分别返回可重试错误,不生成 denied、空候选或 quota reached。
  8. **一个人增加设备不重复占 Human Seat。**只有此前未占席位的用户首次激活时才申请席位;第二台、第三台设备只检查相应设备/资源配额。
  9. 高权限能力单独审批。service_host、subnet_router、exit_provider、ingress_connector 和受管配置不能因普通 endpoint 注册自动获得。
  10. **Next 自身可演进,但不降级安全。**未知可选字段按同 major 的规则处理;未知 major 显式拒绝。Capability discovery 只能关闭可选功能,不能跳过签名、绑定、确认或授权。

#1. 参与者与注册入口

场景用户入口Proof目标来源是否可审批
桌面/移动 App“登录并连接”Identity Authority,系统浏览器 + PKCE登录后可进入的 Network按 Organization 策略
有浏览器 CLIns up本地浏览器 + PKCE单个自动选;多个在浏览器选择按策略
无头 CLIns up --headlessDevice Code手机确认页选择按策略
受邀成员邀请链接后正常登录邀请 + 已认证人类身份邀请绑定的 Organization/Network邀请策略决定
服务器/路由器ns up --enrollment-key ...Enrollment KeyKey 绑定的 Organization/NetworkKey 策略决定
企业 MDM管理配置 + 首次用户登录MDM Attestation + SSO管理配置限定必须
工作负载自动化 APIWorkload Attestation工作负载策略限定按策略

普通员工界面不显示“登录、Auth Key、Network ID 三选一”。App 只有“登录并连接”,网页首版展示邮箱密码、Google、GitHub 和企业 SSO;服务器文档只有 Enrollment Key;无头环境由 ns up 自动进入 Device Code。

#2. Enrollment Request 状态机

#2.1 状态定义

状态含义是否终态待审批列表可见
created请求已持久化并绑定 Device 公钥,尚未开始 Proof否否
proof_pending等待 Identity Authority、Device Code、邀请或 Key 证明完成否否
target_pending人类身份已验证,但必须在允许的多个 Network 中选择一个否否
evaluation_pending服务端正在评估身份、目标、策略和 Entitlement否否
approval_pending自动评估通过,等待管理员审批否是
approved审批决议有效,等待在线客户端取得 Grant 并提交否否;审批历史可见
evaluation_error依赖故障,无法得出允许或拒绝结论否否;安全控制台可见
committedDevice/Membership 事务已完成是否;审批历史可见
deniedPolicy 或管理员明确拒绝是否;审批历史可见
expiredRequest 服务端 TTL 到期是否;审批历史可见
cancelled发起设备主动取消是否;审批历史可见

evaluation_error 不是 denied。它可以在依赖恢复后回到 evaluation_pending,且在此期间不得创建正式资源或进入审批队列。

#2.2 权威转移表

当前状态事件/操作者前置条件下一状态同一事务内副作用幂等与错误
-request.create / 设备Device 公钥格式有效;来源/IP/全局及可确定的 Organization 在途上限未超;协议 major 支持created保存 Request、Proof 类型、Device 公钥 hash、客户端 capability digest、TTL;写创建审计同一 idempotency_key + device_pub_hash 返回同一 Request;不一致返回 idempotency_conflict
createdproof.start / 设备Request 未过期;签名匹配 Device 公钥proof_pending创建 PKCE/Device Code/Key challenge;记录尝试次数重复调用返回同一未过期 challenge
proof_pendingproof.verified / 身份服务Proof 有效,且绑定本 Request、Device 公钥和 noncetarget_pending保存最小身份引用和允许目标摘要仅人类身份且允许目标多于一个
proof_pendingproof.verified / 身份服务邀请或 Enrollment Key 已绑定唯一目标,或人类仅有一个允许目标evaluation_pending保存目标 Organization/Network、proof_source_id、federation_binding_id目标不存在返回 target_not_allowed,不猜测其他目标
proof_pendingproof.rejected / 身份服务Proof 明确无效、身份不匹配、Key/邀请撤销或尝试次数耗尽denied保存稳定拒绝码;清理 challenge;标记 staging key 可销毁依赖故障不能走此分支,必须进入 evaluation_error 或保留等待
target_pendingtarget.select / 已认证用户用户会话与 Proof 主体相同;目标在允许集合;设备签名有效evaluation_pending保存 Organization/Network;冻结目标,后续不可改更换目标必须取消并新建 Request/Device Key
evaluation_pendingpolicy.evaluate / NSD身份、目标、Device 公钥、Policy、Entitlement 和请求 revision 可读approval_pending保存 canonical 决策摘要、请求 Capability、配额影响、审批到期时间需要人工审批
evaluation_pendingpolicy.evaluate / NSD同上且允许自动批准approved保存 approval revision、批准者=policy、有效期;不签发 Grant不占用 quota,不创建 Device/Membership
evaluation_pendingpolicy.evaluate / NSD明确不符合 Policy 或能力范围denied保存稳定拒绝码与最小原因;清理 challenge不创建正式资源
evaluation_pendingpolicy.error / NSDEntitlement、身份源、存储或策略编译无法得出结论evaluation_error保存 retryable code、依赖和 backoff不得伪装成 denied/quota reached
evaluation_errorevaluation.retry / NSD/设备Request 未过期;依赖恢复或用户明确重试evaluation_pending增加 evaluation attempt;清除旧临时错误自动重试有界,超过上限等待用户重试
approval_pendingapproval.approve / 管理员管理员有审批角色;预览 digest 未变;Request 未过期approved保存审批人、理由、revision、有效期;不签发 Grant重复同一 approval revision 返回当前状态
approval_pendingapproval.deny / 管理员管理员有审批角色denied保存理由并写审计终态
approvedrequest.poll / 设备Device 签名有效;Request/审批未过期;无有效 issued Grantapproved惰性签发一个 Enrollment GrantRequest 状态不变;见 Grant 状态机
approvedenrollment.commit / 设备Grant、Request、Device key、目标、Policy、Entitlement、quota 全部在提交事务中重新成立committed消费 Grant;创建/匹配 Device;创建 Membership、Node keys、Capability、quota allocation 和审计同一 Request 重试返回同一结果;不同提交 digest 返回 commit_conflict
任一非终态request.cancel / 设备Device 签名有效cancelled撤销 issued Grant;释放 Reservation;标记 staging key 可销毁重复取消幂等
approval_pending/approvedapproval.revoke / 管理员/Policy管理员撤回、Proof/Key/Binding 撤销或目标 Policy 失效denied撤销 issued Grant;释放 Reservation;记录原因提交必须重查,不能只验未过期
任一非终态request.expire / 服务端now >= request.expires_atexpired撤销 issued Grant;释放 Reservation;记录过期时间只由服务端判定

#2.3 目标选择规则

  • Proof 完成前,NSD 不假设用户属于哪个 Organization/Network;
  • 邀请与 Enrollment Key 已绑定目标,跳过 target_pending;
  • 已认证人类用户只有一个允许目标时自动进入评估;多个目标时必须选择显示名称,不能要求输入 ID;
  • Device Code 的目标选择发生在手机确认页,因为 CLI 尚无已认证用户会话;
  • 目标一旦冻结不可修改。跨 Organization 或改选 Network 必须创建新 Request 和新的随机 Device Key;
  • 同一 Organization 内,已存在 Device 后新增另一个 Network Membership 仍走新的 Request,但可以在提交时匹配现有 Device。

#3. Enrollment Grant 状态机

#3.1 状态定义与字段

Grant 是 Request 的子对象,状态只有:

issued -> consumed
       -> expired
       -> revoked

每个 Grant 至少绑定:grant_id、request_id、generation、device_pub_hash、organization_id、network_id、principal_id、approved_capabilities、approval_revision、entitlement_revision、issued_at、expires_at、state 和签名 key ID。

#3.2 转移表

当前状态事件前置条件下一状态规则
-grant.issueRequest=approved;客户端带 Device Key 签名轮询;当前不存在未过期 issued Grantissuedgeneration = previous + 1;TTL 默认 5 分钟
issued重复轮询Grant 未过期、未撤销issued返回同一 grant_id + generation;不得每次重签成新 Grant
issuedcommit.success提交事务全部通过consumed与 Request=committed、资源和 allocation 同事务
issuednow >= expires_at服务端时间到期expiredRequest 仍为 approved 时,下次签名轮询可签发下一代
issuedgrant.revoke审批撤销、Proof/Key/Binding/Policy/Entitlement 失效或安全管理员操作revoked必须先于或同事务更新 Request 决议;旧 Grant 不得提交

任一 Request 同时至多存在一个 issued Grant。数据库使用 (request_id) WHERE state='issued' 的唯一约束或等价锁保证。generation 达 5 触发告警与退避,达 20 触发异常限速并要求重新证明;Request TTL 仍是最终上限。

客户端只用 grant_id + generation 判断是否同一 Grant,不比较签名字节,因此协议不依赖签名算法是否产生确定性字节。

#4. Device、密钥与所有权

#4.1 密钥分层

密钥作用域生成时机服务端持有生命周期
Staging Device Key单次 Enrollment Request/目标 Organization创建 Request 前仅公钥终态清理或 commit 晋升
Device Key单个 Organizationcommit 晋升仅公钥Device 生命周期,可轮换/撤销
Node Key单个 Network Membershipcommit 前由设备生成仅公钥Membership 生命周期
WireGuard Key单个 Membership/epochcommit 或轮换时生成仅公钥短于 Device,可独立轮换

同一物理设备加入另一个 Organization 时必须生成无派生关系的新 Device Key。Installation Context 只管理本地多个 Organization 配置,不能作为密钥派生种子或跨组织关联标识。

#4.2 Staging Key Store

  • macOS 使用 Keychain,Windows 使用 DPAPI,Linux 优先 Secret Service;无头 Linux 使用 0700 目录和 0600 原子文件;
  • staging 与正式 Device Key 使用相同保护级别,写入必须防 symlink、先临时文件后原子替换;
  • 保存 request_id、控制端身份、Device key handle、device_pub_hash、创建时间和服务端 expiry,不保存 Proof token;
  • 同一 Request 恢复必须复用原 staging key,不能重新生成;
  • 从未得到服务端确认的 local draft 最长保留 24 小时;
  • 已确认 Request 的本地最长保留时间为 min(组织审批 TTL, 7 天硬上限) + 24 小时清理余量;
  • 收到 committed 时原子晋升,收到 denied/expired/cancelled 时立即销毁;启动时扫描孤儿和损坏记录并隔离清理;
  • 私钥路径、内容、hash 和导出值不得进入普通日志。

#4.3 所有权与运行主体

首版只保留两个概念:

对象字段决定什么
Devicelifecycle_owner_type/id谁能撤销、重置、转移资产和执行 MDM 生命周期
Node Membershipprincipal_type/id本次 Network 运行身份,Grant 用它做主体匹配
  • 公司笔记本:Device owner=organization,Membership principal=user:alice;
  • BYOD:Device owner=user:alice,Membership principal=user:alice;
  • 服务器:Device owner=organization,Membership principal=service_identity:web-prod;
  • MDM 预注册只创建 Organization-owned Device,不创建普通数据 Membership;用户首次登录后才创建以该用户为 principal 的 Membership;
  • 离职时撤销 Membership,Organization-owned Device 保留;重新指派创建新 Membership 并轮换 Node/WireGuard key。

不预先加入 assigned_principal 和 runtime_principal 两个始终相同的字段。共享终端形成真实需求后再单独设计会话主体。

#5. Proof、Policy 与审批

#5.1 Proof 类型

类型适用对象必须绑定明确禁止
interactive_identity人类设备Identity Authority 的 issuer/subject、Session、Request nonce、Device 公钥把浏览器 cookie 或 Access Token 单独当 Device 凭据
device_code无头人类设备user code、Device 公钥、确认会话、校验短语用于批量服务器或绕过 MFA
invitation受邀成员邀请身份/email、Organization/Network、一次性 nonce转发给另一个身份使用
enrollment_key服务器/路由器/自动化Organization/Network、能力上限、标签上限、次数、expiry终端用户设备、永久管理凭据
mdm_attestation受管企业设备MDM issuer、设备证明、Organization无用户登录即获得普通数据 Membership
workload_attestationCI/K8s/云工作负载workload identity、环境声明、目标范围复用为人类身份

Enrollment Key 注册后即消费一次使用次数。后续受管配置依赖节点本地、可撤销的 ManagedConfigAuthorization,不得继续使用已消费 Key。

#5.2 Device Code 确认页

确认页必须显示:设备显示名、操作系统、请求时间、短公钥指纹、双端相同校验短语、Organization、Network 和请求 Capability。按钮只有:

  • 确认这是我的设备:完成身份 Proof 和目标选择;
  • 这不是我发起的:立即拒绝 Request,记录 enrollment.phishing_reported,并对来源、IP、Organization 和 Device key 做短期限速。

Device Code 默认 TTL 不超过 5 分钟;同时按用户、IP、Organization、Device 公钥和平台全局在途数限速。企业策略可以禁用 Device Code。高权限 Capability 不得在普通 Device Code 页批准。

Network 策略至少提供 interactive_enrollment=allowed|approval_required|disabled 与 allowed_ownership=user|organization|both。生产服务器 Network 推荐禁用 Device Code 并只允许 Organization-owned Enrollment Key/Workload Attestation,避免把生产节点错误绑定到运维人员生命周期。

#5.3 审批页

普通管理员只看到 approval_pending。详情必须显示:

  • 已验证主体和 Proof 来源;
  • Device 显示名、OS、Device 公钥短指纹和所有权;
  • 目标 Organization/Network;
  • 请求 Capability 与每项风险说明;
  • 是否新占 Human Seat、Managed Resource 或其他 quota;
  • Request/审批到期时间;
  • Policy 命中规则、Entitlement revision 和 canonical preview digest。

批准时必须提交当前 preview digest;数据已变化返回 preview_changed,管理员重新查看后再确认。审批动作只把 Request 置为 approved,不签发 Grant、不占 quota。

#5.4 Federation Binding 生命周期

派生 Membership 保存 proof_source_id 和 federation_binding_id。管理操作分为:

  1. Disable:停止新的 Proof、注册和续期,已有 Membership 按原生命周期处理;
  2. Disable and revoke:先展示影响,再撤销派生 Membership、peer 授权和 issued Grants;
  3. Delete:仅在没有派生对象时允许。

误删配置不能自动级联中断生产;破坏性撤销必须是独立、可预览、可审计的动作。

#6. 权威数据结构

以下是逻辑 schema。实现可使用 PostgreSQL 或 SQLite 的等价类型,但约束与事务语义必须一致。

#6.1 Enrollment 核心表

#enrollment_requests

字段约束/含义
request_id随机不可枚举主键
protocol_major/minorEnrollment 协议版本
state§2.1 枚举
proof_kind§5.1 枚举
proof_source_id可空;验证成功后写入
federation_binding_id可空;联合身份来源
device_pub / device_pub_hash创建后不可变;hash 唯一绑定签名
principal_type/idProof 后写入;最小稳定引用
organization_id/network_idtarget 冻结后写入;不可改选
requested_capabilitiescanonical 集合,创建后不可扩大
client_capability_digest认证协商 transcript 的摘要
policy_revision / approval_revision决议依据
decision_digest审批预览和提交重查依据
error_code/error_detail结构化最小错误,不存敏感 token
idempotency_key创建幂等键,绑定 Device 公钥 hash
created_at/expires_at/updated_at服务端时间
revision乐观并发版本

索引/约束:(idempotency_key, device_pub_hash) 唯一;状态转换使用 compare-and-swap revision;终态不可回到非终态。

#enrollment_grants

字段约束/含义
grant_id随机主键
request_idRequest 外键
generation每 Request 单调递增
stateissued/consumed/expired/revoked
device_pub_hash必须等于 Request
organization_id/network_id/principal_id冻结目标与主体
approved_capabilities不超过 Request/Policy
approval_revision/entitlement_revision提交重查依据
claims_digest/signature/key_id服务端签名材料
issued_at/expires_at/terminal_at服务端时间

约束:(request_id, generation) 唯一;每 Request 至多一个 state=issued;consumed 与资源提交同事务。

#enrollment_approvals

字段约束/含义
approval_id/request_id一次决议记录
decisionapproved/denied/revoked
actor_type/id管理员或 Policy engine
preview_digest防止确认旧预览
reason管理员可读、审计可见
created_at/expires_at/revoked_at生命周期
revision单调递增

#6.2 正式资源表

表关键字段/规则
devicesorganization_id, device_pub, lifecycle_owner_type/id, display/posture/state;Device 公钥只在本 Organization 唯一
node_membershipsdevice_id, network_id, principal_type/id, Node/WG 公钥、Node IP、state、proof 来源;同一 Device 可有多个 Network Membership
node_capabilitiesmembership_id, capability, desired, approved, effective, approval source/revision
quota_allocationsresource type、resource ID、quota subject、数量、来源 Request/Reservation、状态;所有 used 必须能由 allocation 重算
audit_eventsactor、action、target、result、request/grant/revision、时间和最小 metadata;不可记录私钥或完整 Proof token

Device 生命周期默认为 active -> offline -> dormant。dormant 从活跃 peer map 移除但不撤销 Device;peer 授权使用短期 not_after 自然失效。只有显式管理员动作、安全事件、ephemeral lease 或明确配置的 inactivity policy 才进入 revoked。

#7. Entitlement 与 Quota 投影

#7.1 权威表

表关键字段角色
entitlement_snapshotssubject/deployment、entitlement ID/revision、schema、digest、issuer、签名、issued/expiry/grace、payload不可变的验签声明历史
entitlement_headssubject/deployment、current snapshot、revision、digest、state、grace唯一当前指针与防回滚高水位
quota_usagesubject/deployment/resource type、used、reserved、limit_value、admission_state、enforcement、entitlement revision、usage revision低频激活事务产生的准入投影
quota_allocationsquota key、resource ID、amount、source request/reservation、stateused 的可解释明细
quota_reservations§8 批量预留字段批量注册短期占位

quota_usage.limit_value 是当前 Effective Entitlement 的事务化投影,不是第二权威。运行时不得由调用方传入 limit;支持工单可用 derived_from 追溯订阅,但不能据此做准入。

#7.2 Entitlement 激活事务

单写者、低频事务按固定顺序:

  1. 锁定 entitlement_heads(subject, deployment);
  2. 验签、验证 schema major、issuer、deployment、时间和单调 revision;拒绝合法签名的旧 revision 回放;
  3. 写不可变 snapshot;
  4. 计算“新 bundle 必需 quota ∪ 新 bundle 可选 quota ∪ 旧 head 已投影 quota”;
  5. schema major 定义的 required quota 缺失时拒绝整份 bundle,head 不变;bundle 不允许自行把未知键标成 required;
  6. 对存在 quota upsert 当前 limit_value/admission_state/enforcement/revision;
  7. 对旧 head 存在、新 bundle 缺失的可选 quota 写 tombstone:admission_state=disabled、limit_value=0、保留 used/reserved、revision 更新为当前;
  8. 检查 active Reservations;能力被移除时 revoke 并释放未消费量;新 limit 能容纳 used + reserved 时更新 Reservation revision,否则 revoke 受影响 Reservation、释放未消费量,并向批次返回 quota_reservation_invalidated;Entitlement 激活本身继续完成,不能被旧预留永久卡住;
  9. 最后更新 head 指针和防回滚高水位;
  10. 写 entitlement 激活审计和 outbox。

验签通过的最后有效声明及签名必须落盘。重启从副本恢复,仍受 grace_until 约束并标记 stale;副本篡改或无可用声明时不得伪装成零额度。

#7.3 准入分类

所有计数使用同一公式:

used + reserved + delta <= limit_value

limit_value = NULL 表示无限,不表示读取失败。条件更新影响零行后必须在同一事务中分类回读:

事实错误码retryable
required quota 不存在entitlement_missing_required_quota否;声明无效
没有有效 head/超过 graceentitlement_unavailable是
quota revision 与 head 不一致entitlement_projection_stale是,只重试一次
一次重试后仍不一致entitlement_projection_inconsistent否;运维修复
admission_state=disabledfeature_not_entitled否
enforcement 无法识别unsupported_entitlement_enforcement否
active 且超过 limit<resource>_limit_reached否,指向管理端

注册在同一事务内读取 head、执行条件更新和分类。projection_stale 最多重试一次,禁止无限循环。

#8. 单台与批量配额事务

#8.1 单台 commit

单台新增资源在 Enrollment commit 事务中用 delta=1:

  1. 锁定 Request/issued Grant/Entitlement head/quota row;
  2. 重新校验 Grant、审批、Proof source、目标、Policy 和 capability;
  3. 若用户此前没有 active Human Seat allocation,原子申请 human_seats delta=1;已有则跳过;
  4. 对本次新增 Managed Resource 或其他资源执行 used + reserved + 1 <= limit 的条件更新,直接 used += 1;
  5. 创建 allocation 和正式资源;
  6. consume Grant、Request=committed、写 audit/outbox;
  7. 任一步失败全部回滚。

锁顺序全系统固定为:Request -> Grant -> Entitlement head -> quota rows(按 resource type 排序)-> Reservation -> resource rows。

#8.2 批量 Reservation schema

quota_reservations
  reservation_id
  subject_id
  deployment_id
  resource_type
  requested
  consumed
  state                 active | completed | expired | cancelled | revoked
  entitlement_revision
  batch_id
  idempotency_key
  created_at
  expires_at
  revision

约束:consumed <= requested;同一 batch_id + resource_type 只有一个 active Reservation;过期/取消/撤销后不可消费。

#8.3 批量流程

  1. Policy 和人工审批完成、批次机器清单固定后才申请 Reservation;审批等待期间不占 quota;
  2. 创建时调用方提交 requested=N 和预期窗口,服务端原子执行 reserved += N 并写 Reservation;不能循环 N 次抢占;
  3. 默认 TTL 60 分钟,调用方可请求更短或更长窗口,服务端硬上限为创建后 2 小时;
  4. 每台机器用独立短事务消费一个单位:reserved -= 1、used += 1、consumed += 1,并创建 allocation/resource、consume 对应 Grant;
  5. 批次完成时把状态置 completed,并原子退还 requested - consumed;
  6. 到期、取消、撤销时同样退还未消费量;回收任务幂等,不能重复减 reserved;
  7. 管理端显示“已注册 X/N,剩余 M,预留将在 T 后释放”,并提供在 2 小时硬上限内续期或提前结束;
  8. Reservation 只保留容量,不授予注册权限。每台提交仍重新校验 Request、Grant、身份、目标、Policy 和能力。

SQLite 下预留和逐台转换都使用短 BEGIN IMMEDIATE 事务、busy timeout 与服务端写队列;PostgreSQL 使用行锁/条件更新。两者语义相同,只是吞吐不同。

#8.4 对账与人工修复

周期任务比较 used/reserved 与 allocations/active reservations 的权威集合。发现漂移时:

  • 只创建 reconciliation finding 和告警,不静默改账单、删除资源或放宽准入;
  • 管理员使用“重算并修正”查看差异、修正前后值、受影响资源和账单影响;
  • 提交需要 preview digest、原因和有权限的 actor;
  • 修正计数/allocation 并写审计;已出账单通过 correction event 调整,不重写历史账本。

#9. Enrollment API v1

#9.1 通用约定

  • 基础路径:/api/v1/enrollment;
  • JSON 字段采用 snake_case,时间为 RFC 3339 UTC,ID 为不可枚举稳定 ID;
  • 所有 mutation 接受 Idempotency-Key;
  • 设备签名覆盖 method、canonical path、body digest、request ID、timestamp、nonce 和协商 transcript digest;
  • 服务端允许有限时钟偏差,但 replay nonce 必须一次性;
  • 成功响应带 request_revision,mutation 使用 If-Match 或等价 revision;
  • 错误统一为 {code, message, retryable, action, details?, trace_id};message 不参与客户端分支。

#9.2 发现与创建

方法路径认证作用
GET/.well-known/nsio-capabilities无,仅预检返回各协议面支持范围、ETag;Cache-Control: no-cache, must-revalidate
POST/requestsDevice 公钥自签创建证明 + 限速创建 Request,提交 proof kind、device pub、显示信息、客户端 capabilities、可选 invite/key proof
POST/requests/{id}/proof/interactiveDevice 签名创建绑定 Request、Staging Device Key、PKCE 和一次性 state 的登录流程,返回系统浏览器 URL
POST/requests/{id}/proof/device-codeDevice 签名无头回退,返回 verification URI、user code、校验短语和 interval
POST/requests/{id}/target已认证用户会话 + Device 签名在 target_pending 选择 Organization/Network
GET/identity/callbackIdentity Authority state + PKCE 流程验证身份回调并推进对应 Request;回调会话必须再与 Device 签名绑定,不把浏览器会话变成 Device 凭据

POST /requests 不要求普通用户输入 Network ID。邀请/Enrollment Key proof 可以绑定目标;交互登录在 Proof 完成后由服务端返回允许目标。

#9.3 轮询、提交与取消

方法路径认证作用
POST/requests/{id}/pollDevice 签名返回 Request 状态;若 approved 且无有效 Grant,惰性签发;重复轮询返回同一 Grant
POST/requests/{id}/commitDevice 签名 + Grant提交 Node/WG 公钥、期望 Capability、commit digest;执行原子注册
POST/requests/{id}/cancelDevice 签名取消非终态 Request,撤销 Grant/Reservation
GET/requests/{id}/resultDevice 签名committed 后返回 Device/Membership/Node IP/名称和控制配置入口

poll 的响应按状态使用 tagged union,不能用一个 nullable bool 表示:

{
  "request_id": "erq_...",
  "state": "approved",
  "request_revision": 7,
  "expires_at": "2026-08-11T09:00:00Z",
  "grant": {
    "grant_id": "egr_...",
    "generation": 1,
    "expires_at": "2026-08-08T09:05:00Z",
    "token": "signed-envelope"
  }
}

发布 schema 必须用 oneOf 逐态约束 required 字段:

state附加 required 字段
creatednext_action=proof_start
proof_pendingnext_action, proof_expires_at, poll_after
target_pendingallowed_targets[](只含显示名和本 Request 可选目标)、next_action=target_select
evaluation_pendingpoll_after
approval_pendingapproval_expires_at, poll_after
approvedgrant(grant_id/generation/token/expires_at)
evaluation_errorerror(code/retryable/action/trace_id)、retry_after?
committedresult_uri, commit_digest
denied/expired/cancellederror(稳定终态 code 与 action)

未知 state 必须返回协议解析错误,客户端不得回落成“等待中”或“未授权”。allowed_targets 只在 Proof 已验证、用户会话与 Device 签名都成立后返回。

#9.4 用户确认与管理员 API

方法路径认证作用
POST/device-codes/lookup已认证用户在 JSON body 提交 user code,返回一次性 confirmation_id、设备、指纹、校验短语和允许目标
POST/device-code-confirmations/{confirmation_id}/confirm已认证用户确认本人发起并选择目标
POST/device-code-confirmations/{confirmation_id}/report-not-mine已认证用户拒绝、记录钓鱼安全事件并限速来源
GET/admin/requests?state=approval_pendingEnrollment Approver只列真正待审批请求
GET/admin/requests/{id}Enrollment Approver返回决策材料和 preview digest
POST/admin/requests/{id}/approveEnrollment Approver提交 preview digest、理由和审批期限
POST/admin/requests/{id}/denyEnrollment Approver明确拒绝
POST/admin/requests/{id}/revokeEnrollment Approver/Security Admin撤销 approved 决议和 issued Grant

User code 不进入 URL、query string、Referer、访问日志或审计。confirmation_id 与用户会话、Request 和短 TTL 绑定,只能消费一次。

#9.5 Enrollment Key API

方法路径作用
POST/admin/enrollment-keys创建绑定 Organization/Network、能力/标签上限、次数和 expiry 的 Key;明文只返回一次
GET/admin/enrollment-keys只返回 hash 指纹、范围、使用次数和状态,不返回明文
POST/admin/enrollment-keys/{id}/disable停止新 Proof,不影响已注册节点
POST/admin/enrollment-keys/{id}/revoke-pending撤销由该 Key 产生但尚未 committed 的 Request/Grant
DELETE/admin/enrollment-keys/{id}仅在无在途依赖后删除元数据;历史审计保留 ID

#9.6 Reservation API

方法路径作用
POST/admin/enrollment-reservations审批后按固定 batch 一次预留 N
GET/admin/enrollment-reservations/{id}返回 requested/consumed/remaining/expiry/state
POST/admin/enrollment-reservations/{id}/renew在创建后 2 小时硬上限内延长
POST/admin/enrollment-reservations/{id}/finish完成批次并退还未消费量
POST/admin/enrollment-reservations/{id}/cancel取消并退还未消费量,不撤销已 committed 资源

#10. 错误码与用户动作

coderetryable客户端/用户动作
incompatible_enrollment_protocol否显示客户端与服务端协议不兼容,要求升级,不尝试弱化流程
client_upgrade_required否打开受信任更新入口
request_not_found否不泄漏是否属于他人;清理本地孤儿请求
request_signature_invalid否停止轮询并标记本地安全错误
request_expired否销毁 staging key,重新发起
request_cancelled否清理本地状态
proof_invalid否重新登录/换有效邀请或 Key,不创建审批项
proof_temporarily_unavailable是保持 Request,按 backoff 重试
invite_identity_mismatch否使用邀请指定身份登录或联系管理员
network_selection_required否在已认证页面选择显示名称
target_not_allowed否不自动改选其他 Network
device_code_disabled否使用本地浏览器或联系企业管理员
device_code_rate_limited是显示服务端 retry_after
policy_denied否显示最小拒绝原因,联系管理员
approval_required是展示等待状态和到期时间,不反复新建请求
approval_timeout否Request 到期后重新发起
preview_changed是管理员刷新影响预览后重新确认
grant_expired是Request 仍 approved 时继续签名轮询,取得下一代 Grant
grant_revoked否刷新 Request 状态,不拿旧 Grant 重试
grant_consumed否查询幂等结果;不得创建第二个资源
grant_issue_rate_limited是按 retry_after;generation 异常时要求重新证明
entitlement_unavailable是保持请求,不显示额度为零
entitlement_missing_required_quota否阻止声明激活并通知平台运维
entitlement_projection_stale是服务端内部重试一次,客户端短退避
entitlement_projection_inconsistent否管理端执行投影诊断/修复
unsupported_entitlement_enforcement否拒绝准入并升级服务端
feature_not_entitled否显示商品不包含该能力,指向管理员/升级页
human_seat_limit_reached否只在首次激活用户时出现,指向席位管理
managed_resource_limit_reached否指向资源用量,不建议删除其他用户设备
quota_reservation_invalidated否重新规划批次和申请预留
reservation_expired否未提交机器重新申请;已提交资源不回滚
commit_conflict否查询原 Request 结果;不要改变提交 payload 重试
storage_read_failed是保留已知状态,显示暂时无法继续

客户端只按 code/retryable/action 分支,不解析 message 字符串。

#11. App、CLI 与管理员界面投影

#11.1 App 首次连接

登录并连接
  -> 系统浏览器选择邮箱密码、Google、GitHub 或企业 SSO
  -> 正在验证身份
  -> 选择空间/网络(仅多个允许目标时)
  -> 正在检查设备准入
  -> 等待管理员批准(仅策略要求时)
  -> 正在注册设备
  -> 已连接
Request 状态App 文案主要动作
proof_pending正在等待登录完成打开系统浏览器/取消;App 不显示密码字段
target_pending选择要加入的空间单选 Organization/Network
evaluation_pending正在检查设备准入取消
approval_pending等待管理员批准,截止 X关闭窗口但保留请求/取消
approved已批准,正在完成注册自动签名轮询并 commit
evaluation_error暂时无法完成检查重试/查看错误,不显示“管理员拒绝”
denied设备注册未获批准查看原因/联系管理员
committed注册完成请求 VPN/TUN 权限并连接

App 被关闭后重新打开,从 Staging Key Store 恢复同一 Request 和 Device Key。它不能为“看起来卡住”而偷偷新建 Request。

#11.2 CLI

$ ns up
Opening your browser to sign in...
Waiting for approval from Acme...
Approved. Registering this device...
Connected to Acme / Production as build-mac (100.80.12.34)

无头环境:

$ ns up --headless
Open https://login.ns.io/device and enter ABCD-EFGH
Confirm phrase: BLUE RIVER
Waiting for confirmation...
  • Ctrl-C 只停止前台等待,不取消 Request;再次 ns up 恢复;
  • ns enrollment status 显示状态、目标、expiry 和下一步,不显示 token/私钥;
  • ns enrollment cancel 使用 Device Key 签名取消并清理 staging;
  • --enrollment-key 只出现在服务器/自动化文档,不作为员工登录替代方案;
  • 多目标选择由浏览器完成,CLI 不列出未认证目录或要求粘贴 Network ID。

#11.3 管理员

待审批列表只包含 approval_pending,列显示:设备、主体、目标 Network、Capability、配额影响、等待时间和风险。读取失败显示错误,不显示空列表。

批准对话框必须展示影响预览和有效期;高权限能力逐项勾选,不能用“全选并永久允许”默认值。拒绝、撤销、批量批准都要求理由并写审计。批量注册另有 Reservation 进度页,不把每台机器伪装成独立 quota 抢占。

#12. 审计事件

至少记录:

event关键字段
enrollment.request_createdrequest、proof kind、device fingerprint、来源、expiry
enrollment.proof_verified/failedproof source、主体别名、结果码
enrollment.target_selectedOrganization/Network、actor
enrollment.policy_evaluatedpolicy/entitlement revision、decision digest、结果
enrollment.approved/denied/revokedactor、reason、preview digest
enrollment.grant_issued/expired/revoked/consumedgrant、generation、request、时间
enrollment.committedDevice/Membership/Capability/allocation ID
enrollment.phishing_reporteduser、request、来源限速结果,不记录 user code 明文
quota.reserved/consumed/releasedreservation、resource type、数量、revision
quota.reconciliation_found/repairedexpected/observed digest、actor、reason、correction event

未通过 Proof 的请求只保存限速与取证所需的最小记录,并按短保留期清理。正式审计不保存 OIDC token、Enrollment Key 明文、Device Code、私钥或完整设备证明。

#13. Capability 与版本协商

协议面独立版本:Entitlement、Enrollment、每种控制事件、FFI/客户端 payload 和数据库 revision 互不联动。Capability Manifest 使用统一 envelope,但由真实组件分别声明:

  • NSD:well-known 预检 + 认证握手;
  • ns:认证控制握手;
  • libns/NS App:本地 FFI manifest;
  • NSGW:网关认证握手;
  • Entitlement:bundle 自带 schema version。

握手 transcript 包含双方 manifest digest 和最终协商结果,并进入会话签名。攻击者自报“不支持新能力”只能让可选功能不可用,不能选择缺少 Device Key 绑定、确认页、签名、Grant 或 fail-closed 的旧路径。well-known 失败表示“无法预检”,不表示“对端没有能力”。

#14. 时间、限速与默认值

项目默认硬上限/规则
Personal Request TTL10 分钟组织可缩短
Enterprise approval TTL72 小时Request 最长 7 天
Enrollment Grant TTL5 分钟不因审批时间延长
Device Code TTL5 分钟不可延长
未确认 local draft24 小时到期隔离清理
Reservation TTL60 分钟创建后最长 2 小时
Grant generation 告警520 时限速并要求重新证明

限速至少覆盖用户、IP、Organization、Device 公钥、Proof source 和平台全局在途数量。服务端响应提供 retry_after,客户端不得固定高频轮询。

#15. 实现与验收顺序

  1. 状态机契约:对每个合法/非法转移做表驱动测试;终态不可复活;Request/Grant 分离;
  2. 签名与 staging:跨重启恢复同一 Request/Device Key,request_id 单独不可读状态;
  3. Proof/target:单目标、多个目标、邀请、Enrollment Key、Device Code 钓鱼报告;
  4. 审批与惰性 Grant:72 小时审批后客户端上线才开始五分钟 Grant;重复轮询不增代;
  5. 原子 commit:故障注入到每个写点,失败后无半个 Device/Membership/allocation;幂等重试返回同一结果;
  6. Entitlement 激活:回滚 revision、required quota 缺失、可选 quota tombstone、grace 副本恢复;
  7. Quota 并发:单台并发不超限;批量一次预留、逐台短事务转换、到期退还;SQLite/PostgreSQL 语义一致;
  8. 错误反向验证:把 read failure 注入为 denied/quota reached 时测试必须失败;把自动降级安全基线注入时测试必须失败;
  9. App/CLI 端到端:浏览器登录、无头 Device Code、Ctrl-C 恢复、管理员周末审批、TUN 权限和首次连接;
  10. 真机与运维:macOS/Windows/Linux 密钥存储、系统时钟偏差、数据库重启、Entitlement signer 故障和批量云部署超过 60 分钟。

发布前必须存在真实序列化 payload 对发布 schema 的契约测试。只让 fixture 与 fixture 相互匹配不算验证。