iliax 1 year ago
parent
commit
2135e0072c

+ 109 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/AccessContextV2.java

@@ -0,0 +1,109 @@
+package com.provectus.kafka.ui.model.rbac;
+
+import com.provectus.kafka.ui.model.rbac.permission.AclAction;
+import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
+import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
+import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
+import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
+import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
+import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
+import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
+import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
+import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
+import jakarta.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Value;
+import org.springframework.util.Assert;
+
+@Value
+public class AccessContextV2 {
+
+  public record ResourceAccess(@Nullable String name, ResourceV2 type, List<PermissibleAction> actions) {
+  }
+
+  String cluster;
+  List<ResourceAccess> accesses;
+  String operationName;
+  Object operationParams;
+
+  public static AccessContextBuilder builder() {
+    return new AccessContextBuilder();
+  }
+
+  public static final class AccessContextBuilder {
+
+    private String cluster;
+    private String operationName;
+    private Object operationParams;
+    private List<ResourceAccess> accesses = new ArrayList<>();
+
+    private AccessContextBuilder() {
+    }
+
+    public AccessContextBuilder applicationConfigActions(ApplicationConfigAction... actions) {
+      accesses.add(new ResourceAccess(null, ResourceV2.APPLICATIONCONFIG, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder cluster(String cluster) {
+      this.cluster = cluster;
+      return this;
+    }
+
+    public AccessContextBuilder clusterConfigActions(ClusterConfigAction... actions) {
+      accesses.add(new ResourceAccess(null, ResourceV2.CLUSTERCONFIG, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder topicActions(String topic, TopicAction... actions) {
+      accesses.add(new ResourceAccess(topic, ResourceV2.APPLICATIONCONFIG, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder consumerGroupActions(String consumerGroup, ConsumerGroupAction actions) {
+      accesses.add(new ResourceAccess(consumerGroup, ResourceV2.CONSUMER, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder connectActions(String connect, ConnectAction... actions) {
+      accesses.add(new ResourceAccess(connect, ResourceV2.CONSUMER, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder schemaActions(String schema, SchemaAction... actions) {
+      accesses.add(new ResourceAccess(schema, ResourceV2.SCHEMA, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder ksqlActions(KsqlAction... actions) {
+      accesses.add(new ResourceAccess(null, ResourceV2.KSQL, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder aclActions(AclAction... actions) {
+      accesses.add(new ResourceAccess(null, ResourceV2.ACL, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder auditActions(AuditAction... actions) {
+      Assert.isTrue(actions.length > 0, "actions not present");
+      accesses.add(new ResourceAccess(null, ResourceV2.AUDIT, List.of(actions)));
+      return this;
+    }
+
+    public AccessContextBuilder operationName(String operationName) {
+      this.operationName = operationName;
+      return this;
+    }
+
+    public AccessContextBuilder operationParams(Object operationParams) {
+      this.operationParams = operationParams;
+      return this;
+    }
+
+    public AccessContextV2 build() {
+      return new AccessContextV2(cluster, accesses, operationName, operationParams);
+    }
+  }
+}

+ 5 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/Permission.java

@@ -13,6 +13,7 @@ import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
 import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
 import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
 import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
+import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
 import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
 import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
 import java.util.Arrays;
@@ -35,7 +36,10 @@ public class Permission {
       List.of(KSQL, CLUSTERCONFIG, APPLICATIONCONFIG, ACL, AUDIT);
 
   Resource resource;
+  ResourceV2 resourceV2;
   List<String> actions;
+  List<PermissibleAction> actionsv2;
+
 
   @Nullable
   String value;
@@ -55,6 +59,7 @@ public class Permission {
   @SuppressWarnings("unused")
   public void setActions(List<String> actions) {
     this.actions = actions;
+    this.actionsv2 = resourceV2.parseActions(actions); //TODO: setting order ? set on transform?
   }
 
   public void validate() {

+ 55 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/ResourceV2.java

@@ -0,0 +1,55 @@
+package com.provectus.kafka.ui.model.rbac;
+
+import com.provectus.kafka.ui.model.rbac.permission.AclAction;
+import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
+import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
+import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
+import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
+import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
+import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
+import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
+import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
+import java.util.List;
+
+public enum ResourceV2 {
+
+  APPLICATIONCONFIG(ApplicationConfigAction.values()),
+
+  CLUSTERCONFIG(ClusterConfigAction.values()),
+
+  TOPIC(TopicAction.values()),
+
+  CONSUMER(ConsumerGroupAction.values()),
+
+  SCHEMA(SchemaAction.values()),
+
+  CONNECT(ConnectAction.values()),
+
+  KSQL(KsqlAction.values()),
+
+  ACL(AclAction.values()),
+
+  AUDIT(AclAction.values());
+
+  private final List<PermissibleAction> actions;
+
+  ResourceV2(PermissibleAction[] actions) {
+    this.actions = List.of(actions);
+  }
+
+  public List<PermissibleAction> allActions() {
+    return actions;
+  }
+
+  public List<PermissibleAction> parseActions(List<String> actions) {
+    return actions.stream()
+        .map(String::toUpperCase)
+        .map(toParse -> allActions().stream()
+            .filter(a -> toParse.equals(a.name().toUpperCase()))
+            .findFirst().
+            orElseThrow() //TODO err msg
+        )
+        .toList();
+  }
+
+}

+ 50 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java

@@ -9,12 +9,15 @@ import com.provectus.kafka.ui.model.ClusterDTO;
 import com.provectus.kafka.ui.model.ConnectDTO;
 import com.provectus.kafka.ui.model.InternalTopic;
 import com.provectus.kafka.ui.model.rbac.AccessContext;
+import com.provectus.kafka.ui.model.rbac.AccessContextV2;
 import com.provectus.kafka.ui.model.rbac.Permission;
 import com.provectus.kafka.ui.model.rbac.Resource;
+import com.provectus.kafka.ui.model.rbac.ResourceV2;
 import com.provectus.kafka.ui.model.rbac.Role;
 import com.provectus.kafka.ui.model.rbac.Subject;
 import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
 import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
+import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
 import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
 import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
 import com.provectus.kafka.ui.service.rbac.extractor.CognitoAuthorityExtractor;
@@ -94,6 +97,40 @@ public class AccessControlService {
     }
   }
 
+  public Mono<Void> validateAccess(AccessContextV2 context) {
+    if (!rbacEnabled) {
+      return Mono.empty();
+    }
+
+    return getUser()
+        .doOnNext(user -> {
+          if (context.getCluster() != null && !isClusterAccessible(context.getCluster(), user)) {
+            throw new AccessDeniedException(ACCESS_DENIED);
+          }
+          context.getAccesses().forEach(resourceAccess -> {
+            @Nullable String name = resourceAccess.name();
+            ResourceV2 type = resourceAccess.type();
+            List<PermissibleAction> requestedActions = resourceAccess.actions();
+
+            Set<PermissibleAction> allowedActions = properties.getRoles().stream()
+                .filter(filterRole(user))
+                .flatMap(role -> role.getPermissions().stream())
+                .filter(permission -> permission.getResourceV2() == type && Objects.equals(permission.getValue(), name))
+                .flatMap(p -> p.getActionsv2().stream())
+                .collect(Collectors.toSet());
+
+            if (allowedActions.isEmpty()) {
+              //TODO: do smth?
+            }
+
+            if (!allowedActions.containsAll(requestedActions)) {
+              throw new AccessDeniedException(ACCESS_DENIED);
+            }
+          });
+        })
+        .then();
+  }
+
   public Mono<Void> validateAccess(AccessContext context) {
     if (!rbacEnabled) {
       return Mono.empty();
@@ -154,6 +191,19 @@ public class AccessControlService {
     return isAccessible(APPLICATIONCONFIG, null, user, context, requiredActions);
   }
 
+  private boolean isClusterAccessible(String clusterName, AuthenticatedUser user) {
+    if (!rbacEnabled) {
+      return true;
+    }
+
+    Assert.isTrue(StringUtils.isNotEmpty(clusterName), "cluster value is empty");
+
+    return properties.getRoles()
+        .stream()
+        .filter(filterRole(user))
+        .anyMatch(filterCluster(clusterName));
+  }
+
   private boolean isClusterAccessible(AccessContext context, AuthenticatedUser user) {
     if (!rbacEnabled) {
       return true;