-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Description
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 → Namespace
和 AppId + Env → Namespace
两种权限模型的 PermissionType 都为 ModifyNamespace/ReleaseNamespace
,包括原有的TargetId格式,都是不能修改的。
Apollo的权限校验方式是这样的:
原有的两种权限模型的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
整体架构
改动点
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));
}
原本逻辑:
- 校验是否有 AppId → Namespace 权限,如有则直接放行
- if 没有,则分别校验对每个Namespace的 AppId + Env → Namespace 权限
- 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,可能会成为性能瓶颈,后续可以改为一次查询,或引入一些缓存方案来解决。