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

[Refactor] Pack namespace permission api to one #5304

Open
BlackBear2003 opened this issue Jan 1, 2025 · 1 comment
Open

[Refactor] Pack namespace permission api to one #5304

BlackBear2003 opened this issue Jan 1, 2025 · 1 comment
Labels
area/portal apollo-portal feature request Categorizes issue as related to a new feature.

Comments

@BlackBear2003
Copy link
Member

BlackBear2003 commented Jan 1, 2025

Is your feature request related to a problem? Please describe.

According to #5301

In order to better expand the permission management capabilities of Apollo Portal in the future, the existing Namespace-related permission management has been sorted out and integrated.

Describe the solution you'd like

Namespace权限模型梳理

appId env cluster namespace Model
☑️ AppId → *
☑️ ☑️ AppId → Namespace
☑️ ☑️ AppId + Env → *
☑️ ☑️ ☑️ AppId + Env → Namespace
☑️ ☑️ ☑️ AppId + Env + Cluster → *
☑️ ☑️ ☑️ ☑️ AppId + Env + Cluster → Namespace

前三个字段是限定了一个权限scope,范围从App到Env再到Cluster三级维度。

因为是针对Namespace的权限,所以Namespace字段是比较不同的,为空时代表指向当前scope下所有Namespace,否则指向所有名为传入值的Namespace。

Model Target PermissionType (e.g. Modify) TargetId
AppId → * AppId的所有namespace
AppId → Namespace AppId下的所有指定名字的namespace ModifyNamespace AppId+Namespace
AppId + Env → * AppId的env下所有namespace
AppId + Env → Namespace AppId的env下所有指定名字的namespace ModifyNamespace AppId+Namespace+Env
AppId + Env + Cluster → * AppId的env中cluster的所有namespace *ModifyNamespaceInCluster AppId+Env+ClusterName
AppId + Env + Cluster → Namespace AppId的env中cluster下指定名字的namespace

考虑到向前兼容,兼容原有的 PermissionType ,原有的 AppId → NamespaceAppId + Env → Namespace 两种权限模型的 PermissionType 都为 ModifyNamespace/ReleaseNamespace ,包括原有的TargetId格式,都是不能修改的。

Apollo的权限校验方式是这样的:

image

原有的两种权限模型的PermissionType是一样的,而在匹配需要的Permission的时候是要用Type+TargetId的,TargetId在Apollo中是用字符串拼接的方式生成的,中间用“+”号隔开,举个例子:

  • AppId → Namespace: ModifyNamespace - test20251228+application
  • AppId + Env → Namespace: ModifyNamespace - test20251228+application+LOCAL

因为参数数量分别是两个和三个,当Type相同,TargetId不会起歧义,指向错误的目标。

但是后续的权限模型的Type不能再继续沿用这个TypeName了,因为还有其他三个参数的权限模型存在,比如说 AppId + Env + Cluster → * 这个模型。

TargetId可能是test20251228+LOCAL+PRO ,也就是**“LOCAL环境下的PRO集群”,这种情况下就会和“PRO环境下所有名为LOCAL的Namespace”**产生二义性,这在权限系统中是危险的,可能会出现越权行为。

因此除了现有的两种存量权限模型,后续新增的Namespace权限模型,都需要有自己独特的PermissionType,以避免TargetId出现二义性问题。

e.g. 本次新增的AppId + Env + Cluster → *的PermissionType为 ModifyNamespaceInCluster / ReleaseNamespaceInCluster

整体架构

image 1

改动点

1. Annotation 入口

全部改为四个入参的方法

2. Api 入口

同步Namespace能力接口

Before:

  @PutMapping(value = "/apps/{appId}/namespaces/{namespaceName}/items", consumes = {"application/json"})
  public ResponseEntity<Void> update(@PathVariable String appId, @PathVariable String namespaceName,
                                     @RequestBody NamespaceSyncModel model) {
    checkModel(!model.isInvalid() && model.syncToNamespacesValid(appId, namespaceName));
    boolean hasPermission = permissionValidator.hasModifyNamespacePermission(appId, namespaceName);
    Env envNoPermission = null;
    // if uses has ModifyNamespace permission then he has permission
    if (!hasPermission) {
      // else check if user has every env's ModifyNamespace permission
      hasPermission = true;
      for (NamespaceIdentifier namespaceIdentifier : model.getSyncToNamespaces()) {
        // once user has not one of the env's ModifyNamespace permission, then break the loop
        hasPermission &= permissionValidator.hasModifyNamespacePermission(namespaceIdentifier.getAppId(), namespaceIdentifier.getNamespaceName(), namespaceIdentifier.getEnv().toString())
        || permissionValidator.hasModifyClusterPermission(namespaceIdentifier.getAppId(), namespaceIdentifier.getEnv().toString(), namespaceIdentifier.getClusterName());
        if (!hasPermission) {
          envNoPermission = namespaceIdentifier.getEnv();
          break;
        }
      }
    if (hasPermission) {
      configService.syncItems(model.getSyncToNamespaces(), model.getSyncItems());
      return ResponseEntity.status(HttpStatus.OK).build();
    }
    throw new AccessDeniedException(String.format("You don't have the permission to modify namespace: %s", noPermissionNamespace));
  }

原本逻辑:

  1. 校验是否有 AppId → Namespace 权限,如有则直接放行
  2. if 没有,则分别校验对每个Namespace的 AppId + Env → Namespace 权限
  3. if 其中一个Namespace没有权限,则抛出无权限异常

改动逻辑:

  • 取消掉第一步校验,直接对每个Namespace调用统一的接口进行权限校验

After:

  @PutMapping(value = "/apps/{appId}/namespaces/{namespaceName}/items", consumes = {"application/json"})
  public ResponseEntity<Void> update(@PathVariable String appId, @PathVariable String namespaceName,
                                     @RequestBody NamespaceSyncModel model) {
    checkModel(!model.isInvalid() && model.syncToNamespacesValid(appId, namespaceName));
    NamespaceIdentifier noPermissionNamespace = null;
    // check if user has every namespace's ModifyNamespace permission
    boolean hasPermission = true;
    for (NamespaceIdentifier namespaceIdentifier : model.getSyncToNamespaces()) {
      // once user has not one of the namespace's ModifyNamespace permission, then break the loop
      hasPermission = permissionValidator.hasModifyNamespacePermission(
          namespaceIdentifier.getAppId(),
          namespaceIdentifier.getEnv().getName(),
          namespaceIdentifier.getClusterName(),
          namespaceIdentifier.getNamespaceName()
      );
      if (!hasPermission) {
        noPermissionNamespace = namespaceIdentifier;
        break;
      }
    }
    if (hasPermission) {
      configService.syncItems(model.getSyncToNamespaces(), model.getSyncItems());
      return ResponseEntity.status(HttpStatus.OK).build();
    }
    throw new AccessDeniedException(String.format("You don't have the permission to modify namespace: %s", noPermissionNamespace));
  }

思考 & 风险点 & 改进点

这样每次进行一次权限校验,可能会涉及到多次数据库IO,可能会成为性能瓶颈,后续可以改为一次查询,或引入一些缓存方案来解决。

@BlackBear2003 BlackBear2003 self-assigned this Jan 1, 2025
@BlackBear2003 BlackBear2003 added feature request Categorizes issue as related to a new feature. area/portal apollo-portal labels Jan 1, 2025
@BlackBear2003 BlackBear2003 removed their assignment Jan 1, 2025
@nobodyiam
Copy link
Member

Sounds good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/portal apollo-portal feature request Categorizes issue as related to a new feature.
Projects
None yet
Development

No branches or pull requests

2 participants