-
Notifications
You must be signed in to change notification settings - Fork 153
Dlp #1909
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
Dlp #1909
Changes from 12 commits
bcc7dcb
497a43c
f0119d3
5f84eb6
e2536fe
6ebb9c4
9d2e722
699b226
7ffee70
559a62b
de3ed95
4c3ff8f
8cfb161
b17b14a
099ec70
02d75ec
2be464b
450204b
75b56d9
8adedb9
e7cc895
0f673bc
6a0bed1
469256e
53bc2f8
7d04071
ff15443
ea1e179
dce7806
e2eaa48
13711b1
7aa7e95
60a13df
0bf1268
21c3489
20ed318
c20c70d
528cc14
8d75f7c
c3ec071
e9a6286
8b1a278
77022dd
5008ee7
e58e11d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.BufferedReader; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.io.InputStreamReader; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| public class CsvFieldConfiguration implements FieldConfiguration { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(CsvFieldConfiguration.class); | ||
|
|
||
| @Override | ||
| public Map<String, String> getFields(String fileName) { | ||
| try (InputStream inputStream = CsvFieldConfiguration.class.getClassLoader().getResourceAsStream(fileName)) { | ||
| Map<String, String> riskDict = new HashMap<>(); | ||
| if (inputStream == null) { | ||
| log.error("Could not find file: {}", fileName); | ||
| throw new NullPointerException("InputStream is null. File not found: " + fileName); | ||
| } | ||
|
|
||
| BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); | ||
| String line; | ||
| boolean isHeader = true; | ||
|
|
||
| while ((line = reader.readLine()) != null) { | ||
| if (isHeader) { | ||
| isHeader = false; | ||
| continue; | ||
| } | ||
|
|
||
| String[] parts = line.split(",", -1); | ||
| if (parts.length >= 3) { | ||
| String field = parts[0].trim().toLowerCase(); | ||
| String riskLevel = parts[2].trim(); | ||
| riskDict.put(field, riskLevel); | ||
| } else { | ||
| log.warn("Invalid CSV line: {}", line); | ||
| } | ||
| } | ||
| return riskDict; | ||
| } catch (IOException e) { | ||
| throw new RuntimeException("Failed to load risk data from " + fileName, e); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonFactory; | ||
| import com.fasterxml.jackson.core.JsonParser; | ||
| import com.fasterxml.jackson.core.JsonToken; | ||
| import com.predic8.membrane.core.http.Message; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.*; | ||
|
|
||
| public class DLP { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(DLP.class); | ||
| private static final JsonFactory JSON_FACTORY = new JsonFactory(); | ||
|
|
||
| private final Map<String, String> riskDict; | ||
|
|
||
| public DLP(Map<String, String> riskDict) { | ||
| this.riskDict = riskDict; | ||
| } | ||
|
|
||
| public RiskReport analyze(Message msg) { | ||
| try (JsonParser parser = createParser(msg)) { | ||
| Deque<String> contextStack = new ArrayDeque<>(); | ||
| RiskReport report = new RiskReport(); | ||
|
|
||
| String currentField = null; | ||
|
|
||
| while (parser.nextToken() != null) { | ||
| JsonToken token = parser.currentToken(); | ||
|
|
||
| switch (token) { | ||
| case FIELD_NAME -> currentField = parser.currentName(); | ||
|
|
||
| case START_OBJECT, START_ARRAY -> { | ||
| if (currentField != null) { | ||
| contextStack.addLast(currentField); | ||
| currentField = null; | ||
| } | ||
| } | ||
|
|
||
| case END_OBJECT, END_ARRAY -> { | ||
| if (!contextStack.isEmpty()) contextStack.removeLast(); | ||
| } | ||
|
|
||
| default -> { | ||
| if (currentField != null) { | ||
| String fullPath = buildFullPath(contextStack, currentField).toLowerCase(); | ||
| report.recordField(fullPath, Optional.ofNullable(riskDict.get(fullPath)) | ||
| .orElse(riskDict.getOrDefault(currentField.toLowerCase(), "unclassified")) | ||
| .toLowerCase()); | ||
| currentField = null; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return report; | ||
| } catch (IOException e) { | ||
| log.error("Parse Error: {}", e.getMessage()); | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| private JsonParser createParser(Message msg) throws IOException { | ||
| return JSON_FACTORY.createParser(new InputStreamReader(msg.getBodyAsStreamDecoded(), Optional.ofNullable(msg.getCharset()).orElse(StandardCharsets.UTF_8.name()))); | ||
| } | ||
|
|
||
| private String buildFullPath(Deque<String> stack, String field) { | ||
| List<String> path = new ArrayList<>(stack); | ||
| path.add(field); | ||
| return String.join(".", path); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.predic8.membrane.annot.MCAttribute; | ||
| import com.predic8.membrane.annot.MCChildElement; | ||
| import com.predic8.membrane.annot.MCElement; | ||
| import com.predic8.membrane.core.exchange.Exchange; | ||
| import com.predic8.membrane.core.http.Message; | ||
| import com.predic8.membrane.core.interceptor.AbstractInterceptor; | ||
| import com.predic8.membrane.core.interceptor.Outcome; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; | ||
|
|
||
| @MCElement(name = "dlp") | ||
| public class DLPInterceptor extends AbstractInterceptor { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(DLPInterceptor.class); | ||
| private DLP dlp; | ||
| private String fieldsConfig; | ||
| private String action = "report"; | ||
| private Fields fields; | ||
|
|
||
| @Override | ||
| public void init() { | ||
| super.init(); | ||
| dlp = new DLP(new CsvFieldConfiguration().getFields(fieldsConfig)); | ||
| } | ||
|
|
||
| @Override | ||
| public Outcome handleRequest(Exchange exc) { | ||
| return handleInternal(exc.getRequest()); | ||
| } | ||
|
|
||
| @Override | ||
| public Outcome handleResponse(Exchange exc) { | ||
| return handleInternal(exc.getResponse()); | ||
| } | ||
|
|
||
| public Outcome handleInternal(Message msg) { | ||
| if (dlp == null) { | ||
| log.warn("DLP not initialized."); | ||
| return CONTINUE; | ||
| } | ||
|
|
||
| RiskReport report = dlp.analyze(msg); | ||
| log.info("DLP Risk Analysis: {}", report.getLogReport()); | ||
|
|
||
| if (fields != null && !fields.getFields().isEmpty()) { | ||
| for (Field f : fields.getFields()) { | ||
| f.handleAction(msg); | ||
| } | ||
| } else { | ||
| report.getMatchedFields().keySet().forEach(name -> { | ||
| Field f = new Field(); | ||
| f.setName(name); | ||
| f.setAction(action); | ||
| f.handleAction(msg); | ||
| }); | ||
| } | ||
| return CONTINUE; | ||
| } | ||
|
|
||
| public String getFieldsConfig() { | ||
| return fieldsConfig; | ||
| } | ||
|
|
||
| @MCAttribute | ||
| public void setFieldsConfig(String fieldsConfig) { | ||
| this.fieldsConfig = fieldsConfig; | ||
| } | ||
|
|
||
| public String getAction() { | ||
| return action; | ||
| } | ||
|
|
||
| @MCAttribute | ||
| public void setAction(String action) { | ||
| this.action = action; | ||
| } | ||
|
||
|
|
||
| public Fields getFields() { | ||
| return fields; | ||
| } | ||
|
|
||
| @MCChildElement | ||
| public void setFields(Fields fields) { | ||
| this.fields = fields; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.predic8.membrane.annot.MCAttribute; | ||
| import com.predic8.membrane.annot.MCElement; | ||
| import com.predic8.membrane.core.http.Message; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| @MCElement(name = "field") | ||
| public class Field { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(Field.class); | ||
|
|
||
| private String name; | ||
| private String action; | ||
|
|
||
| @MCAttribute | ||
| public void setName(String name) { | ||
| this.name = name; | ||
| } | ||
|
|
||
| @MCAttribute | ||
| public void setAction(String action) { | ||
| this.action = action.toLowerCase(); | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public String getAction() { | ||
| return action; | ||
| } | ||
|
|
||
| public void handleAction(Message msg) { | ||
| String json = msg.getBodyAsStringDecoded(); | ||
| String modified = json; | ||
|
|
||
| switch (action) { | ||
| case "filter" -> modified = filter(json); | ||
| case "mask" -> modified = mask(json); | ||
| case "report" -> modified = ""; | ||
| default -> log.warn("Unknown DLP action: {}", action); | ||
| } | ||
|
|
||
| msg.setBodyContent(modified.getBytes()); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| private String filter(String json) { | ||
| return json.replaceAll("\"(" + name + ")\"\\s*:\\s*\".*?\"\\s*,?", ""); | ||
| } | ||
|
|
||
| private String mask(String json) { | ||
| return json.replaceAll("\"(" + name + ")\"\\s*:\\s*(\".*?\"|-?\\d+(\\.\\d+)?|true|false|null)", "\"$1\":\"****\""); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| public interface FieldConfiguration { | ||
| Map<String, String> getFields(String fileName); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.predic8.membrane.annot.MCChildElement; | ||
| import com.predic8.membrane.annot.MCElement; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @MCElement(name = "fields") | ||
| public class Fields { | ||
|
|
||
| private List<Field> fields = new ArrayList<>(); | ||
|
|
||
| @MCChildElement | ||
| public Fields setFields(List<Field> fields) { | ||
| this.fields = fields; | ||
| return this; | ||
| } | ||
|
|
||
| public List<Field> getFields() { | ||
| return fields; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class RiskReport { | ||
|
|
||
| private static final List<String> LEVELS = List.of("high", "medium", "low", "unclassified"); | ||
|
|
||
| private final Map<String, String> matchedFields = new LinkedHashMap<>(); | ||
| private final Map<String, Integer> riskCounts = new HashMap<>(); | ||
| private final Map<String, Map<String, Integer>> riskDetails = new HashMap<>(); | ||
|
|
||
| public void recordField(String field, String riskLevel) { | ||
| matchedFields.put(field, riskLevel); | ||
| riskCounts.merge(riskLevel, 1, Integer::sum); | ||
| riskDetails.computeIfAbsent(riskLevel, r -> new LinkedHashMap<>()).merge(field, 1, Integer::sum); | ||
| } | ||
|
|
||
| public Map<String, Object> getLogReport() { | ||
| Map<String, Object> out = new LinkedHashMap<String, Object>(); | ||
| LEVELS.forEach(level -> { | ||
| out.put(level + "_risk", riskCounts.getOrDefault(level, 0)); | ||
| riskDetails.computeIfPresent(level, (k, v) -> { | ||
| out.put(level + "_details", v); | ||
| return v; | ||
| }); | ||
| }); | ||
| return out; | ||
| } | ||
|
|
||
| Map<String, String> getMatchedFields() { | ||
| return matchedFields; | ||
| } | ||
|
|
||
| Map<String, Integer> getRiskCounts() { | ||
| return riskCounts; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.