注意
本文档基于 Backstage 1.32.0 版本编写,并采用自 1.24.0 版本以来官方推荐使用的新后端系统(New BackEnd System)。Backstage 仍处于不断更新发展中,本文下述步骤可能将不适用于后续更新版本。
一、开发前准备
开发参考文档:
二、权限架构的简单说明
随着我们新增了许多组件与插件,也许部分功能并非会对全部使用者开放,有些项目或者功能只适合特定小组成员才能看到或使用。因此,管理使用者的存取权限成为我们不可或缺的步骤。
Backstage 整合了各类资源和使用者资料,但在预设情况下没有启用任何权限限制。尽管如此,它提供了一个弹性的权限框架,让我们能够根据需求,自行定义与制作不同的权限管理机制。这样可以确保使用者根据其角色或职责,存取适当的资源或使用功能,避免资料的泄露或意外操作。
如图所示:在后端设定的权限插件中,可以看到预设使用不限制策略。
以 Backstage 的权限运作流程来看,当我们在进入某个插件或者执行某一操作前,会先与后端的权限政策交互进行核查,比较常用的案例是:设定如果该使用者或所属团队不存在于该插件或项目的 Owner 列表中时,则无法存取。
中间的 API,Backstage 已经帮我们处理好了,我们只需要专注于定义 Permission Policy。通过设定使用者的存取权限后,我们能够更好地控制谁能查看、修改特定资源。

三、编写权限设定文件
接下来,我们将编写一个简单的示例,循序渐进,探索 Backstage 的权限设定方式。
(1)启用权限设定
为了启用我们自定义的权限政策规则,首先要到 app-config.yaml 文件中启用 permission 这项设定。
(2)新建一个后端功能模块
从项目的根目录开始:
选择 backend-module,创建一个新的后端功能模块。plugin-ID 和 module-ID 可以填写为一致的,但是最好做出区分(示例:pluginId: 'permission',moduleId: 'custom')。
(3)模块结构说明
如图所示,src 目录下的 index.ts 文件负责向外暴露这个模块,module.ts 文件将包含模块功能逻辑。
(4)第一个简单示例:限制所有使用者读取组件的权限
① 在 src/module.ts 文件中编写如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; import { PolicyQuery } from '@backstage/plugin-permission-node'; import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha'; import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; import { AuthorizeResult, PolicyDecision, } from '@backstage/plugin-permission-common';
class MyPermissionPolicy implements MyPermissionPolicy { async handle( request: PolicyQuery, _user?: BackstageIdentityResponse, ): Promise<PolicyDecision> { if (request.permission.name === 'catalog.entity.read') { return { result: AuthorizeResult.DENY, }; } return { result: AuthorizeResult.ALLOW }; } }
export const permissionModuleCustom = createBackendModule({ pluginId: 'permission', moduleId: 'custom', register(reg) { reg.registerInit({ deps: { policy: policyExtensionPoint, logger: coreServices.logger, }, async init({ logger, policy }) { logger.info('自定义权限策略启用中...'); policy.setPolicy(new MyPermissionPolicy()); }, }); }, });
|
② 向后端注册权限策略模块
Backstage 预设会使用完全开放的不限制权限政策,我们需要换成刚刚我们新增的模块来套用规则。
③ 重新启动 Backstage 后端服务
启动完成后,登录 Backstage,此时将看到目录页不会展示项目:
拒绝所有人的访问并不是我们要实现的功能,我们应该做的是让项目所有者才能读取。
(5)第二个示例:限制仅所有者或群组才能读取
修改 src/module.ts 文件中的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { catalogConditions, createCatalogConditionalDecision, } from '@backstage/plugin-catalog-backend/alpha'; import { catalogEntityDeletePermission, } from '@backstage/plugin-catalog-common/alpha';
class OnlyOwnerPermissionPolicy implements OnlyOwnerPermissionPolicy { async handle( request: PolicyQuery, user?: BackstageIdentityResponse, ): Promise<PolicyDecision> { if (isPermission(request.permission, catalogEntityReadPermission)) { return createCatalogConditionalDecision( request.permission, catalogConditions.isEntityOwner({ claims: user?.identity.ownershipEntityRefs ?? [], }), ); } return { result: AuthorizeResult.ALLOW }; } }
|
启用规则后可以看到最后只剩下了两个所有者为 team-a 的组件在页面中,由于其他的实体并没有定义拥有者是谁,或者并不属于 team-a,最后导致我们看不见,选择项下拉也只有 Component 有东西。因此,虽然是符合我们的预期,但是还可以再优化一下。
(6)第三个示例:限制特定的实体类型
按照上面一个步骤的做法,会将所有的 catalog 类型纳入作用范围,导致无法读取其他使用者的实体资料,如联络信息等。为了解决这个问题,我们还可以限定实体类型。
例如 kinds: ['API', 'Component'],然后再检查是否具有拥有者权限:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class CheckKindPermissionPolicy implements CheckKindPermissionPolicy { async handle( request: PolicyQuery, user?: BackstageIdentityResponse, ): Promise<PolicyDecision> { if (isPermission(request.permission, catalogEntityReadPermission)) { const apiKindCondition: PermissionCondition< 'catalog-entity', PermissionRuleParams > = catalogConditions.isEntityKind({ kinds: ['API', 'Component'], });
const ownerCondition: PermissionCondition< 'catalog-entity', PermissionRuleParams > = catalogConditions.isEntityOwner({ claims: user?.identity.ownershipEntityRefs ?? [], });
return createCatalogConditionalDecision(request.permission, { anyOf: [ { not: apiKindCondition, }, { allOf: [apiKindCondition, ownerCondition], }, ], }); } return { result: AuthorizeResult.ALLOW }; } }
|
通过这一规则的设定,我们还能看到额外的组件了,并且也可以读取其他使用者的实体信息,而非一视同仁地被隐藏掉。通过这样限制某些类型才会进行权限检查,理论和实际都是较为合理的做法。
(7)开发时选用
如果在平时本地开发时会使用到访客身份,可以在权限规则最上方新增一个允许访问所有权限的限制,其效果近似于之前在后端中注释掉的 Backstage 预设的权限设定:
1 2 3
| if (_user?.identity.ownershipEntityRefs.some(ref => ref.startsWith('user:development/guest'))) { return { result: AuthorizeResult.ALLOW }; }
|
四、要点小结
- 权限判断的入口统一在
PermissionPolicy.handle(),未匹配的权限一律回落到 AuthorizeResult.ALLOW
createCatalogConditionalDecision 用于对单个实体做条件判断,返回 AuthorizeResult.CONDITIONAL 后由 Catalog 逐条实体评估
- 用
anyOf / allOf / not 组合条件,可以把「哪些实体类型需要检查拥有者」表达得很清楚
- 记得给本地访客账号留一条放行规则,否则开发时会一直看到空目录