- 可观测性
- 运维
- 后端
【免费下载链接】Pulse
Real-time monitoring dashboard for Proxmox VE, PBS, Docker, Kubernetes, TrueNAS and vSphere. Self-hosted, with smart alerts and AI patrols that catch silent failures.
本文档是 Pulse 项目
api-token-scope-and-assignment门禁(gate)的验证记录(2026-03-12),完整呈现了 API Token 在会话身份归属绑定、多租户组织隔离、作用域强制校验与即时吊销、以及 v6agent:*作用域别名归一化四个维度的端到端验证过程。通过阅读本文,你将掌握 Pulse API Token 的创建、授权、隔离、吊销与遗留别名兼容的完整行为契约,并能在自己的部署环境中复现同样的手工验证路径。
验证环境与前置条件
本次验证运行在 Pulse 自托管后端上,核心环境参数如下:
- 受管本地后端:
http://127.0.0.1:61530 - 多租户授权配置文件:
multi-tenant - 被测认证用户:
admin
整个验证分为两部分:自动化证据基线(Automated Proof Baseline)与手工演练(Manual Exercise)。自动化测试覆盖 Go 后端、前端 React 组件与端到端集成测试三个层次,手工演练则模拟真实操作者对会话、Token 生命周期与多组织边界的实际操作。
自动化证据基线:三层测试矩阵
门禁的自动化证据由以下命令构成,验证结果全部为pass:
后端 Go 测试
覆盖 API Token、安全令牌、系统设置与多租户四大主题的回归测试:
go test ./internal/api -run 'Test(APIToken|SecurityTokens|SystemSettings|MultiTenant)' -count=1聚焦作用域归一化与遗留别名兼容的契约测试:
go test ./internal/api \ -run 'TestNormalizeRequestedScopesCanonicalizesLegacyUnifiedAgentAliases|TestUnifiedAgentEndpointsAcceptLegacyUnifiedAgentReportScopeAlias|TestContract_APITokenScopeAliasNormalization' \ -count=1上述三个用例分别验证:请求作用域归一化函数会把host-agent:*遗留别名改写为 v6 规范作用域、统一 Agent 端点接受遗留unified-agent上报别名、以及 API Token 作用域别名归一化的契约。相关的源码佐证位于 internal/api/security_tokens_test.go(TestNormalizeRequestedScopesCanonicalizesLegacyUnifiedAgentAliases直接断言host-agent:report与host-agent:config:read被归一化为agent:config:read与agent:report)与 internal/api/contract_test.go。
前端组件与工具函数测试
cd frontend-modern && npx vitest run \ src/components/Settings/__tests__/APITokenManager.test.tsx \ src/utils/__tests__/apiClient.org.test.ts \ src/utils/__tests__/apiTokenPresentation.test.ts \ src/utils/__tests__/frontendResourceTypeBoundaries.test.ts前端对应的 Token 管理界面实现在 frontend-modern/src/components/Settings/APITokenManager.tsx 及其配套的状态管理钩子 frontend-modern/src/components/Settings/useAPITokenManagerState.ts。
端到端集成测试
cd tests/integration && \ PULSE_E2E_USE_LOCAL_BACKEND=1 \ PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 \ PULSE_MULTI_TENANT_ENABLED=true \ npm test -- tests/13-api-token-scope.spec.ts --project=chromium端到端用例tests/13-api-token-scope.spec.ts在本地后端、跳过 Playwright 安装、开启多租户的前提下,以 Chromium 项目跑通完整的作用域场景,是验证multi-tenant授权配置在真实 HTTP 链路上生效的最终证据。
手工演练:完整的 13 步操作实录
第 1 步:建立会话并取得凭据
以admin身份通过POST /api/login登录受管本地后端,取得会话 Cookie 与 CSRF Cookie,后续所有 Token 管理请求均携带这对凭据。这一步确立了"会话创建 Token"的归属主体——后续创建的 Token 都会自动绑定到当前认证用户身份上。
第 2~4 步:最小权限 Token 的创建、放行与拒绝
创建一个绑定所有者的 API Token,只授予settings:read单一作用域。创建响应中可以看到ownerUserId=admin,即 Token 归属到当前会话用户。
随后用该 Token 发起三类请求,验证作用域强制机制:
| 请求 | 所需作用域(requiredScope) | 期望结果 |
|---|---|---|
GET /api/system/settings | settings:read | 成功(200) |
POST /api/security/tokens | settings:write | 拒绝(missing_scope) |
POST /api/ai/execute/stream | ai:execute | 拒绝(missing_scope) |
PATCH /api/agents/agent/host-1/config | agent:manage | 拒绝(missing_scope) |
这条链路在源码中的实现路径非常清晰:作用域校验由 internal/api/apihttp/scope.go 中的EnsureScope/EnsureAnyScope完成,当凭据缺少所需作用域时返回结构化的missing_scope错误体,其中携带requiredScope(单作用域)或requiredScopes(多作用域候选)字段,便于客户端精确得知缺失的是哪个作用域;Token 的创建则统一经过 internal/api/security_tokens.go 的normalizeRequestedScopes进行规范化。文档实测中的 "Read、mutate、exec 三类作用域强制均返回预期的missing_scope失败,并带有规范作用域名称" 正是该实现的行为契约。
第 5 步:吊销与即时失效
通过DELETE /api/security/tokens/{id}吊销上述 Token,随后立即用旧的 Bearer Token 再次请求GET /api/system/settings,返回401。吊销的底层逻辑位于 internal/api/security_tokens.go 的handleDeleteAPIToken:从内存配置中移除记录并通过持久化层落盘(持久化失败时回滚内存状态以保证与磁盘一致),因此凭据在服务端记录消失的瞬间即告失效,无需等待任何缓存过期。
值得一提的安全细节:handleDeleteAPIToken与handleRotateAPIToken都实现了作用域升级防护——若调用者本身是 API Token,则只能删除/轮换作用域是其自身作用域子集的 Token,防止低权限 Token 通过删除或轮换高权限 Token 造成锁定或拿到更高级别的原始凭据。
第 6~8 步:多租户组织隔离
创建两个组织:
manual-token-org-a-1773352558099-998718manual-token-org-b-1773352558099-245536
在组织 A 的作用域下创建一个组织绑定 Token,创建响应仍然绑定ownerUserId=admin(归属主体不因组织切换而改变)。随后用该 Token 验证组织边界:
GET /api/orgs/{orgA}/members→200GET /api/orgs/{orgB}/members→403,错误信息为Token is not authorized for this organization
组织隔离在数据模型中通过APITokenRecord的OrgID(单组织绑定)与OrgIDs(MSP 多组织访问,设置时优先于OrgID)字段实现,定义见 internal/config/api_tokens.go。授权层在 internal/api/authorization.go 生成 "Token is not authorized for this organization" 的拒绝原因,并由 internal/api/access_admin_handlers.go、internal/api/cloud_org_admin_auth.go 等组织管理端点统一以403返回。
第 9~12 步:遗留作用域别名的 v6 归一化
创建 Token 时使用遗留作用域host-agent:report,创建后存储的作用域被规范化(canonicalize)为agent:report。该 Token 对两个统一 Agent 上报端点均能到达处理器:
POST /api/agents/agent/reportPOST /api/agents/host/report
两者都仅因故意构造的非法 JSON 返回400,而非因作用域授权失败被拦下——证明遗留别名 Token 通过了作用域门禁,只是业务数据不合法。
同理,使用遗留作用域host-agent:config:read创建的 Token 被归一化为agent:config:read,对配置读取端点:
GET /api/agents/agent/host-1/configGET /api/agents/host/host-1/config
均通过作用域授权,仅因合成 host 尚未注册返回404 agent_not_found,而非403。
别名映射表定义在 internal/config/api_tokens.go:
var legacyScopeAliases = map[string]string{ "host-agent:report": ScopeAgentReport, // agent:report "host-agent:config:read": ScopeAgentConfigRead, // agent:config:read "host-agent:manage": ScopeAgentManage, // agent:manage "host-agent:enroll": ScopeAgentEnroll, // agent:enroll }运行时入口则在 internal/api/security_tokens.go 的canonicalizeRequestedScope,其在normalizeRequestedScopes内被逐项调用;同时 internal/config/api_tokens.go 的ensureScopes会在记录加载/克隆时对已存储的遗留别名做同样的改写。规范作用域的字符串常量集中在 pkg/auth/scopes.go(如agent:report、agent:config:read、agent:manage、agent:enroll),IsKnownScope只接受规范标识符,这保证了从配置、API 请求、持久化到授权校验的整条链路上,遗留host-agent:*别名都会被收敛到 v6 的agent:*命名空间。
第 13 步:清理
演练结束后删除临时创建的组织绑定 Token、遗留别名 Token,并移除两个临时组织,避免在环境中留下测试残留。
结果验收(Outcome)
本次门禁验证共确认五项行为契约:
- 会话创建的 API Token 始终绑定到认证用户身份:无论是否处于组织作用域下,
ownerUserId都指向创建者(admin)。归属写入由 internal/api/security_tokens.go 的setAPITokenOwnerUserID与apiTokenOwnerUserIDForRequest完成:优先取请求携带的 Token 元数据,其次取上下文中的已认证用户,最后回退到配置中的认证用户名;调用者元数据中的owner_user_id是保留键,外部无法伪造。 - 组织绑定 Token 被严格限制在签发组织内:跨组织访问返回
403 Token is not authorized for this organization。 - 读、写、执行三类作用域强制均返回预期的
missing_scope错误,并携带规范作用域名称(settings:write、ai:execute、agent:manage)。 - 吊销立即令 Bearer Token 失效:删除后旧凭据立刻得到
401。 - 遗留持久化的
host-agent:*作用域别名被归一化为 v6agent:*规范作用域,并顺利通过规范化的上报/配置读取作用域门禁(agent:report、agent:config:read),与统一 Agent 端点的行为完全兼容。
源码深度解读:作用域生命周期全景
如果把上面的验证结论映射到源码,可以串出一条完整的 Token 生命周期链路:
- 创建(handleCreateAPIToken):解码请求体 →
normalizeRequestedScopes(空字段默认全量*、空列表/空字符串/未知作用域拒绝、*与显式作用域混用拒绝、去重并按字典序排序)→ 检查调用方 Token 是否持有待授予作用域(防升级)→ 生成原始凭据与哈希记录 → 绑定OrgID与ownerUserId→ 落盘(失败则回滚)。expiresIn支持24h/720h/8760h等 Go duration 格式,且最短不得低于 1 分钟。 - 查询(
GET /api/security/tokens、GET /api/security/tokens/{id}):仅返回元数据(DTO),绝不回显原始凭据。 - 更新/重命名(handleUpdateAPIToken):不轮换密钥;若调用方是 Token,需同时持有目标 Token 的现有作用域与请求的新作用域,防止缩小/篡改更高权限凭据。
- 轮换(handleRotateAPIToken):原子地生成新 Token 并移除旧 Token,保留组织绑定、元数据与过期策略;持久化失败时整体回滚。
- 吊销(
DELETE /api/security/tokens/{id}):删除记录并落盘,配合前端 frontend-modern/src/components/Settings/APITokenManager.tsx 形成"撤销即失效"的完整闭环。
对于需要在自有环境中复现本门禁验证的读者,建议按multi-tenant授权配置启动本地后端(默认端口61530),依次执行文档中第 1~13 步操作,并配合上文三组自动化测试命令对照验证。整个验证表明:Pulse 的 API Token 体系在"最小权限授予、组织隔离、即时吊销、遗留兼容"四个维度上均形成了可自动化、可手工复现、可审计的行为契约。
- 可观测性
- 运维
- 后端
【免费下载链接】Pulse
Real-time monitoring dashboard for Proxmox VE, PBS, Docker, Kubernetes, TrueNAS and vSphere. Self-hosted, with smart alerts and AI patrols that catch silent failures.
相关推荐
Rustcat(rcat) vs Netcat:为什么现代网络安全工具需要升级到Rustcat
Rustcat rcat vs Netcat:为什么现代网络安全工具需要升级到Rustcat Rustcat rcat 作为一款现代端口监听和反向 shell
被 500 多所大学采用的《动手学深度学习》:公式旁边就能跑代码
被 500 多所大学采用的《动手学深度学习》:公式旁边就能跑代码 看视频觉得都懂了,一写代码就卡住?《动手学深度学习》就是冲着这个痛点来的:每页公式旁边都配着能
人工智能深度学习机器学习教程Astrid 主体验证与按调用隔离:从 PrincipalId 到 IPC 透明重定作用域的内核实现解析
Astrid 主体验证与按调用隔离:从 PrincipalId 到 IPC 透明重定作用域的内核实现解析 导读 在 Astrid 操作系统中,从 KV 命名空间
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考