状态:实现依据。本文定义 NSIO Next 原生 Enrollment v1 的状态机、数据结构、事务、API、错误码和界面投影。Next 从零创建全部对象,不读取、导入或兼容旧 NSIO 的身份、密钥、Auth Key、设备、策略和状态目录。
人类账号、登录方式、PKCE 回调、会话、MFA 和身份撤销以身份、登录与会话实现基线为权威。本文从“取得已认证身份 Proof”开始,登录成功仍不等于设备已获准入。
系统对象与安全边界见统一 L3 + L4 架构,商品与 Entitlement 语义见商业模型与 Entitlement,用户完整操作见产品闭环与功能实现规格。
Enrollment 只回答“这个设备是否可以成为某个 Network 的节点”,不回答“这个节点可以访问什么”。协议必须同时满足:
request_id 不能观察或抢占注册。approved 可以跨周末保持,Grant 只在客户端在线轮询时惰性签发五分钟。denied、空候选或 quota reached。service_host、subnet_router、exit_provider、ingress_connector 和受管配置不能因普通 endpoint 注册自动获得。| 场景 | 用户入口 | Proof | 目标来源 | 是否可审批 |
|---|---|---|---|---|
| 桌面/移动 App | “登录并连接” | Identity Authority,系统浏览器 + PKCE | 登录后可进入的 Network | 按 Organization 策略 |
| 有浏览器 CLI | ns up | 本地浏览器 + PKCE | 单个自动选;多个在浏览器选择 | 按策略 |
| 无头 CLI | ns up --headless | Device Code | 手机确认页选择 | 按策略 |
| 受邀成员 | 邀请链接后正常登录 | 邀请 + 已认证人类身份 | 邀请绑定的 Organization/Network | 邀请策略决定 |
| 服务器/路由器 | ns up --enrollment-key ... | Enrollment Key | Key 绑定的 Organization/Network | Key 策略决定 |
| 企业 MDM | 管理配置 + 首次用户登录 | MDM Attestation + SSO | 管理配置限定 | 必须 |
| 工作负载 | 自动化 API | Workload Attestation | 工作负载策略限定 | 按策略 |
普通员工界面不显示“登录、Auth Key、Network ID 三选一”。App 只有“登录并连接”,网页首版展示邮箱密码、Google、GitHub 和企业 SSO;服务器文档只有 Enrollment Key;无头环境由 ns up 自动进入 Device Code。
| 状态 | 含义 | 是否终态 | 待审批列表可见 |
|---|---|---|---|
created | 请求已持久化并绑定 Device 公钥,尚未开始 Proof | 否 | 否 |
proof_pending | 等待 Identity Authority、Device Code、邀请或 Key 证明完成 | 否 | 否 |
target_pending | 人类身份已验证,但必须在允许的多个 Network 中选择一个 | 否 | 否 |
evaluation_pending | 服务端正在评估身份、目标、策略和 Entitlement | 否 | 否 |
approval_pending | 自动评估通过,等待管理员审批 | 否 | 是 |
approved | 审批决议有效,等待在线客户端取得 Grant 并提交 | 否 | 否;审批历史可见 |
evaluation_error | 依赖故障,无法得出允许或拒绝结论 | 否 | 否;安全控制台可见 |
committed | Device/Membership 事务已完成 | 是 | 否;审批历史可见 |
denied | Policy 或管理员明确拒绝 | 是 | 否;审批历史可见 |
expired | Request 服务端 TTL 到期 | 是 | 否;审批历史可见 |
cancelled | 发起设备主动取消 | 是 | 否;审批历史可见 |
evaluation_error 不是 denied。它可以在依赖恢复后回到 evaluation_pending,且在此期间不得创建正式资源或进入审批队列。
| 当前状态 | 事件/操作者 | 前置条件 | 下一状态 | 同一事务内副作用 | 幂等与错误 |
|---|---|---|---|---|---|
| - | request.create / 设备 | Device 公钥格式有效;来源/IP/全局及可确定的 Organization 在途上限未超;协议 major 支持 | created | 保存 Request、Proof 类型、Device 公钥 hash、客户端 capability digest、TTL;写创建审计 | 同一 idempotency_key + device_pub_hash 返回同一 Request;不一致返回 idempotency_conflict |
created | proof.start / 设备 | Request 未过期;签名匹配 Device 公钥 | proof_pending | 创建 PKCE/Device Code/Key challenge;记录尝试次数 | 重复调用返回同一未过期 challenge |
proof_pending | proof.verified / 身份服务 | Proof 有效,且绑定本 Request、Device 公钥和 nonce | target_pending | 保存最小身份引用和允许目标摘要 | 仅人类身份且允许目标多于一个 |
proof_pending | proof.verified / 身份服务 | 邀请或 Enrollment Key 已绑定唯一目标,或人类仅有一个允许目标 | evaluation_pending | 保存目标 Organization/Network、proof_source_id、federation_binding_id | 目标不存在返回 target_not_allowed,不猜测其他目标 |
proof_pending | proof.rejected / 身份服务 | Proof 明确无效、身份不匹配、Key/邀请撤销或尝试次数耗尽 | denied | 保存稳定拒绝码;清理 challenge;标记 staging key 可销毁 | 依赖故障不能走此分支,必须进入 evaluation_error 或保留等待 |
target_pending | target.select / 已认证用户 | 用户会话与 Proof 主体相同;目标在允许集合;设备签名有效 | evaluation_pending | 保存 Organization/Network;冻结目标,后续不可改 | 更换目标必须取消并新建 Request/Device Key |
evaluation_pending | policy.evaluate / NSD | 身份、目标、Device 公钥、Policy、Entitlement 和请求 revision 可读 | approval_pending | 保存 canonical 决策摘要、请求 Capability、配额影响、审批到期时间 | 需要人工审批 |
evaluation_pending | policy.evaluate / NSD | 同上且允许自动批准 | approved | 保存 approval revision、批准者=policy、有效期;不签发 Grant | 不占用 quota,不创建 Device/Membership |
evaluation_pending | policy.evaluate / NSD | 明确不符合 Policy 或能力范围 | denied | 保存稳定拒绝码与最小原因;清理 challenge | 不创建正式资源 |
evaluation_pending | policy.error / NSD | Entitlement、身份源、存储或策略编译无法得出结论 | evaluation_error | 保存 retryable code、依赖和 backoff | 不得伪装成 denied/quota reached |
evaluation_error | evaluation.retry / NSD/设备 | Request 未过期;依赖恢复或用户明确重试 | evaluation_pending | 增加 evaluation attempt;清除旧临时错误 | 自动重试有界,超过上限等待用户重试 |
approval_pending | approval.approve / 管理员 | 管理员有审批角色;预览 digest 未变;Request 未过期 | approved | 保存审批人、理由、revision、有效期;不签发 Grant | 重复同一 approval revision 返回当前状态 |
approval_pending | approval.deny / 管理员 | 管理员有审批角色 | denied | 保存理由并写审计 | 终态 |
approved | request.poll / 设备 | Device 签名有效;Request/审批未过期;无有效 issued Grant | approved | 惰性签发一个 Enrollment Grant | Request 状态不变;见 Grant 状态机 |
approved | enrollment.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/approved | approval.revoke / 管理员/Policy | 管理员撤回、Proof/Key/Binding 撤销或目标 Policy 失效 | denied | 撤销 issued Grant;释放 Reservation;记录原因 | 提交必须重查,不能只验未过期 |
| 任一非终态 | request.expire / 服务端 | now >= request.expires_at | expired | 撤销 issued Grant;释放 Reservation;记录过期 | 时间只由服务端判定 |
target_pending;Grant 是 Request 的子对象,状态只有:
每个 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。
| 当前状态 | 事件 | 前置条件 | 下一状态 | 规则 |
|---|---|---|---|---|
| - | grant.issue | Request=approved;客户端带 Device Key 签名轮询;当前不存在未过期 issued Grant | issued | generation = previous + 1;TTL 默认 5 分钟 |
issued | 重复轮询 | Grant 未过期、未撤销 | issued | 返回同一 grant_id + generation;不得每次重签成新 Grant |
issued | commit.success | 提交事务全部通过 | consumed | 与 Request=committed、资源和 allocation 同事务 |
issued | now >= expires_at | 服务端时间到期 | expired | Request 仍为 approved 时,下次签名轮询可签发下一代 |
issued | grant.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,不比较签名字节,因此协议不依赖签名算法是否产生确定性字节。
| 密钥 | 作用域 | 生成时机 | 服务端持有 | 生命周期 |
|---|---|---|---|---|
| Staging Device Key | 单次 Enrollment Request/目标 Organization | 创建 Request 前 | 仅公钥 | 终态清理或 commit 晋升 |
| Device Key | 单个 Organization | commit 晋升 | 仅公钥 | Device 生命周期,可轮换/撤销 |
| Node Key | 单个 Network Membership | commit 前由设备生成 | 仅公钥 | Membership 生命周期 |
| WireGuard Key | 单个 Membership/epoch | commit 或轮换时生成 | 仅公钥 | 短于 Device,可独立轮换 |
同一物理设备加入另一个 Organization 时必须生成无派生关系的新 Device Key。Installation Context 只管理本地多个 Organization 配置,不能作为密钥派生种子或跨组织关联标识。
0700 目录和 0600 原子文件;request_id、控制端身份、Device key handle、device_pub_hash、创建时间和服务端 expiry,不保存 Proof token;min(组织审批 TTL, 7 天硬上限) + 24 小时清理余量;committed 时原子晋升,收到 denied/expired/cancelled 时立即销毁;启动时扫描孤儿和损坏记录并隔离清理;首版只保留两个概念:
| 对象 | 字段 | 决定什么 |
|---|---|---|
| Device | lifecycle_owner_type/id | 谁能撤销、重置、转移资产和执行 MDM 生命周期 |
| Node Membership | principal_type/id | 本次 Network 运行身份,Grant 用它做主体匹配 |
organization,Membership principal=user:alice;user:alice,Membership principal=user:alice;organization,Membership principal=service_identity:web-prod;不预先加入 assigned_principal 和 runtime_principal 两个始终相同的字段。共享终端形成真实需求后再单独设计会话主体。
| 类型 | 适用对象 | 必须绑定 | 明确禁止 |
|---|---|---|---|
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_attestation | CI/K8s/云工作负载 | workload identity、环境声明、目标范围 | 复用为人类身份 |
Enrollment Key 注册后即消费一次使用次数。后续受管配置依赖节点本地、可撤销的 ManagedConfigAuthorization,不得继续使用已消费 Key。
确认页必须显示:设备显示名、操作系统、请求时间、短公钥指纹、双端相同校验短语、Organization、Network 和请求 Capability。按钮只有:
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,避免把生产节点错误绑定到运维人员生命周期。
普通管理员只看到 approval_pending。详情必须显示:
批准时必须提交当前 preview digest;数据已变化返回 preview_changed,管理员重新查看后再确认。审批动作只把 Request 置为 approved,不签发 Grant、不占 quota。
派生 Membership 保存 proof_source_id 和 federation_binding_id。管理操作分为:
误删配置不能自动级联中断生产;破坏性撤销必须是独立、可预览、可审计的动作。
以下是逻辑 schema。实现可使用 PostgreSQL 或 SQLite 的等价类型,但约束与事务语义必须一致。
enrollment_requests| 字段 | 约束/含义 |
|---|---|
request_id | 随机不可枚举主键 |
protocol_major/minor | Enrollment 协议版本 |
state | §2.1 枚举 |
proof_kind | §5.1 枚举 |
proof_source_id | 可空;验证成功后写入 |
federation_binding_id | 可空;联合身份来源 |
device_pub / device_pub_hash | 创建后不可变;hash 唯一绑定签名 |
principal_type/id | Proof 后写入;最小稳定引用 |
organization_id/network_id | target 冻结后写入;不可改选 |
requested_capabilities | canonical 集合,创建后不可扩大 |
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_id | Request 外键 |
generation | 每 Request 单调递增 |
state | issued/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 | 一次决议记录 |
decision | approved/denied/revoked |
actor_type/id | 管理员或 Policy engine |
preview_digest | 防止确认旧预览 |
reason | 管理员可读、审计可见 |
created_at/expires_at/revoked_at | 生命周期 |
revision | 单调递增 |
| 表 | 关键字段/规则 |
|---|---|
devices | organization_id, device_pub, lifecycle_owner_type/id, display/posture/state;Device 公钥只在本 Organization 唯一 |
node_memberships | device_id, network_id, principal_type/id, Node/WG 公钥、Node IP、state、proof 来源;同一 Device 可有多个 Network Membership |
node_capabilities | membership_id, capability, desired, approved, effective, approval source/revision |
quota_allocations | resource type、resource ID、quota subject、数量、来源 Request/Reservation、状态;所有 used 必须能由 allocation 重算 |
audit_events | actor、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。
| 表 | 关键字段 | 角色 |
|---|---|---|
entitlement_snapshots | subject/deployment、entitlement ID/revision、schema、digest、issuer、签名、issued/expiry/grace、payload | 不可变的验签声明历史 |
entitlement_heads | subject/deployment、current snapshot、revision、digest、state、grace | 唯一当前指针与防回滚高水位 |
quota_usage | subject/deployment/resource type、used、reserved、limit_value、admission_state、enforcement、entitlement revision、usage revision | 低频激活事务产生的准入投影 |
quota_allocations | quota key、resource ID、amount、source request/reservation、state | used 的可解释明细 |
quota_reservations | §8 批量预留字段 | 批量注册短期占位 |
quota_usage.limit_value 是当前 Effective Entitlement 的事务化投影,不是第二权威。运行时不得由调用方传入 limit;支持工单可用 derived_from 追溯订阅,但不能据此做准入。
单写者、低频事务按固定顺序:
entitlement_heads(subject, deployment);limit_value/admission_state/enforcement/revision;admission_state=disabled、limit_value=0、保留 used/reserved、revision 更新为当前;used + reserved 时更新 Reservation revision,否则 revoke 受影响 Reservation、释放未消费量,并向批次返回 quota_reservation_invalidated;Entitlement 激活本身继续完成,不能被旧预留永久卡住;验签通过的最后有效声明及签名必须落盘。重启从副本恢复,仍受 grace_until 约束并标记 stale;副本篡改或无可用声明时不得伪装成零额度。
所有计数使用同一公式:
limit_value = NULL 表示无限,不表示读取失败。条件更新影响零行后必须在同一事务中分类回读:
| 事实 | 错误码 | retryable |
|---|---|---|
| required quota 不存在 | entitlement_missing_required_quota | 否;声明无效 |
| 没有有效 head/超过 grace | entitlement_unavailable | 是 |
| quota revision 与 head 不一致 | entitlement_projection_stale | 是,只重试一次 |
| 一次重试后仍不一致 | entitlement_projection_inconsistent | 否;运维修复 |
admission_state=disabled | feature_not_entitled | 否 |
| enforcement 无法识别 | unsupported_entitlement_enforcement | 否 |
| active 且超过 limit | <resource>_limit_reached | 否,指向管理端 |
注册在同一事务内读取 head、执行条件更新和分类。projection_stale 最多重试一次,禁止无限循环。
单台新增资源在 Enrollment commit 事务中用 delta=1:
human_seats delta=1;已有则跳过;used + reserved + 1 <= limit 的条件更新,直接 used += 1;committed、写 audit/outbox;锁顺序全系统固定为:Request -> Grant -> Entitlement head -> quota rows(按 resource type 排序)-> Reservation -> resource rows。
约束:consumed <= requested;同一 batch_id + resource_type 只有一个 active Reservation;过期/取消/撤销后不可消费。
requested=N 和预期窗口,服务端原子执行 reserved += N 并写 Reservation;不能循环 N 次抢占;reserved -= 1、used += 1、consumed += 1,并创建 allocation/resource、consume 对应 Grant;completed,并原子退还 requested - consumed;SQLite 下预留和逐台转换都使用短 BEGIN IMMEDIATE 事务、busy timeout 与服务端写队列;PostgreSQL 使用行锁/条件更新。两者语义相同,只是吞吐不同。
周期任务比较 used/reserved 与 allocations/active reservations 的权威集合。发现漂移时:
/api/v1/enrollment;Idempotency-Key;request_revision,mutation 使用 If-Match 或等价 revision;{code, message, retryable, action, details?, trace_id};message 不参与客户端分支。| 方法 | 路径 | 认证 | 作用 |
|---|---|---|---|
GET | /.well-known/nsio-capabilities | 无,仅预检 | 返回各协议面支持范围、ETag;Cache-Control: no-cache, must-revalidate |
POST | /requests | Device 公钥自签创建证明 + 限速 | 创建 Request,提交 proof kind、device pub、显示信息、客户端 capabilities、可选 invite/key proof |
POST | /requests/{id}/proof/interactive | Device 签名 | 创建绑定 Request、Staging Device Key、PKCE 和一次性 state 的登录流程,返回系统浏览器 URL |
POST | /requests/{id}/proof/device-code | Device 签名 | 无头回退,返回 verification URI、user code、校验短语和 interval |
POST | /requests/{id}/target | 已认证用户会话 + Device 签名 | 在 target_pending 选择 Organization/Network |
GET | /identity/callback | Identity Authority state + PKCE 流程 | 验证身份回调并推进对应 Request;回调会话必须再与 Device 签名绑定,不把浏览器会话变成 Device 凭据 |
POST /requests 不要求普通用户输入 Network ID。邀请/Enrollment Key proof 可以绑定目标;交互登录在 Proof 完成后由服务端返回允许目标。
| 方法 | 路径 | 认证 | 作用 |
|---|---|---|---|
POST | /requests/{id}/poll | Device 签名 | 返回 Request 状态;若 approved 且无有效 Grant,惰性签发;重复轮询返回同一 Grant |
POST | /requests/{id}/commit | Device 签名 + Grant | 提交 Node/WG 公钥、期望 Capability、commit digest;执行原子注册 |
POST | /requests/{id}/cancel | Device 签名 | 取消非终态 Request,撤销 Grant/Reservation |
GET | /requests/{id}/result | Device 签名 | committed 后返回 Device/Membership/Node IP/名称和控制配置入口 |
poll 的响应按状态使用 tagged union,不能用一个 nullable bool 表示:
发布 schema 必须用 oneOf 逐态约束 required 字段:
| state | 附加 required 字段 |
|---|---|
created | next_action=proof_start |
proof_pending | next_action, proof_expires_at, poll_after |
target_pending | allowed_targets[](只含显示名和本 Request 可选目标)、next_action=target_select |
evaluation_pending | poll_after |
approval_pending | approval_expires_at, poll_after |
approved | grant(grant_id/generation/token/expires_at) |
evaluation_error | error(code/retryable/action/trace_id)、retry_after? |
committed | result_uri, commit_digest |
denied/expired/cancelled | error(稳定终态 code 与 action) |
未知 state 必须返回协议解析错误,客户端不得回落成“等待中”或“未授权”。allowed_targets 只在 Proof 已验证、用户会话与 Device 签名都成立后返回。
| 方法 | 路径 | 认证 | 作用 |
|---|---|---|---|
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_pending | Enrollment Approver | 只列真正待审批请求 |
GET | /admin/requests/{id} | Enrollment Approver | 返回决策材料和 preview digest |
POST | /admin/requests/{id}/approve | Enrollment Approver | 提交 preview digest、理由和审批期限 |
POST | /admin/requests/{id}/deny | Enrollment Approver | 明确拒绝 |
POST | /admin/requests/{id}/revoke | Enrollment Approver/Security Admin | 撤销 approved 决议和 issued Grant |
User code 不进入 URL、query string、Referer、访问日志或审计。confirmation_id 与用户会话、Request 和短 TTL 绑定,只能消费一次。
| 方法 | 路径 | 作用 |
|---|---|---|
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 |
| 方法 | 路径 | 作用 |
|---|---|---|
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 资源 |
| code | retryable | 客户端/用户动作 |
|---|---|---|
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 字符串。
| 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。
无头环境:
ns up 恢复;ns enrollment status 显示状态、目标、expiry 和下一步,不显示 token/私钥;ns enrollment cancel 使用 Device Key 签名取消并清理 staging;--enrollment-key 只出现在服务器/自动化文档,不作为员工登录替代方案;待审批列表只包含 approval_pending,列显示:设备、主体、目标 Network、Capability、配额影响、等待时间和风险。读取失败显示错误,不显示空列表。
批准对话框必须展示影响预览和有效期;高权限能力逐项勾选,不能用“全选并永久允许”默认值。拒绝、撤销、批量批准都要求理由并写审计。批量注册另有 Reservation 进度页,不把每台机器伪装成独立 quota 抢占。
至少记录:
| event | 关键字段 |
|---|---|
enrollment.request_created | request、proof kind、device fingerprint、来源、expiry |
enrollment.proof_verified/failed | proof source、主体别名、结果码 |
enrollment.target_selected | Organization/Network、actor |
enrollment.policy_evaluated | policy/entitlement revision、decision digest、结果 |
enrollment.approved/denied/revoked | actor、reason、preview digest |
enrollment.grant_issued/expired/revoked/consumed | grant、generation、request、时间 |
enrollment.committed | Device/Membership/Capability/allocation ID |
enrollment.phishing_reported | user、request、来源限速结果,不记录 user code 明文 |
quota.reserved/consumed/released | reservation、resource type、数量、revision |
quota.reconciliation_found/repaired | expected/observed digest、actor、reason、correction event |
未通过 Proof 的请求只保存限速与取证所需的最小记录,并按短保留期清理。正式审计不保存 OIDC token、Enrollment Key 明文、Device Code、私钥或完整设备证明。
协议面独立版本:Entitlement、Enrollment、每种控制事件、FFI/客户端 payload 和数据库 revision 互不联动。Capability Manifest 使用统一 envelope,但由真实组件分别声明:
ns:认证控制握手;握手 transcript 包含双方 manifest digest 和最终协商结果,并进入会话签名。攻击者自报“不支持新能力”只能让可选功能不可用,不能选择缺少 Device Key 绑定、确认页、签名、Grant 或 fail-closed 的旧路径。well-known 失败表示“无法预检”,不表示“对端没有能力”。
| 项目 | 默认 | 硬上限/规则 |
|---|---|---|
| Personal Request TTL | 10 分钟 | 组织可缩短 |
| Enterprise approval TTL | 72 小时 | Request 最长 7 天 |
| Enrollment Grant TTL | 5 分钟 | 不因审批时间延长 |
| Device Code TTL | 5 分钟 | 不可延长 |
| 未确认 local draft | 24 小时 | 到期隔离清理 |
| Reservation TTL | 60 分钟 | 创建后最长 2 小时 |
| Grant generation 告警 | 5 | 20 时限速并要求重新证明 |
限速至少覆盖用户、IP、Organization、Device 公钥、Proof source 和平台全局在途数量。服务端响应提供 retry_after,客户端不得固定高频轮询。
发布前必须存在真实序列化 payload 对发布 schema 的契约测试。只让 fixture 与 fixture 相互匹配不算验证。