Skip to content

Commit

Permalink
feat(topic-data): added new ability to mask the record key and values (
Browse files Browse the repository at this point in the history
…#1291)

this can be used to obscure sensitive data from all akhq users
  • Loading branch information
doxsch authored and tchiotludo committed Apr 4, 2023
1 parent a3d1bdb commit cc20b74
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 5 deletions.
11 changes: 10 additions & 1 deletion application.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,13 @@ akhq:
- username: header-admin
groups:
- admin


# Data masking configuration
data-masking:
filters:
- description: "Masks value for secret-key fields"
search-regex: '"(secret-key)":".*"'
replacement: '"$1":"xxxx"'
- description: "Masks last digits of phone numbers"
search-regex: '"([\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?)[0-9]{4,6}"'
replacement: '"$1xxxx"'
15 changes: 15 additions & 0 deletions docs/docs/configuration/akhq.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,18 @@ akhq:
value: "none"
```

## Data Masking
If you want to hide some data in your records, you can configure this with the following filters.
These will be applied to all record values and keys.
```yaml
akhq:
security:
data-masking:
filters:
- description: "Masks value for secret-key fields"
search-regex: '"(secret-key)":".*"'
replacement: '"$1":"xxxx"'
- description: "Masks last digits of phone numbers"
search-regex: '"([\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?)[0-9]{4,6}"'
replacement: '"$1xxxx"'
```
13 changes: 13 additions & 0 deletions src/main/java/org/akhq/configs/DataMasking.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.akhq.configs;

import io.micronaut.context.annotation.ConfigurationProperties;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@ConfigurationProperties("akhq.security.data-masking")
@Data
public class DataMasking {
List<DataMaskingFilter> filters = new ArrayList<>();
}
12 changes: 12 additions & 0 deletions src/main/java/org/akhq/configs/DataMaskingFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.akhq.configs;

import io.micronaut.context.annotation.EachProperty;
import lombok.Data;

@EachProperty("filters")
@Data
public class DataMaskingFilter {
String description;
String searchRegex;
String replacement;
}
4 changes: 4 additions & 0 deletions src/main/java/org/akhq/models/Record.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ public void setValue(String value) {
this.value = value;
}

public void setKey(String key) {
this.key = key;
}

public void setTruncated(Boolean truncated) {
this.truncated = truncated;
}
Expand Down
12 changes: 8 additions & 4 deletions src/main/java/org/akhq/repositories/RecordRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.akhq.modules.schemaregistry.RecordWithSchemaSerializerFactory;
import org.akhq.utils.AvroToJsonSerializer;
import org.akhq.utils.Debug;
import org.akhq.utils.MaskingUtils;
import org.apache.kafka.clients.admin.DeletedRecords;
import org.apache.kafka.clients.admin.RecordsToDelete;
import org.apache.kafka.clients.consumer.*;
Expand Down Expand Up @@ -72,6 +73,9 @@ public class RecordRepository extends AbstractRepository {
@Inject
private AvroWireFormatConverter avroWireFormatConverter;

@Inject
private MaskingUtils maskingUtils;

@Value("${akhq.topic-data.poll-timeout:1000}")
protected int pollTimeout;

Expand Down Expand Up @@ -443,7 +447,7 @@ private ConsumerRecords<byte[], byte[]> poll(KafkaConsumer<byte[], byte[]> consu
private Record newRecord(ConsumerRecord<byte[], byte[]> record, String clusterId, Topic topic) {
SchemaRegistryType schemaRegistryType = this.schemaRegistryRepository.getSchemaRegistryType(clusterId);
SchemaRegistryClient client = this.kafkaModule.getRegistryClient(clusterId);
return new Record(
return maskingUtils.maskRecord(new Record(
client,
record,
this.schemaRegistryRepository.getSchemaRegistryType(clusterId),
Expand All @@ -456,13 +460,13 @@ private Record newRecord(ConsumerRecord<byte[], byte[]> record, String clusterId
avroWireFormatConverter.convertValueToWireFormat(record, client,
this.schemaRegistryRepository.getSchemaRegistryType(clusterId)),
topic
);
));
}

private Record newRecord(ConsumerRecord<byte[], byte[]> record, BaseOptions options, Topic topic) {
SchemaRegistryType schemaRegistryType = this.schemaRegistryRepository.getSchemaRegistryType(options.clusterId);
SchemaRegistryClient client = this.kafkaModule.getRegistryClient(options.clusterId);
return new Record(
return maskingUtils.maskRecord(new Record(
client,
record,
schemaRegistryType,
Expand All @@ -475,7 +479,7 @@ private Record newRecord(ConsumerRecord<byte[], byte[]> record, BaseOptions opti
avroWireFormatConverter.convertValueToWireFormat(record, client,
this.schemaRegistryRepository.getSchemaRegistryType(options.clusterId)),
topic
);
));
}

public List<RecordMetadata> produce(
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/org/akhq/utils/MaskingUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.akhq.utils;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.akhq.configs.DataMasking;
import org.akhq.configs.DataMaskingFilter;
import org.akhq.models.Record;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class MaskingUtils {
private static final Logger LOG = LoggerFactory.getLogger(MaskingUtils.class);

@Inject
DataMasking dataMasking;

public Record maskRecord(Record record) {
LOG.trace("masking record");

String value = record.getValue();
String key = record.getKey();

for (DataMaskingFilter filter : dataMasking.getFilters()) {
if (value != null) {
value = value.replaceAll(filter.getSearchRegex(), filter.getReplacement());
}
if (key != null) {
key = key.replaceAll(filter.getSearchRegex(), filter.getReplacement());
}
}

record.setValue(value);
record.setKey(key);

return record;
}
}
64 changes: 64 additions & 0 deletions src/test/java/org/akhq/utils/MaskingUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.akhq.utils;

import io.micronaut.context.ApplicationContext;
import org.akhq.models.Record;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class MaskingUtilsTest {

@Test
void shouldMasksRecordValue() {
ApplicationContext ctx = ApplicationContext.run("data-masking");
MaskingUtils maskingUtils = ctx.getBean(MaskingUtils.class);

Record record = new Record();
record.setValue("{\"secret-key\":\"my-secret-value\"}");

Record maskedRecord = maskingUtils.maskRecord(record);

assertEquals(
"{\"secret-key\":\"xxxx\"}",
maskedRecord.getValue()
);

ctx.close();
}

@Test
void shouldMasksRecordKey() {
ApplicationContext ctx = ApplicationContext.run("data-masking");
MaskingUtils maskingUtils = ctx.getBean(MaskingUtils.class);

Record record = new Record();
record.setKey("{\"secret-key\":\"my-secret-value\"}");

Record maskedRecord = maskingUtils.maskRecord(record);

assertEquals(
"{\"secret-key\":\"xxxx\"}",
maskedRecord.getKey()
);

ctx.close();
}

@Test
void shouldReturnGroupsToAllowPartialMasking() {
ApplicationContext ctx = ApplicationContext.run("data-masking");
MaskingUtils maskingUtils = ctx.getBean(MaskingUtils.class);

Record record = new Record();
record.setValue("{\"some-key\":\"+12092503766\"}");

Record maskedRecord = maskingUtils.maskRecord(record);

assertEquals(
"{\"some-key\":\"+120925xxxx\"}",
maskedRecord.getValue()
);

ctx.close();
}
}
10 changes: 10 additions & 0 deletions src/test/resources/application-data-masking.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
akhq:
security:
data-masking:
filters:
- description: "Masks value for secret-key fields"
search-regex: '"(secret-key)":".*"'
replacement: '"$1":"xxxx"'
- description: "Masks last digits of phone numbers"
search-regex: '"([\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?)[0-9]{4,6}"'
replacement: '"$1xxxx"'

0 comments on commit cc20b74

Please sign in to comment.