Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Cluster Authorization By Env 支持按环境给用户授权集群管理权限 #5301

Open
BlackBear2003 opened this issue Dec 22, 2024 · 3 comments

Comments

@BlackBear2003
Copy link
Contributor

BlackBear2003 commented Dec 22, 2024

需求:

#4372

新用户按环境授权,无法直接作用于集群,只能作用于集群的namespace。

支持按环境给用户授权集群管理权限,如查看、更改和发布。

** 实际上集群就是跟着环境走的,集群 = App 在某个 Env 下的一种划分 **

现状

实际上,从Permission这张表里面可以看得到,对于一个应用(App)的对应权限有如下权限的划分

image

  • App 管理员

    应用管理员具有以下权限: 1. 创建 Namespace 2. 创建Cluster 3. 管理App、Namespace 权限

  • App-Namespace 维度的Namespace管理员(分为两种权限)

    • 更改(Modify)
    • 发布(Release)
  • App-Namespace-Env 维度的Namespace管理员 (分为两种权限)

    • 更改(Modify)
    • 发布(Release)

方案:

  • Permission 侧:

    新增两种PermissionType,ModifyCluster 和 ReleaseCluster。代表在Cluster维度的修改和发布的权利,即可以修改和发布Env-Cluster下所有Namespace。

    对应的TargetId格式为 AppId + Env + ClusterName

  • Role 侧:

    新增RoleNameModifyCluster+TargetIdReleaseCluster+TargetId

image (1)

Management/Subjective

在Portal新增三个接口,获取、赋予、移除Cluster相关的权限Permission。

OpenApi处暂时没有支持相关管理能力。

Management/Passive

需要在Cluster被创建的时候进行RoleInitialize,Cluster在下面几个场景会被创建:

  1. 创建App时,默认在每个Env下创建一个default集群
  2. 指定给某个Env创建App时,默认创建default集群
  3. 在portal端指定App-Env创建Cluster
image (2)

第一种创建默认的Cluster的场景,Portal是完全不感知的,只知道自己向对应Env发送了创建App的请求,创建Cluster的逻辑完全在AdminService端,只能在创建完远端App之后,默认Cluster也创建成功。

由于权限系统只存在于Portal,三条创建Cluster的链路在Portal端是没有交集的,完全不同,因此需要在三个地方都加上initializeClusterRole的逻辑。(以后或许是可以优化的点)

Usage/Anno&Api

目前来说,Namespace的权限校验相关功能,现有的系统的调用链路还是比较统一的,比如说ModifyNamespace权限校验基本都是调用的hasModifyNamespacePermission 这个接口。

这对于要新加入的ModifyCluster权限就不太合适了,首先方法的语义就不相同,ModifyNamespace是ModifyCluster的子集。如果要在这个接口里面再做对Cluster权限的校验也不合适,因为参数列表里面没有传递ClusterName。

所以可以得到结论:要实现Cluster级别权限的需求,必定要在原有的代码上进行修改了,也就是这个注解:

@PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName, #env)")

目前来说有两种实现方式:

  1. 修改参数列表,接收四个String
@PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #env, #clusterNam, #namespaceName)")
  1. 多加一个方法hasModifyClusterPermission,在注解表达式中用 or 加进去。
@PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName, #env) " 
      + "or @permissionValidator.hasModifyClusterPermission(#appId, #env, #clusterName)")

两种方法其实都对原本代码是破坏性的,所以还是打算先选择第二种方法,**对原本的功能的影响尽可能的少。**如有需要,后续再对权限校验进行重构。

测试报告

后端集成测试

主要写了两个集成测试,从Usage和Management两个方面入手

ItemControllerAuthIntegrationTest:测试是否有权限时,对需鉴权接口的访问情况

  /**
   * Test cluster permission denied.
   */
  @Test
  public void testCreateItemPermissionDenied() {
    setUserId("xxx");
    try {
      HttpHeaders headers = new HttpHeaders();
      headers.set("Content-Type", "application/json");

      HttpEntity<String> entity = new HttpEntity<>(GSON.toJson(itemDTO), headers);

      restTemplate.postForEntity(
          url("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item"),
          entity, String.class, appId, env, clusterName, namespaceName);

      fail("should throw");
    } catch (final HttpClientErrorException e) {
      assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode());
    }
  }

PermissionControllerTest:测试Cluster Role的管理端整个生命周期(初始化,assign,get,remove)

  @Test
  @Sql(scripts = "/sql/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testClusterRoleLifeCycle() {

    HttpHeaders headers = new HttpHeaders();
    headers.set("Content-Type", "application/json");
    HttpEntity<String> entity = new HttpEntity<>(user, headers);

    // check role not assigned
    ResponseEntity<ClusterRolesAssignedUsers> beforeAssign = restTemplate.getForEntity(
        url("/apps/{appId}/envs/{env}/clusters/{clusterName}/role_users"),
        ClusterRolesAssignedUsers.class, appId, env, clusterName);
    assertEquals(200, beforeAssign.getStatusCodeValue());
    ClusterRolesAssignedUsers body = beforeAssign.getBody();
    assertNotNull(body);
    assertEquals(appId, body.getAppId());
    assertEquals(env, body.getEnv());
    assertEquals(clusterName, body.getCluster());
    assertTrue(body.getModifyRoleUsers() == null || body.getModifyRoleUsers().isEmpty());

    // assign role to user
    restTemplate.postForEntity(
        url("/apps/{appId}/envs/{env}/clusters/{clusterName}/roles/{roleType}"), entity, Void.class,
        appId, env, clusterName, roleType);

    // check role assigned
    ...
    // remove role from user
    ...
    // check role removed
    ...
  }

整体Portal端验收测试

  1. 创建好测试环境和集群
image (3)
  1. 给开发账号luke授予boe_feat_123的Modify和Release权限
image (5) image (4)
  1. 登录luke账号,查看集群Namespace,在boe_feat_123有权限,在default没有权限
image (6) image (7)
  1. 正常测试创建配置、修改、发布等功能
image (8) image (9) image (10)
@BlackBear2003 BlackBear2003 changed the title Cluster Authorization By Env 支持按环境给用户授权集群管理权限 [Feat] Cluster Authorization By Env 支持按环境给用户授权集群管理权限 Dec 22, 2024
@BlackBear2003
Copy link
Contributor Author

cc. @nobodyiam

@nobodyiam
Copy link
Member

非常感谢提交 PR 增强 apollo 的权限管理能力!apollo 在初始设计时把权限绑定在了 namespace 上,粒度比较细,在某些场景下确实不太方便,用户需要做大量的权限配置工作。
如你所分析的,apollo 的权限管理本身是比较灵活的,所以可以在这个基础之上进行扩展。
目前对 namespace 的权限管控粒度是以下两种:

  • appId -> namespace(appId 下所有集群中的某个 namespace)
  • appId -> env -> namespace(appId 在某个环境下所有集群中的某个 namespace )

其实未来在这个基础之上可以做更多扩展,例如:

  • appId(appId 的所有 namespace)
  • appId -> env(appId 某个环境中的所有 namespace)
  • appId -> env -> cluster(appId 某个环境中的某个集群下所有 namespace)
  • appId -> env -> cluster -> namespace(appId 某个环境中的某个集群下的某个 namespace)

这次 PR 要实现的是 appId -> env -> cluster 这个粒度,可以考虑新增权限码形如ModifyNamespacesInClusterReleaseNamespacesInCluster

ModifyClusterReleaseCluster 从字面意思上理解是对 Cluster 本身的修改,会有些歧义。

按照上述的分析,对于修改方式我可能更倾向于修改hasModifyNamespacePermissionhasReleaseNamespacePermission的签名,这样未来再去扩展 appIdappId -> envappId -> env -> cluster -> namespace 等权限粒度时只要修改这两个方法逻辑即可。

@PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #env, #clusterName, #namespaceName)")

可以考虑把public boolean hasModifyNamespacePermission(String appId, String namespaceName)public boolean hasReleaseNamespacePermission(String appId, String namespaceName)public boolean hasOperateNamespacePermission(String appId, String namespaceName) 这些老的方法置为 private,而后把目前传了 appId、namespaceName、cluster 的方法增加 cluster 字段,改动量可能稍有些大,不多从长远来看复用度会比较高。

@BlackBear2003
Copy link
Contributor Author

了解,这样所有权限的入口会相对唯一,复用度会更高。🙌
我先设置PR为draft,等这边相关内容修改完毕,我会再次打开PR。😎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants