Openship权限模型详解:组织、项目与Token的细粒度访问控制完整指南
2026/9/16 17:25:12 网站建设 项目流程

Openship权限模型详解:组织、项目与Token的细粒度访问控制完整指南

【免费下载链接】openshipSelf-hosted deployment platform项目地址: https://gitcode.com/GitHub_Trending/ope/openship

Openship(Openship权限模型)是一个自托管部署平台,通过组织(Organization)—项目(Project)—Token三层结构实现细粒度访问控制:每个资源归属唯一组织,用户以 owner / admin / member / restricted 四种角色加入,受限成员和 API Token 还可被精确授权到"单个项目只读"这类最小粒度。本文带你完整理解 Openship 的权限体系。

🏢 第一层:组织——所有资源的"家"

在 Openship 中,组织是唯一的多租户边界。项目、部署、服务器、邮箱服务器、备份目标等资源全部挂在组织下,而不是挂在用户下。数据库结构一目了然:

  • 组织与成员表:organization.ts 定义了organizationmemberinvitation三张表
  • 权限唯一依据是member表:"用户是否属于该资源所在的组织 + 扮演什么角色",这是整个模型的地基

组织还直接携带计费信息(套餐档位planTierId、订阅状态),使"这个组织能否使用某功能"的检查无需额外联表,见 organization.ts。

💡 新手提示:一个组织 ≈ 一个"团队工作区"。邀请成员通过invitation表完成,邀请可指定对方加入后的角色并设置过期时间。

👥 第二层:四种角色与默认权限

角色定位默认权限范围
owner创建者/最高权限组织内一切,含计费与审计
admin管理员除计费(billing)外的全部
member普通成员除计费与审计日志(audit)外
restricted受限成员默认零权限,仅靠显式授权获得访问

角色策略集中在一个纯函数里,供"每次调用校验"和"工具列表过滤"共用,保证两处永不漂移:permission.ts。

restricted 角色:最小权限的核心

这是 Openship 权限模型最有特色的设计——默认拒绝(default-deny)

  • 角色为restricted的成员,除非有一行显式授权,否则对组织资源零访问
  • 授权记录存储在 resource-grant.ts:一行授权 = "谁(user)+ 对什么资源(类型+ID)+ 哪些动作(read/write/admin)"
  • resourceId可以是具体 ID(只授这一个项目),也可以是"*"(该类型下组织内全部资源)

典型场景:给外包工程师授予项目 A + read,他可以查看 A 的部署与日志,但完全看不到其他项目、服务器和计费信息。

权限继承:授权一个项目,覆盖它的一切

授权"项目"时,其子资源自动受保护:

  • deployment(部署)、domain(域名)、service(服务)、env_var(环境变量)、build_session(构建)→ 继承项目的授权
  • backup_policy/backup_run/backup_restore→ 继承备份目标(backup_destination)的授权

解析逻辑在 permission.ts 的resolveResourceOrg中:从叶子资源一路向上找到"可授权根",再校验该根上的授权。

🔐 第三层:Token——给机器和脚本发"缩小版通行证"

API Token(PAT)是 Openship 供 CLI、脚本、MCP 客户端使用的 Bearer 凭据,格式为opsh_pat_<secret>。关键安全设计:

  1. 只存哈希:数据库中仅保存 SHA-256 哈希(personal-access-token.ts),明文只在创建时展示一次,生成逻辑见 pat.ts
  2. 可读标记readOnly的 Token 直接拒绝所有修改类请求
  3. 范围锁定(scoped):设为scoped后,Token 携带自己的授权表(personal-access-token-grant.ts),以restricted身份运行——即使属主是 owner,Token 也不能超越自身授权
  4. 独立存储:Token 授权与成员授权分表存放,互不串读,避免任何查询路径意外放大权限

🧭 一次请求是如何被校验的?

每个 API 路由都声明一个"权限标签"(如project:readproject:service:edit),中间件按四步裁决:

  1. 解析标签→ 得到资源类型 + 动作(read/write/admin/list)
  2. 从 URL 提取资源 ID,嵌套标签还会校验"子资源确实属于 URL 声明的父资源",防跨父混淆
  3. 定位组织→ 详情接口从资源自身读出 org_id;列表/创建接口按优先级取:X-Organization-Id请求头 → 会话默认组织
  4. 执行裁决member查角色,restricted 查授权,assert统一出口

完整流程见 route-permission.ts 与 permission.ts。

两个值得称赞的安全细节

  • IDOR 安全:越权时统一返回404而非 403——攻击者无法通过错误码探测"资源是否存在"
  • 启动即审计:路由注册表在启动时被扫描,任何缺少权限声明的路由都会直接拒绝服务启动,杜绝"忘记加权限检查"

🎯 授权面统一:一个文件定义"能授什么"

历史上"可授权资源类型"散落在 7 处导致漂移。现在统一收敛到 access-grants.ts:

  • GRANTABLE_RESOURCE_TYPES:用户实际能授予的类型清单(项目、服务器、邮箱服务器、备份目标、GitHub 仓库、平台功能等)
  • SENSITIVE_GRANT_TYPESbillingaudit被标记为高影响授权,UI 中会明确提示爆炸半径
  • grantableTypesForMode():按部署形态过滤——自托管不显示仅云端存在的billing,云端不显示仅自托管的server/mail_server/job

授权类型还分为两组呈现:具体"资源"(可从目录中挑选单个项目/仓库)与"平台功能"(整功能级授权,resourceId 固定为*),见 access-grants.ts。

✅ 实践建议

  1. 团队日常成员:用member角色即可,无需任何显式授权
  2. 外部协作者:设为restricted+ 项目级read/write授权,边界清晰
  3. CI/脚本/Agent:一律使用scopedPAT,只授其任务所需的最小资源集,并设置过期时间
  4. 敏感操作:计费与审计日志只给 owner;给 member 审计权限时清楚这等于交出"全员操作历史"
  5. 排查越权:优先看 404 是否由permission.assert抛出——那是设计行为,不是资源真的不存在

小结

Openship 的权限模型可以浓缩为三条原则:资源认组织不认用户角色给默认值、授权给例外Token 的权限只由自己定义。这套机制让"两个团队共用一台自托管实例、却互不可见"成为开箱即用的默认状态,而不需要额外的隔离改造。

延伸阅读

  • 权限裁决核心:permission.ts
  • 路由标签系统:route-permission.ts
  • 成员授权表:resource-grant.ts
  • Token 授权表:personal-access-token-grant.ts
  • 授权面单一来源:access-grants.ts
  • 审计事件:audit.ts

【免费下载链接】openshipSelf-hosted deployment platform项目地址: https://gitcode.com/GitHub_Trending/ope/openship

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询