Merge branch 'master' of github.com:provectus/kafka-ui into ISSUE-3732_quotas_apis
Conflicts: kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/permission/PermissibleAction.java
This commit is contained in:
commit
f69c484dd7
17 changed files with 213 additions and 20 deletions
|
@ -152,7 +152,13 @@ public class ClustersProperties {
|
||||||
Integer auditTopicsPartitions;
|
Integer auditTopicsPartitions;
|
||||||
Boolean topicAuditEnabled;
|
Boolean topicAuditEnabled;
|
||||||
Boolean consoleAuditEnabled;
|
Boolean consoleAuditEnabled;
|
||||||
|
LogLevel level;
|
||||||
Map<String, String> auditTopicProperties;
|
Map<String, String> auditTopicProperties;
|
||||||
|
|
||||||
|
public enum LogLevel {
|
||||||
|
ALL,
|
||||||
|
ALTER_ONLY //default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public enum AclAction implements PermissibleAction {
|
public enum AclAction implements PermissibleAction {
|
||||||
|
|
||||||
VIEW,
|
VIEW,
|
||||||
EDIT;
|
EDIT
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public static final Set<AclAction> ALTER_ACTIONS = Set.of(EDIT);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static AclAction fromString(String name) {
|
public static AclAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(AclAction.class, name);
|
return EnumUtils.getEnum(AclAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -10,9 +11,15 @@ public enum ApplicationConfigAction implements PermissibleAction {
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
public static final Set<ApplicationConfigAction> ALTER_ACTIONS = Set.of(EDIT);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static ApplicationConfigAction fromString(String name) {
|
public static ApplicationConfigAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(ApplicationConfigAction.class, name);
|
return EnumUtils.getEnum(ApplicationConfigAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public enum AuditAction implements PermissibleAction {
|
public enum AuditAction implements PermissibleAction {
|
||||||
|
|
||||||
VIEW;
|
VIEW
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private static final Set<AuditAction> ALTER_ACTIONS = Set.of();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static AuditAction fromString(String name) {
|
public static AuditAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(AuditAction.class, name);
|
return EnumUtils.getEnum(AuditAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -10,9 +11,15 @@ public enum ClusterConfigAction implements PermissibleAction {
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
public static final Set<ClusterConfigAction> ALTER_ACTIONS = Set.of(EDIT);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static ClusterConfigAction fromString(String name) {
|
public static ClusterConfigAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(ClusterConfigAction.class, name);
|
return EnumUtils.getEnum(ClusterConfigAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -12,9 +13,15 @@ public enum ConnectAction implements PermissibleAction {
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
public static final Set<ConnectAction> ALTER_ACTIONS = Set.of(CREATE, EDIT, RESTART);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static ConnectAction fromString(String name) {
|
public static ConnectAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(ConnectAction.class, name);
|
return EnumUtils.getEnum(ConnectAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -7,14 +8,19 @@ public enum ConsumerGroupAction implements PermissibleAction {
|
||||||
|
|
||||||
VIEW,
|
VIEW,
|
||||||
DELETE,
|
DELETE,
|
||||||
|
|
||||||
RESET_OFFSETS
|
RESET_OFFSETS
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
public static final Set<ConsumerGroupAction> ALTER_ACTIONS = Set.of(DELETE, RESET_OFFSETS);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static ConsumerGroupAction fromString(String name) {
|
public static ConsumerGroupAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(ConsumerGroupAction.class, name);
|
return EnumUtils.getEnum(ConsumerGroupAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,24 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public enum KsqlAction implements PermissibleAction {
|
public enum KsqlAction implements PermissibleAction {
|
||||||
|
|
||||||
EXECUTE;
|
EXECUTE
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public static final Set<KsqlAction> ALTER_ACTIONS = Set.of(EXECUTE);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static KsqlAction fromString(String name) {
|
public static KsqlAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(KsqlAction.class, name);
|
return EnumUtils.getEnum(KsqlAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,9 @@ public sealed interface PermissibleAction permits
|
||||||
ConsumerGroupAction, SchemaAction,
|
ConsumerGroupAction, SchemaAction,
|
||||||
ConnectAction, ClusterConfigAction,
|
ConnectAction, ClusterConfigAction,
|
||||||
KsqlAction, TopicAction, AuditAction, ClientQuotaAction {
|
KsqlAction, TopicAction, AuditAction, ClientQuotaAction {
|
||||||
|
|
||||||
|
String name();
|
||||||
|
|
||||||
|
boolean isAlter();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -13,9 +14,15 @@ public enum SchemaAction implements PermissibleAction {
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
public static final Set<SchemaAction> ALTER_ACTIONS = Set.of(CREATE, DELETE, EDIT, MODIFY_GLOBAL_COMPATIBILITY);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static SchemaAction fromString(String name) {
|
public static SchemaAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(SchemaAction.class, name);
|
return EnumUtils.getEnum(SchemaAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.model.rbac.permission;
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -9,16 +10,21 @@ public enum TopicAction implements PermissibleAction {
|
||||||
CREATE,
|
CREATE,
|
||||||
EDIT,
|
EDIT,
|
||||||
DELETE,
|
DELETE,
|
||||||
|
|
||||||
MESSAGES_READ,
|
MESSAGES_READ,
|
||||||
MESSAGES_PRODUCE,
|
MESSAGES_PRODUCE,
|
||||||
MESSAGES_DELETE,
|
MESSAGES_DELETE,
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
public static final Set<TopicAction> ALTER_ACTIONS = Set.of(CREATE, EDIT, DELETE, MESSAGES_PRODUCE, MESSAGES_DELETE);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static TopicAction fromString(String name) {
|
public static TopicAction fromString(String name) {
|
||||||
return EnumUtils.getEnum(TopicAction.class, name);
|
return EnumUtils.getEnum(TopicAction.class, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.provectus.kafka.ui.exception.CustomBaseException;
|
||||||
import com.provectus.kafka.ui.exception.ValidationException;
|
import com.provectus.kafka.ui.exception.ValidationException;
|
||||||
import com.provectus.kafka.ui.model.rbac.AccessContext;
|
import com.provectus.kafka.ui.model.rbac.AccessContext;
|
||||||
import com.provectus.kafka.ui.model.rbac.Resource;
|
import com.provectus.kafka.ui.model.rbac.Resource;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -33,16 +34,20 @@ record AuditRecord(String timestamp,
|
||||||
return MAPPER.writeValueAsString(this);
|
return MAPPER.writeValueAsString(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
record AuditResource(String accessType, Resource type, @Nullable Object id) {
|
record AuditResource(String accessType, boolean alter, Resource type, @Nullable Object id) {
|
||||||
|
|
||||||
|
private static AuditResource create(PermissibleAction action, Resource type, @Nullable Object id) {
|
||||||
|
return new AuditResource(action.name(), action.isAlter(), type, id);
|
||||||
|
}
|
||||||
|
|
||||||
static List<AuditResource> getAccessedResources(AccessContext ctx) {
|
static List<AuditResource> getAccessedResources(AccessContext ctx) {
|
||||||
List<AuditResource> resources = new ArrayList<>();
|
List<AuditResource> resources = new ArrayList<>();
|
||||||
ctx.getClusterConfigActions()
|
ctx.getClusterConfigActions()
|
||||||
.forEach(a -> resources.add(new AuditResource(a.name(), Resource.CLUSTERCONFIG, null)));
|
.forEach(a -> resources.add(create(a, Resource.CLUSTERCONFIG, null)));
|
||||||
ctx.getTopicActions()
|
ctx.getTopicActions()
|
||||||
.forEach(a -> resources.add(new AuditResource(a.name(), Resource.TOPIC, nameId(ctx.getTopic()))));
|
.forEach(a -> resources.add(create(a, Resource.TOPIC, nameId(ctx.getTopic()))));
|
||||||
ctx.getConsumerGroupActions()
|
ctx.getConsumerGroupActions()
|
||||||
.forEach(a -> resources.add(new AuditResource(a.name(), Resource.CONSUMER, nameId(ctx.getConsumerGroup()))));
|
.forEach(a -> resources.add(create(a, Resource.CONSUMER, nameId(ctx.getConsumerGroup()))));
|
||||||
ctx.getConnectActions()
|
ctx.getConnectActions()
|
||||||
.forEach(a -> {
|
.forEach(a -> {
|
||||||
Map<String, String> resourceId = new LinkedHashMap<>();
|
Map<String, String> resourceId = new LinkedHashMap<>();
|
||||||
|
@ -50,16 +55,16 @@ record AuditRecord(String timestamp,
|
||||||
if (ctx.getConnector() != null) {
|
if (ctx.getConnector() != null) {
|
||||||
resourceId.put("connector", ctx.getConnector());
|
resourceId.put("connector", ctx.getConnector());
|
||||||
}
|
}
|
||||||
resources.add(new AuditResource(a.name(), Resource.CONNECT, resourceId));
|
resources.add(create(a, Resource.CONNECT, resourceId));
|
||||||
});
|
});
|
||||||
ctx.getSchemaActions()
|
ctx.getSchemaActions()
|
||||||
.forEach(a -> resources.add(new AuditResource(a.name(), Resource.SCHEMA, nameId(ctx.getSchema()))));
|
.forEach(a -> resources.add(create(a, Resource.SCHEMA, nameId(ctx.getSchema()))));
|
||||||
ctx.getKsqlActions()
|
ctx.getKsqlActions()
|
||||||
.forEach(a -> resources.add(new AuditResource(a.name(), Resource.KSQL, null)));
|
.forEach(a -> resources.add(create(a, Resource.KSQL, null)));
|
||||||
ctx.getAclActions()
|
ctx.getAclActions()
|
||||||
.forEach(a -> resources.add(new AuditResource(a.name(), Resource.ACL, null)));
|
.forEach(a -> resources.add(create(a, Resource.ACL, null)));
|
||||||
ctx.getAuditAction()
|
ctx.getAuditAction()
|
||||||
.forEach(a -> resources.add(new AuditResource(a.name(), Resource.AUDIT, null)));
|
.forEach(a -> resources.add(create(a, Resource.AUDIT, null)));
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.provectus.kafka.ui.service.audit;
|
package com.provectus.kafka.ui.service.audit;
|
||||||
|
|
||||||
|
import static com.provectus.kafka.ui.config.ClustersProperties.AuditProperties.LogLevel.ALTER_ONLY;
|
||||||
import static com.provectus.kafka.ui.service.MessagesService.createProducer;
|
import static com.provectus.kafka.ui.service.MessagesService.createProducer;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
@ -80,12 +81,13 @@ public class AuditService implements Closeable {
|
||||||
}
|
}
|
||||||
boolean topicAudit = Optional.ofNullable(auditProps.getTopicAuditEnabled()).orElse(false);
|
boolean topicAudit = Optional.ofNullable(auditProps.getTopicAuditEnabled()).orElse(false);
|
||||||
boolean consoleAudit = Optional.ofNullable(auditProps.getConsoleAuditEnabled()).orElse(false);
|
boolean consoleAudit = Optional.ofNullable(auditProps.getConsoleAuditEnabled()).orElse(false);
|
||||||
|
boolean alterLogOnly = Optional.ofNullable(auditProps.getLevel()).map(lvl -> lvl == ALTER_ONLY).orElse(true);
|
||||||
if (!topicAudit && !consoleAudit) {
|
if (!topicAudit && !consoleAudit) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
if (!topicAudit) {
|
if (!topicAudit) {
|
||||||
log.info("Audit initialization finished for cluster '{}' (console only)", cluster.getName());
|
log.info("Audit initialization finished for cluster '{}' (console only)", cluster.getName());
|
||||||
return Optional.of(consoleOnlyWriter(cluster));
|
return Optional.of(consoleOnlyWriter(cluster, alterLogOnly));
|
||||||
}
|
}
|
||||||
String auditTopicName = Optional.ofNullable(auditProps.getTopic()).orElse(DEFAULT_AUDIT_TOPIC_NAME);
|
String auditTopicName = Optional.ofNullable(auditProps.getTopic()).orElse(DEFAULT_AUDIT_TOPIC_NAME);
|
||||||
boolean topicAuditCanBeDone = createTopicIfNeeded(cluster, acSupplier, auditTopicName, auditProps);
|
boolean topicAuditCanBeDone = createTopicIfNeeded(cluster, acSupplier, auditTopicName, auditProps);
|
||||||
|
@ -95,7 +97,7 @@ public class AuditService implements Closeable {
|
||||||
"Audit initialization finished for cluster '{}' (console only, topic audit init failed)",
|
"Audit initialization finished for cluster '{}' (console only, topic audit init failed)",
|
||||||
cluster.getName()
|
cluster.getName()
|
||||||
);
|
);
|
||||||
return Optional.of(consoleOnlyWriter(cluster));
|
return Optional.of(consoleOnlyWriter(cluster, alterLogOnly));
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
@ -103,6 +105,7 @@ public class AuditService implements Closeable {
|
||||||
return Optional.of(
|
return Optional.of(
|
||||||
new AuditWriter(
|
new AuditWriter(
|
||||||
cluster.getName(),
|
cluster.getName(),
|
||||||
|
alterLogOnly,
|
||||||
auditTopicName,
|
auditTopicName,
|
||||||
producerFactory.get(),
|
producerFactory.get(),
|
||||||
consoleAudit ? AUDIT_LOGGER : null
|
consoleAudit ? AUDIT_LOGGER : null
|
||||||
|
@ -110,8 +113,8 @@ public class AuditService implements Closeable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AuditWriter consoleOnlyWriter(KafkaCluster cluster) {
|
private static AuditWriter consoleOnlyWriter(KafkaCluster cluster, boolean alterLogOnly) {
|
||||||
return new AuditWriter(cluster.getName(), null, null, AUDIT_LOGGER);
|
return new AuditWriter(cluster.getName(), alterLogOnly, null, null, AUDIT_LOGGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.slf4j.Logger;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
record AuditWriter(String clusterName,
|
record AuditWriter(String clusterName,
|
||||||
|
boolean logAlterOperationsOnly,
|
||||||
@Nullable String targetTopic,
|
@Nullable String targetTopic,
|
||||||
@Nullable KafkaProducer<byte[], byte[]> producer,
|
@Nullable KafkaProducer<byte[], byte[]> producer,
|
||||||
@Nullable Logger consoleLogger) implements Closeable {
|
@Nullable Logger consoleLogger) implements Closeable {
|
||||||
|
@ -39,6 +40,10 @@ record AuditWriter(String clusterName,
|
||||||
}
|
}
|
||||||
|
|
||||||
private void write(AuditRecord rec) {
|
private void write(AuditRecord rec) {
|
||||||
|
if (logAlterOperationsOnly && rec.resources().stream().noneMatch(AuditResource::alter)) {
|
||||||
|
//we should only log alter operations, but this is read-only op
|
||||||
|
return;
|
||||||
|
}
|
||||||
String json = rec.toJson();
|
String json = rec.toJson();
|
||||||
if (consoleLogger != null) {
|
if (consoleLogger != null) {
|
||||||
consoleLogger.info(json);
|
consoleLogger.info(json);
|
||||||
|
|
|
@ -30,8 +30,8 @@ class AuditServiceTest {
|
||||||
@Test
|
@Test
|
||||||
void isAuditTopicChecksIfAuditIsEnabledForCluster() {
|
void isAuditTopicChecksIfAuditIsEnabledForCluster() {
|
||||||
Map<String, AuditWriter> writers = Map.of(
|
Map<String, AuditWriter> writers = Map.of(
|
||||||
"c1", new AuditWriter("с1", "c1topic", null, null),
|
"c1", new AuditWriter("с1", true, "c1topic", null, null),
|
||||||
"c2", new AuditWriter("c2", "c2topic", mock(KafkaProducer.class), null)
|
"c2", new AuditWriter("c2", false, "c2topic", mock(KafkaProducer.class), null)
|
||||||
);
|
);
|
||||||
|
|
||||||
var auditService = new AuditService(writers);
|
var auditService = new AuditService(writers);
|
||||||
|
@ -79,6 +79,17 @@ class AuditServiceTest {
|
||||||
.thenReturn(mock(KafkaProducer.class));
|
.thenReturn(mock(KafkaProducer.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void logOnlyAlterOpsByDefault() {
|
||||||
|
var auditProps = new ClustersProperties.AuditProperties();
|
||||||
|
auditProps.setConsoleAuditEnabled(true);
|
||||||
|
clustersProperties.setAudit(auditProps);
|
||||||
|
|
||||||
|
var maybeWriter = createAuditWriter(cluster, () -> adminClientMock, producerSupplierMock);
|
||||||
|
assertThat(maybeWriter)
|
||||||
|
.hasValueSatisfying(w -> assertThat(w.logAlterOperationsOnly()).isTrue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void noWriterIfNoAuditPropsSet() {
|
void noWriterIfNoAuditPropsSet() {
|
||||||
var maybeWriter = createAuditWriter(cluster, () -> adminClientMock, producerSupplierMock);
|
var maybeWriter = createAuditWriter(cluster, () -> adminClientMock, producerSupplierMock);
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.provectus.kafka.ui.service.audit;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
|
||||||
|
import com.provectus.kafka.ui.config.auth.AuthenticatedUser;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.AccessContext;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.AccessContext.AccessContextBuilder;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.permission.AclAction;
|
||||||
|
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.SchemaAction;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
class AuditWriterTest {
|
||||||
|
|
||||||
|
final KafkaProducer<byte[], byte[]> producerMock = Mockito.mock(KafkaProducer.class);
|
||||||
|
final Logger loggerMock = Mockito.mock(Logger.class);
|
||||||
|
final AuthenticatedUser user = new AuthenticatedUser("someone", List.of());
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class AlterOperationsOnlyWriter {
|
||||||
|
|
||||||
|
final AuditWriter alterOnlyWriter = new AuditWriter("test", true, "test-topic", producerMock, loggerMock);
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
void onlyLogsWhenAlterOperationIsPresentForOneOfResources(AccessContext ctxWithAlterOperation) {
|
||||||
|
alterOnlyWriter.write(ctxWithAlterOperation, user, null);
|
||||||
|
verify(producerMock).send(any(), any());
|
||||||
|
verify(loggerMock).info(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<AccessContext> onlyLogsWhenAlterOperationIsPresentForOneOfResources() {
|
||||||
|
Stream<UnaryOperator<AccessContextBuilder>> topicEditActions =
|
||||||
|
TopicAction.ALTER_ACTIONS.stream().map(a -> c -> c.topic("test").topicActions(a));
|
||||||
|
Stream<UnaryOperator<AccessContextBuilder>> clusterConfigEditActions =
|
||||||
|
ClusterConfigAction.ALTER_ACTIONS.stream().map(a -> c -> c.clusterConfigActions(a));
|
||||||
|
Stream<UnaryOperator<AccessContextBuilder>> aclEditActions =
|
||||||
|
AclAction.ALTER_ACTIONS.stream().map(a -> c -> c.aclActions(a));
|
||||||
|
Stream<UnaryOperator<AccessContextBuilder>> cgEditActions =
|
||||||
|
ConsumerGroupAction.ALTER_ACTIONS.stream().map(a -> c -> c.consumerGroup("cg").consumerGroupActions(a));
|
||||||
|
Stream<UnaryOperator<AccessContextBuilder>> schemaEditActions =
|
||||||
|
SchemaAction.ALTER_ACTIONS.stream().map(a -> c -> c.schema("sc").schemaActions(a));
|
||||||
|
Stream<UnaryOperator<AccessContextBuilder>> connEditActions =
|
||||||
|
ConnectAction.ALTER_ACTIONS.stream().map(a -> c -> c.connect("conn").connectActions(a));
|
||||||
|
return Stream.of(
|
||||||
|
topicEditActions, clusterConfigEditActions, aclEditActions,
|
||||||
|
cgEditActions, connEditActions, schemaEditActions
|
||||||
|
)
|
||||||
|
.flatMap(c -> c)
|
||||||
|
.map(setter -> setter.apply(AccessContext.builder().cluster("test").operationName("test")).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
void doesNothingIfNoResourceHasAlterAction(AccessContext readOnlyCxt) {
|
||||||
|
alterOnlyWriter.write(readOnlyCxt, user, null);
|
||||||
|
verifyNoInteractions(producerMock);
|
||||||
|
verifyNoInteractions(loggerMock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<AccessContext> doesNothingIfNoResourceHasAlterAction() {
|
||||||
|
return Stream.<UnaryOperator<AccessContextBuilder>>of(
|
||||||
|
c -> c.topic("test").topicActions(TopicAction.VIEW),
|
||||||
|
c -> c.clusterConfigActions(ClusterConfigAction.VIEW),
|
||||||
|
c -> c.aclActions(AclAction.VIEW),
|
||||||
|
c -> c.consumerGroup("cg").consumerGroupActions(ConsumerGroupAction.VIEW),
|
||||||
|
c -> c.schema("sc").schemaActions(SchemaAction.VIEW),
|
||||||
|
c -> c.connect("conn").connectActions(ConnectAction.VIEW)
|
||||||
|
).map(setter -> setter.apply(AccessContext.builder().cluster("test").operationName("test")).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4091,6 +4091,9 @@ components:
|
||||||
audit:
|
audit:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
level:
|
||||||
|
type: string
|
||||||
|
enum: [ "ALL", "ALTER_ONLY" ]
|
||||||
topic:
|
topic:
|
||||||
type: string
|
type: string
|
||||||
auditTopicsPartitions:
|
auditTopicsPartitions:
|
||||||
|
|
Loading…
Add table
Reference in a new issue