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

Add ability to hide specific ACL entries from listing #217

Merged
merged 1 commit into from
Nov 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)$",
Original file line number Diff line number Diff line change
@@ -274,6 +274,7 @@ public final List<? extends CompletionStage<AclDeleteResult>> deleteAcls(
public final Iterable<AclBinding> 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());
30 changes: 24 additions & 6 deletions src/main/java/io/aiven/kafka/auth/json/AivenAcl.java
Original file line number Diff line number Diff line change
@@ -47,20 +47,24 @@ 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;
this.operationRe = Pattern.compile(operation);
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;
}
}
6 changes: 3 additions & 3 deletions src/test/java/io/aiven/kafka/auth/json/AivenAclTest.java
Original file line number Diff line number Diff line change
@@ -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"));
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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
)
);

3 changes: 2 additions & 1 deletion src/test/resources/acls_full.json
Original file line number Diff line number Diff line change
@@ -107,6 +107,7 @@
"principal": "^pass-14$",
"host": "example.com",
"operation": "^Read$",
"resource": "^Topic:(.*)$"
"resource": "^Topic:(.*)$",
"hidden": true
}
]
7 changes: 7 additions & 0 deletions src/test/resources/test_acls_for_acls_method.json
Original file line number Diff line number Diff line change
@@ -34,5 +34,12 @@
"principal": "^(.*)$",
"principal_type": "Service",
"resource": "^(.*)$"
},
{
"operation": "^(Delete)$",
"principal": "^(test\\-user)$",
"principal_type": "User",
"resource": "^Topic:(hidden\\.(.*))$",
"hidden": true
}
]