Skip to content

Commit

Permalink
Optimized claims by removing unnecessary ACL contained in another ACL (
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexisSouquiere authored Aug 28, 2023
1 parent 694bb6b commit 8747c0a
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ public AKHQClaimResponseV3 generateClaimV3(@Valid @Body AKHQClaimRequest request
// Add all public ACLs
relatedACL.addAll(accessControlEntryService.findAllPublicGrantedTo());

// Remove unnecessary ACLs (project.topic1 when project.* is granted on the same resource type and cluster)
optimizeACL(relatedACL);

Map<String, AKHQClaimResponseV3.Group> bindings = new LinkedHashMap<>();

// Start by creating a map that store permissions by role/cluster
Expand Down Expand Up @@ -270,6 +273,25 @@ public List<String> computeAllowedRegexListForResourceType(List<AccessControlEnt
return !allowedRegex.isEmpty() ? allowedRegex : EMPTY_REGEXP;
}

/**
* Remove ACL that are already included by another ACL on the same resource and cluster
* Ex: LITERAL ACL1 with project.topic1 resource + PREFIXED ACL2 with project -> return ACL2 only
*
* @param acl the input list of acl to optimize
*/
private static void optimizeACL(List<AccessControlEntry> acl) {
acl.removeIf(accessControlEntry -> acl.stream()
// Keep PREFIXED ACL with a different resource but same resource type and cluster
.filter(accessControlEntryOther ->
accessControlEntryOther.getSpec().getResourcePatternType().equals(AccessControlEntry.ResourcePatternType.PREFIXED)
&& !accessControlEntryOther.getSpec().getResource().equals(accessControlEntry.getSpec().getResource())
&& accessControlEntryOther.getSpec().getResourceType().equals(accessControlEntry.getSpec().getResourceType())
&& accessControlEntryOther.getMetadata().getCluster().equals(accessControlEntry.getMetadata().getCluster()))
.map(accessControlEntryOther -> accessControlEntryOther.getSpec().getResource())
// Remove the ACL if there is one that contains the current resource
.anyMatch(escapedString -> accessControlEntry.getSpec().getResource().startsWith(escapedString)));
}

@Introspected
@Builder
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,4 +357,121 @@ void generateClaimWithPatternOnMultipleClusters() {
Assertions.assertEquals(List.of("^\\Qproject2_t.\\E.*$"), groups.get(3).getPatterns());
Assertions.assertEquals(List.of("^cluster2$"), groups.get(3).getClusters());
}

@Test
void generateClaimAndOptimizePatterns(){
Namespace ns1Cluster1 = Namespace.builder()
.metadata(ObjectMeta.builder()
.name("ns1").cluster("cluster1").labels(Map.of("support-group", "GP-PROJECT1&2-SUPPORT"))
.build())
.build();
List<AccessControlEntry> inputACLs = List.of(
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.PREFIXED)
.resource("project1.")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.LITERAL)
.resource("project1.topic1")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.CONNECT)
.resourcePatternType(AccessControlEntry.ResourcePatternType.LITERAL)
.resource("project1.topic1")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.LITERAL)
.resource("project2.topic2")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.LITERAL)
.resource("project2.topic2a")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.LITERAL)
.resource("project2.topic3")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.CONNECT)
.resourcePatternType(AccessControlEntry.ResourcePatternType.PREFIXED)
.resource("project2.")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.LITERAL)
.resource("project3.topic4")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.LITERAL)
.resource("project3.topic5")
.build())
.build(),
AccessControlEntry.builder()
.metadata(ObjectMeta.builder().cluster("cluster1").build())
.spec(AccessControlEntry.AccessControlEntrySpec.builder()
.resourceType(AccessControlEntry.ResourceType.TOPIC)
.resourcePatternType(AccessControlEntry.ResourcePatternType.PREFIXED)
.resource("project3.")
.build())
.build()
);
akhqClaimProviderController.managedClusters = List.of(new KafkaAsyncExecutorConfig("cluster1"), new KafkaAsyncExecutorConfig("cluster2"));
Mockito.when(namespaceService.listAll()).thenReturn(List.of(ns1Cluster1));
Mockito.when(accessControlEntryService.findAllGrantedToNamespace(ns1Cluster1)).thenReturn(inputACLs);

AkhqClaimProviderController.AKHQClaimRequest request = AkhqClaimProviderController.AKHQClaimRequest.builder()
.groups(List.of("GP-PROJECT1&2-SUPPORT"))
.build();
AkhqClaimProviderController.AKHQClaimResponseV3 actual = akhqClaimProviderController.generateClaimV3(request);

List<AkhqClaimProviderController.AKHQClaimResponseV3.Group> groups = actual.getGroups().get("group");
Assertions.assertEquals(3, groups.size());
Assertions.assertEquals("topic-read", groups.get(0).getRole());
Assertions.assertEquals(
List.of("^\\Qproject1.\\E.*$", "^\\Qproject2.topic2\\E$", "^\\Qproject2.topic2a\\E$", "^\\Qproject2.topic3\\E$", "^\\Qproject3.\\E.*$"),
groups.get(0).getPatterns()
);
Assertions.assertEquals("connect-rw", groups.get(1).getRole());
Assertions.assertEquals(
List.of("^\\Qproject1.topic1\\E$", "^\\Qproject2.\\E.*$"),
groups.get(1).getPatterns()
);
Assertions.assertEquals("registry-read", groups.get(2).getRole());
Assertions.assertEquals(
List.of("^\\Qproject1.\\E.*$", "^\\Qproject2.topic2\\E$", "^\\Qproject2.topic2a\\E$", "^\\Qproject2.topic3\\E$", "^\\Qproject3.\\E.*$"),
groups.get(2).getPatterns()
);
}
}

0 comments on commit 8747c0a

Please sign in to comment.