You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ap...@apache.org on 2021/09/07 23:50:06 UTC

[pinot] 01/01: basic obfuscator config

This is an automated email from the ASF dual-hosted git repository.

apucher pushed a commit to branch config-obfuscator
in repository https://gitbox.apache.org/repos/asf/pinot.git

commit fc7af0c8ea94d0aee364e85815b597c6be3dc01e
Author: Alexander Pucher <ap...@apache.org>
AuthorDate: Tue Sep 7 16:49:43 2021 -0700

    basic obfuscator config
---
 .../apache/pinot/spi/env/PinotConfiguration.java   |  6 ++
 .../org/apache/pinot/spi/utils/Obfuscator.java     | 96 ++++++++++++++++++++++
 .../apache/pinot/spi/config/ConfigUtilsTest.java   | 17 ++++
 .../org/apache/pinot/spi/utils/ObfuscatorTest.java | 83 +++++++++++++++++++
 4 files changed, 202 insertions(+)

diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/env/PinotConfiguration.java b/pinot-spi/src/main/java/org/apache/pinot/spi/env/PinotConfiguration.java
index ce4dd8f..e50b13f 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/env/PinotConfiguration.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/env/PinotConfiguration.java
@@ -32,6 +32,7 @@ import org.apache.commons.configuration.ConfigurationException;
 import org.apache.commons.configuration.MapConfiguration;
 import org.apache.commons.configuration.PropertiesConfiguration;
 import org.apache.pinot.spi.ingestion.batch.spec.PinotFSSpec;
+import org.apache.pinot.spi.utils.Obfuscator;
 
 
 /**
@@ -441,4 +442,9 @@ public class PinotConfiguration {
   public Map<String, Object> toMap() {
     return CommonsConfigurationUtils.toMap(_configuration);
   }
+
+  @Override
+  public String toString() {
+    return String.valueOf(new Obfuscator().obfuscateJson(this));
+  }
 }
diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/Obfuscator.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/Obfuscator.java
new file mode 100644
index 0000000..7e0fd21
--- /dev/null
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/Obfuscator.java
@@ -0,0 +1,96 @@
+package org.apache.pinot.spi.utils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+
+/**
+ * Simple obfuscator for object trees and configuration containers with key-value pairs. Matches a configurable set of
+ * patterns and replaces sensitive values with a pre-defined masked value for output.
+ *
+ * Example input:
+ * <pre>
+ *   {
+ *     "type": "sample object",
+ *     "nestedCredentials": {
+ *       "user": "admin",
+ *       "password": "verysecret"
+ *     }
+ *   }
+ * </pre>
+ *
+ * Example output
+ * <pre>
+ *   {
+ *     "type": "sample object",
+ *     "nestedCredentials": {
+ *       "user": "admin",
+ *       "password": "*****"
+ *     }
+ *   }
+ * </pre>
+ */
+public final class Obfuscator {
+  private static final String DEFAULT_MASKED_VALUE = "*****";
+  private static final List<Pattern> DEFAULT_PATTERNS =
+      Stream.of("(?i).*secret$", "(?i).*password$", "(?i).*token$").map(Pattern::compile).collect(Collectors.toList());
+
+  private final String _maskedValue;
+  private final List<Pattern> _patterns;
+
+  /**
+   * Obfuscator with default behavior matching (ignore case) "secret", "password", and "token" suffixes. Masks any
+   * values with '*****'
+   */
+  public Obfuscator() {
+    _maskedValue = DEFAULT_MASKED_VALUE;
+    _patterns = DEFAULT_PATTERNS;
+  }
+
+  /**
+   * Obfuscator with customized masking behavior. Defaults do not apply! Please ensure case-insensitive regex matching.
+   *
+   * @param maskedValue replacement value
+   * @param patterns key patterns to obfuscate
+   */
+  public Obfuscator(String maskedValue, List<Pattern> patterns) {
+    _maskedValue = maskedValue;
+    _patterns = patterns;
+  }
+
+  /**
+   * Serialize an object tree as JSON and obfuscate matching keys.
+   *
+   * @param object input value
+   * @return obfuscated JSON tree
+   */
+  public JsonNode obfuscateJson(Object object) {
+    // NOTE: jayway json path 2.4.0 seems to have issues with '@.name' so we'll do this manually
+    // as determined by a cursory and purely subjective investigation by alex
+    // "$..[?(@.name =~ /password$/i || @.name =~ /secret$/i || @.name =~ /token$/i)]"
+
+    return obfuscateJsonRec(JsonUtils.objectToJsonNode(object));
+  }
+
+  private JsonNode obfuscateJsonRec(JsonNode node) {
+    if (node.isObject()) {
+      node.fieldNames().forEachRemaining(field -> {
+        if (_patterns.stream().anyMatch(pattern -> pattern.matcher(field).matches())) {
+          ((ObjectNode) node).put(field, _maskedValue);
+        } else if (node.isArray()) {
+          IntStream.range(0, node.size()).forEach(i -> ((ArrayNode) node).set(i, obfuscateJsonRec(node.get(i))));
+        } else if (node.isObject()) {
+          ((ObjectNode) node).put(field, obfuscateJsonRec(node.get(field)));
+        }
+      });
+    }
+
+    return node;
+  }
+}
diff --git a/pinot-spi/src/test/java/org/apache/pinot/spi/config/ConfigUtilsTest.java b/pinot-spi/src/test/java/org/apache/pinot/spi/config/ConfigUtilsTest.java
index 5127357..61d9021 100644
--- a/pinot-spi/src/test/java/org/apache/pinot/spi/config/ConfigUtilsTest.java
+++ b/pinot-spi/src/test/java/org/apache/pinot/spi/config/ConfigUtilsTest.java
@@ -26,6 +26,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.apache.pinot.spi.config.table.IndexingConfig;
+import org.apache.pinot.spi.env.PinotConfiguration;
 import org.apache.pinot.spi.stream.OffsetCriteria;
 import org.apache.pinot.spi.stream.StreamConfig;
 import org.apache.pinot.spi.stream.StreamConfigProperties;
@@ -153,4 +154,20 @@ public class ConfigUtilsTest {
       }
     }
   }
+
+  @Test
+  public void testDefaultObfuscation() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("username", "admin");
+    map.put("password", "verysecret");
+    map.put("my.authToken", "secrettoken");
+
+    Map<String, Object> nestedMap = new HashMap<>();
+    map.put("credentials", map);
+
+    PinotConfiguration config = new PinotConfiguration(nestedMap);
+
+    Assert.assertFalse(config.toString().contains("verysecret"));
+    Assert.assertFalse(config.toString().contains("secrettoken"));
+  }
 }
diff --git a/pinot-spi/src/test/java/org/apache/pinot/spi/utils/ObfuscatorTest.java b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/ObfuscatorTest.java
new file mode 100644
index 0000000..204dd0b
--- /dev/null
+++ b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/ObfuscatorTest.java
@@ -0,0 +1,83 @@
+package org.apache.pinot.spi.utils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.tuple.Pair;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+
+public class ObfuscatorTest {
+  private static final String VALUE = "VALUE";
+  private static final String SECRET = "SECRET";
+
+  private Obfuscator _obfuscator;
+
+  private Map<String, Object> _map;
+  private Map<String, Object> _nestedMap;
+
+  @BeforeMethod
+  public void setup() {
+    _obfuscator = new Obfuscator();
+
+    _map = new HashMap<>();
+    _map.put("value", "VALUE");
+    _map.put("secret", "SECRET");
+    _map.put("a.secret", "SECRET");
+    _map.put("mysecret", "SECRET");
+    _map.put("password", "SECRET");
+    _map.put("a.password", "SECRET");
+    _map.put("mypassword", "SECRET");
+    _map.put("token", "SECRET");
+    _map.put("a.token", "SECRET");
+    _map.put("mytoken", "SECRET");
+
+    _nestedMap = new HashMap<>();
+    _nestedMap.put("value", "VALUE");
+    _nestedMap.put("map", _map);
+  }
+
+  @Test
+  public void testSimple() {
+    String output = String.valueOf(_obfuscator.obfuscateJson(_map));
+    Assert.assertTrue(output.contains(VALUE));
+    Assert.assertFalse(output.contains(SECRET));
+  }
+
+  @Test
+  public void testNested() {
+    String output = String.valueOf(_obfuscator.obfuscateJson(_nestedMap));
+    Assert.assertTrue(output.contains(VALUE));
+    Assert.assertFalse(output.contains(SECRET));
+  }
+
+  @Test
+  public void testComplexObject() {
+    Object complex = Pair.of("nested", Pair.of("moreNested", Pair.of("mostNestedSecret", SECRET)));
+    String output = String.valueOf(_obfuscator.obfuscateJson(complex));
+    Assert.assertFalse(output.contains(SECRET));
+  }
+
+  @Test
+  public void testNull() {
+    Assert.assertEquals(String.valueOf(_obfuscator.obfuscateJson(null)), "null");
+  }
+
+  @Test
+  public void testNoop() {
+    Object output = new Obfuscator("nope", Collections.emptyList()).obfuscateJson(_map);
+    Assert.assertEquals(output, JsonUtils.objectToJsonNode(_map));
+  }
+
+  @Test
+  public void testCustomPattern() {
+    Obfuscator obfuscator = new Obfuscator("verycustomized", Collections.singletonList(Pattern.compile("^value$")));
+    String output = String.valueOf(obfuscator.obfuscateJson(_nestedMap));
+    Assert.assertFalse(output.contains(VALUE));
+    Assert.assertTrue(output.contains("verycustomized"));
+    Assert.assertTrue(output.contains(SECRET));
+  }
+}

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org