diff --git a/README.md b/README.md index 5461393..bda0571 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ for each project. Permission type allows to define the verification result in case of an ACL match. By default, the permission type is `ALLOW`. +A specific ACL entry can be hidden from public listing by setting hidden flag. + ### Example [ @@ -31,6 +33,7 @@ By default, the permission type is `ALLOW`. "principal_type": "Prune", "host": "*", "resource": "^(.*)$", + "hidden": true }, { "operation": "^(Describe|DescribeConfigs|Read|Write)$", diff --git a/src/main/java/io/aiven/kafka/auth/AivenAclAuthorizerV2.java b/src/main/java/io/aiven/kafka/auth/AivenAclAuthorizerV2.java index 7e598be..7a50433 100644 --- a/src/main/java/io/aiven/kafka/auth/AivenAclAuthorizerV2.java +++ b/src/main/java/io/aiven/kafka/auth/AivenAclAuthorizerV2.java @@ -274,6 +274,7 @@ public final List> deleteAcls( public final Iterable acls(final AclBindingFilter filter) { if (this.config.listAclsEnabled()) { return this.cacheReference.get().aclEntries() + .filter(acl -> !acl.isHidden()) .flatMap(acl -> AclAivenToNativeConverter.convert(acl).stream()) .filter(filter::matches) .collect(Collectors.toList()); diff --git a/src/main/java/io/aiven/kafka/auth/json/AivenAcl.java b/src/main/java/io/aiven/kafka/auth/json/AivenAcl.java index aaab9a7..5cc9c56 100644 --- a/src/main/java/io/aiven/kafka/auth/json/AivenAcl.java +++ b/src/main/java/io/aiven/kafka/auth/json/AivenAcl.java @@ -47,13 +47,16 @@ public class AivenAcl { @SerializedName("host") private final String hostMatcher; + private final boolean hidden; + public AivenAcl(final String principalType, final String principal, final String host, final String operation, final String resource, final String resourcePattern, - final AclPermissionType permissionType) { + final AclPermissionType permissionType, + final boolean hidden) { this.principalType = principalType; this.principalRe = Pattern.compile(principal); this.hostMatcher = host; @@ -61,6 +64,7 @@ public AivenAcl(final String principalType, this.resourceRe = Objects.nonNull(resource) ? Pattern.compile(resource) : null; this.resourceRePattern = resourcePattern; this.permissionType = Objects.requireNonNullElse(permissionType, AclPermissionType.ALLOW); + this.hidden = hidden; } public AclPermissionType getPermissionType() { @@ -119,13 +123,22 @@ public boolean equals(final Object o) { return false; } final AivenAcl aivenAcl = (AivenAcl) o; - return Objects.equals(principalType, aivenAcl.principalType) - && comparePattern(principalRe, aivenAcl.principalRe) + return equalsPrincipal(aivenAcl) && getHostMatcher().equals(aivenAcl.getHostMatcher()) && comparePattern(operationRe, aivenAcl.operationRe) - && comparePattern(resourceRe, aivenAcl.resourceRe) - && Objects.equals(resourceRePattern, aivenAcl.resourceRePattern) - && getPermissionType() == aivenAcl.getPermissionType(); // always compare permission type using getter + && equalsResource(aivenAcl) + && getPermissionType() == aivenAcl.getPermissionType() // always compare permission type using getter + && hidden == aivenAcl.hidden; + } + + private boolean equalsPrincipal(final AivenAcl aivenAcl) { + return Objects.equals(principalType, aivenAcl.principalType) + && comparePattern(principalRe, aivenAcl.principalRe); + } + + private boolean equalsResource(final AivenAcl aivenAcl) { + return comparePattern(resourceRe, aivenAcl.resourceRe) + && Objects.equals(resourceRePattern, aivenAcl.resourceRePattern); } private boolean comparePattern(final Pattern p1, final Pattern p2) { @@ -158,6 +171,11 @@ public String toString() { + ", resourceRePattern='" + resourceRePattern + "', permissionType=" + getPermissionType() + ", hostMatcher='" + getHostMatcher() + + ", hidden=" + hidden + "'}"; } + + public boolean isHidden() { + return hidden; + } } diff --git a/src/test/java/io/aiven/kafka/auth/json/AivenAclTest.java b/src/test/java/io/aiven/kafka/auth/json/AivenAclTest.java index f80235f..3aec01b 100644 --- a/src/test/java/io/aiven/kafka/auth/json/AivenAclTest.java +++ b/src/test/java/io/aiven/kafka/auth/json/AivenAclTest.java @@ -32,7 +32,7 @@ public void testAivenAclEntry() { "^(Describe|Read)$", // operation "^Topic:p_(.*)_s", // resource, null, // resource pattern - null + null, false ); assertTrue(entry.match("User", "CN=p_pass_s", "*", "Read", "Topic:p_pass_s")); @@ -49,7 +49,7 @@ public void testAivenAclEntry() { "^(Describe|Read)$", // operation "^Topic:p_(.*)_s", // resource null, // resource pattern - null + null, false ); assertTrue(entry.match("User", "CN=p_pass_s", "*", "Read", "Topic:p_pass_s")); @@ -65,7 +65,7 @@ public void testAivenAclEntry() { "^(Describe|Read)$", // operation null, // resource "^Topic:p_${username}_s\\$", // resource pattern - null + null, false ); assertTrue(entry.match("User", "CN=p_user1_s", "*", "Read", "Topic:p_user1_s")); diff --git a/src/test/java/io/aiven/kafka/auth/json/reader/AclJsonReaderTest.java b/src/test/java/io/aiven/kafka/auth/json/reader/AclJsonReaderTest.java index 877e0aa..2ecd22f 100644 --- a/src/test/java/io/aiven/kafka/auth/json/reader/AclJsonReaderTest.java +++ b/src/test/java/io/aiven/kafka/auth/json/reader/AclJsonReaderTest.java @@ -36,27 +36,28 @@ public final void parseAcls() { final var jsonReader = new AclJsonReader(path); final var acls = jsonReader.read(); assertThat(acls).containsExactly( - new AivenAcl("User", "^pass-3$", "*", "^Read$", "^Topic:denied$", null, AclPermissionType.DENY), - new AivenAcl("User", "^pass-0$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-1$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-2$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-3$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-4$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-5$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-6$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-7$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-8$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-9$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-10$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-11$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-12$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl(null, "^pass-notype$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), + new AivenAcl("User", "^pass-3$", "*", "^Read$", "^Topic:denied$", null, AclPermissionType.DENY, false), + new AivenAcl("User", "^pass-0$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-1$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-2$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-3$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-4$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-5$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-6$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-7$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-8$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-9$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-10$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-11$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-12$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl(null, "^pass-notype$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), new AivenAcl( "User", "^pass-resource-pattern$", "*", "^Read$", - null, "^Topic:${projectid}-(.*)", AclPermissionType.ALLOW + null, "^Topic:${projectid}-(.*)", AclPermissionType.ALLOW, false ), - new AivenAcl("User", "^pass-13$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW), - new AivenAcl("User", "^pass-14$", "example.com", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW) + new AivenAcl("User", "^pass-13$", "*", "^Read$", "^Topic:(.*)$", null, AclPermissionType.ALLOW, false), + new AivenAcl("User", "^pass-14$", "example.com", "^Read$", "^Topic:(.*)$", + null, AclPermissionType.ALLOW, true) ); } @@ -72,7 +73,7 @@ public final void parseDenyAcl() { "^Read$", "^(.*)$", null, - AclPermissionType.ALLOW + AclPermissionType.ALLOW, false ); final var denyAcl = new AivenAcl( "User", @@ -81,7 +82,7 @@ public final void parseDenyAcl() { "^Read$", "^(.*)$", null, - AclPermissionType.DENY + AclPermissionType.DENY, false ); assertThat(acls).containsExactly(allowAcl, allowAcl, allowAcl, denyAcl, denyAcl); } diff --git a/src/test/java/io/aiven/kafka/auth/nativeacls/AclAivenToNativeConverterTest.java b/src/test/java/io/aiven/kafka/auth/nativeacls/AclAivenToNativeConverterTest.java index da0cd2e..a60102c 100644 --- a/src/test/java/io/aiven/kafka/auth/nativeacls/AclAivenToNativeConverterTest.java +++ b/src/test/java/io/aiven/kafka/auth/nativeacls/AclAivenToNativeConverterTest.java @@ -44,7 +44,7 @@ public final void testConvertSimple() { "^(Alter|AlterConfigs|Delete|Read|Write)$", "^Topic:(xxx)$", null, - io.aiven.kafka.auth.json.AclPermissionType.ALLOW + io.aiven.kafka.auth.json.AclPermissionType.ALLOW, false ) ); final ResourcePattern resourcePattern = new ResourcePattern(ResourceType.TOPIC, "xxx", PatternType.LITERAL); @@ -77,7 +77,7 @@ public final void testNullPermissionTypeIsAllow() { "^Read$", "^Topic:(xxx)$", null, - null + null, false ) ); final ResourcePattern resourcePattern = new ResourcePattern(ResourceType.TOPIC, "xxx", PatternType.LITERAL); @@ -98,7 +98,7 @@ public final void testConvertPrefix() { "^Read$", "^Topic:(topic\\.(.*))$", null, - null + null, false ) ); assertThat(result).containsExactly( @@ -119,7 +119,7 @@ public final void testDeny() { "^Read$", "^Topic:(topic\\.(.*))$", null, - io.aiven.kafka.auth.json.AclPermissionType.DENY + io.aiven.kafka.auth.json.AclPermissionType.DENY, false ) ); assertThat(result).containsExactly( @@ -140,7 +140,7 @@ public final void testConvertMultiplePrefixes() { "^(Delete|Read|Write)$", "^Topic:(topic\\.(.*)|prefix\\-(.*))$", null, - null + null, false ) ); assertThat(result).containsExactly( @@ -181,7 +181,7 @@ public final void testSuperadmin() { "^(.*)$", "^(.*)$", null, - null + null, false ) ); @@ -215,7 +215,7 @@ public final void testAllUsers() { "^Read$", "^Topic:(xxx)$", null, - null + null, false ) ); @@ -237,7 +237,7 @@ public final void testNoUserPrincipalType() { "^Read$", "^Topic:(xxx)$", null, - null + null, false ) ); @@ -254,7 +254,7 @@ public final void testConvertHostMatcher() { "^Read$", "^Topic:(xxx)$", null, - null + null, false ) ); diff --git a/src/test/resources/acls_full.json b/src/test/resources/acls_full.json index cae9a1c..43023b5 100644 --- a/src/test/resources/acls_full.json +++ b/src/test/resources/acls_full.json @@ -107,6 +107,7 @@ "principal": "^pass-14$", "host": "example.com", "operation": "^Read$", - "resource": "^Topic:(.*)$" + "resource": "^Topic:(.*)$", + "hidden": true } ] diff --git a/src/test/resources/test_acls_for_acls_method.json b/src/test/resources/test_acls_for_acls_method.json index 0381bce..4f9218f 100644 --- a/src/test/resources/test_acls_for_acls_method.json +++ b/src/test/resources/test_acls_for_acls_method.json @@ -34,5 +34,12 @@ "principal": "^(.*)$", "principal_type": "Service", "resource": "^(.*)$" + }, + { + "operation": "^(Delete)$", + "principal": "^(test\\-user)$", + "principal_type": "User", + "resource": "^Topic:(hidden\\.(.*))$", + "hidden": true } ]