Browse Source

Add mongo sanitization configuration (#6541)

Trask Stalnaker 2 years ago
parent
commit
13cb1f6498
11 changed files with 116 additions and 21 deletions
  1. 10 1
      instrumentation/mongo/mongo-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoInstrumentationSingletons.java
  2. 14 7
      instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetter.java
  3. 4 2
      instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoInstrumenterFactory.java
  4. 6 2
      instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTelemetry.java
  5. 14 1
      instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTelemetryBuilder.java
  6. 33 0
      instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/StringBuilderWriter.java
  7. 4 4
      instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.groovy
  8. 1 1
      instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.groovy
  9. 10 1
      instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoInstrumentationSingletons.java
  10. 10 1
      instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoInstrumentationSingletons.java
  11. 10 1
      instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoInstrumentationSingletons.java

+ 10 - 1
instrumentation/mongo/mongo-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoInstrumentationSingletons.java

@@ -8,11 +8,20 @@ package io.opentelemetry.javaagent.instrumentation.mongo.v3_1;
 import com.mongodb.event.CommandListener;
 import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry;
+import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
+import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
 
 public final class MongoInstrumentationSingletons {
 
   public static final CommandListener LISTENER =
-      MongoTelemetry.create(GlobalOpenTelemetry.get()).newCommandListener();
+      MongoTelemetry.builder(GlobalOpenTelemetry.get())
+          .setStatementSanitizationEnabled(
+              InstrumentationConfig.get()
+                  .getBoolean(
+                      "otel.instrumentation.mongo.statement-sanitizer.enabled",
+                      CommonConfig.get().isStatementSanitizationEnabled()))
+          .build()
+          .newCommandListener();
 
   public static boolean isTracingListener(CommandListener listener) {
     return listener.getClass().getName().equals(LISTENER.getClass().getName());

+ 14 - 7
instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetter.java

@@ -10,7 +10,6 @@ import com.mongodb.connection.ConnectionDescription;
 import com.mongodb.event.CommandStartedEvent;
 import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
-import java.io.StringWriter;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
@@ -20,6 +19,8 @@ import javax.annotation.Nullable;
 import org.bson.BsonArray;
 import org.bson.BsonDocument;
 import org.bson.BsonValue;
+import org.bson.codecs.BsonDocumentCodec;
+import org.bson.codecs.EncoderContext;
 import org.bson.json.JsonWriter;
 import org.bson.json.JsonWriterSettings;
 
@@ -36,10 +37,12 @@ class MongoDbAttributesGetter implements DbClientAttributesGetter<CommandStarted
             .orElse(null);
   }
 
+  private final boolean statementSanitizationEnabled;
   private final int maxNormalizedQueryLength;
   @Nullable private final JsonWriterSettings jsonWriterSettings;
 
-  MongoDbAttributesGetter(int maxNormalizedQueryLength) {
+  MongoDbAttributesGetter(boolean statementSanitizationEnabled, int maxNormalizedQueryLength) {
+    this.statementSanitizationEnabled = statementSanitizationEnabled;
     this.maxNormalizedQueryLength = maxNormalizedQueryLength;
     this.jsonWriterSettings = createJsonWriterSettings(maxNormalizedQueryLength);
   }
@@ -90,20 +93,24 @@ class MongoDbAttributesGetter implements DbClientAttributesGetter<CommandStarted
     return event.getCommandName();
   }
 
-  // TODO(anuraaga): Migrate off of StringWriter to avoid synchronization.
-  // accessible to tests
   String sanitizeStatement(BsonDocument command) {
-    StringWriter stringWriter = new StringWriter(128);
+    StringBuilderWriter stringWriter = new StringBuilderWriter(128);
     // jsonWriterSettings is generally not null but could be due to security manager or unknown
     // API incompatibilities, which we can't detect by Muzzle because we use reflection.
     JsonWriter jsonWriter =
         jsonWriterSettings != null
             ? new JsonWriter(stringWriter, jsonWriterSettings)
             : new JsonWriter(stringWriter);
-    writeScrubbed(command, jsonWriter, /* isRoot= */ true);
+
+    if (statementSanitizationEnabled) {
+      writeScrubbed(command, jsonWriter, /* isRoot= */ true);
+    } else {
+      new BsonDocumentCodec().encode(jsonWriter, command, EncoderContext.builder().build());
+    }
+
     // If using MongoDB driver >= 3.7, the substring invocation will be a no-op due to use of
     // JsonWriterSettings.Builder.maxLength in the static initializer for JSON_WRITER_SETTINGS
-    StringBuffer buf = stringWriter.getBuffer();
+    StringBuilder buf = stringWriter.getBuilder();
     if (buf.length() <= maxNormalizedQueryLength) {
       return buf.toString();
     }

+ 4 - 2
instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoInstrumenterFactory.java

@@ -21,10 +21,12 @@ class MongoInstrumenterFactory {
       netAttributesExtractor = NetClientAttributesExtractor.create(new MongoNetAttributesGetter());
 
   static Instrumenter<CommandStartedEvent, Void> createInstrumenter(
-      OpenTelemetry openTelemetry, int maxNormalizedQueryLength) {
+      OpenTelemetry openTelemetry,
+      boolean statementSanitizationEnabled,
+      int maxNormalizedQueryLength) {
 
     MongoDbAttributesGetter dbAttributesGetter =
-        new MongoDbAttributesGetter(maxNormalizedQueryLength);
+        new MongoDbAttributesGetter(statementSanitizationEnabled, maxNormalizedQueryLength);
     SpanNameExtractor<CommandStartedEvent> spanNameExtractor =
         new MongoSpanNameExtractor(dbAttributesGetter, attributesExtractor);
 

+ 6 - 2
instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTelemetry.java

@@ -28,9 +28,13 @@ public final class MongoTelemetry {
 
   private final Instrumenter<CommandStartedEvent, Void> instrumenter;
 
-  MongoTelemetry(OpenTelemetry openTelemetry, int maxNormalizedQueryLength) {
+  MongoTelemetry(
+      OpenTelemetry openTelemetry,
+      boolean statementSanitizationEnabled,
+      int maxNormalizedQueryLength) {
     this.instrumenter =
-        MongoInstrumenterFactory.createInstrumenter(openTelemetry, maxNormalizedQueryLength);
+        MongoInstrumenterFactory.createInstrumenter(
+            openTelemetry, statementSanitizationEnabled, maxNormalizedQueryLength);
   }
 
   /**

+ 14 - 1
instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTelemetryBuilder.java

@@ -15,12 +15,24 @@ public final class MongoTelemetryBuilder {
 
   private final OpenTelemetry openTelemetry;
 
+  private boolean statementSanitizationEnabled = true;
   private int maxNormalizedQueryLength = DEFAULT_MAX_NORMALIZED_QUERY_LENGTH;
 
   MongoTelemetryBuilder(OpenTelemetry openTelemetry) {
     this.openTelemetry = openTelemetry;
   }
 
+  /**
+   * Sets whether the {@code db.statement} attribute on the spans emitted by the constructed {@link
+   * MongoTelemetry} should be sanitized. If set to {@code true}, all parameters that can
+   * potentially contain sensitive information will be masked. Enabled by default.
+   */
+  public MongoTelemetryBuilder setStatementSanitizationEnabled(
+      boolean statementSanitizationEnabled) {
+    this.statementSanitizationEnabled = statementSanitizationEnabled;
+    return this;
+  }
+
   /**
    * Sets the max length of recorded queries after normalization. Defaults to {@value
    * DEFAULT_MAX_NORMALIZED_QUERY_LENGTH}.
@@ -34,6 +46,7 @@ public final class MongoTelemetryBuilder {
    * Returns a new {@link MongoTelemetry} with the settings of this {@link MongoTelemetryBuilder}.
    */
   public MongoTelemetry build() {
-    return new MongoTelemetry(openTelemetry, maxNormalizedQueryLength);
+    return new MongoTelemetry(
+        openTelemetry, statementSanitizationEnabled, maxNormalizedQueryLength);
   }
 }

+ 33 - 0
instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/StringBuilderWriter.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.mongo.v3_1;
+
+import java.io.Writer;
+
+// because StringWriter uses the synchronized StringBuffer
+class StringBuilderWriter extends Writer {
+
+  private final StringBuilder sb;
+
+  StringBuilderWriter(int initialSize) {
+    sb = new StringBuilder(initialSize);
+  }
+
+  @Override
+  public void write(char[] cbuf, int off, int len) {
+    sb.append(cbuf, off, len);
+  }
+
+  @Override
+  public void flush() {}
+
+  @Override
+  public void close() {}
+
+  public StringBuilder getBuilder() {
+    return sb;
+  }
+}

+ 4 - 4
instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.groovy

@@ -18,7 +18,7 @@ class MongoDbAttributesGetterTest extends Specification {
 
   def 'should sanitize statements to json'() {
     setup:
-    def extractor = new MongoDbAttributesGetter(DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)
+    def extractor = new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)
 
     expect:
     sanitizeStatementAcrossVersions(extractor,
@@ -38,7 +38,7 @@ class MongoDbAttributesGetterTest extends Specification {
 
   def 'should only preserve string value if it is the value of the first top-level key'() {
     setup:
-    def extractor = new MongoDbAttributesGetter(DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)
+    def extractor = new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)
 
     expect:
     sanitizeStatementAcrossVersions(extractor,
@@ -50,7 +50,7 @@ class MongoDbAttributesGetterTest extends Specification {
 
   def 'should truncate simple command'() {
     setup:
-    def extractor = new MongoDbAttributesGetter(20)
+    def extractor = new MongoDbAttributesGetter(true, 20)
 
     def normalized = sanitizeStatementAcrossVersions(extractor,
       new BsonDocument("cmd", new BsonString("c"))
@@ -63,7 +63,7 @@ class MongoDbAttributesGetterTest extends Specification {
 
   def 'should truncate array'() {
     setup:
-    def extractor = new MongoDbAttributesGetter(27)
+    def extractor = new MongoDbAttributesGetter(true, 27)
 
     def normalized = sanitizeStatementAcrossVersions(extractor,
       new BsonDocument("cmd", new BsonString("c"))

+ 1 - 1
instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.groovy

@@ -16,7 +16,7 @@ class MongoSpanNameExtractorTest extends Specification {
 
   def 'test span name with no dbName'() {
     setup:
-    def nameExtractor = new MongoSpanNameExtractor(new MongoDbAttributesGetter(DEFAULT_MAX_NORMALIZED_QUERY_LENGTH), new MongoAttributesExtractor())
+    def nameExtractor = new MongoSpanNameExtractor(new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH), new MongoAttributesExtractor())
     def event = new CommandStartedEvent(
       0, null, null, command, new BsonDocument(command, new BsonInt32(1)))
 

+ 10 - 1
instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoInstrumentationSingletons.java

@@ -8,11 +8,20 @@ package io.opentelemetry.javaagent.instrumentation.mongo.v3_7;
 import com.mongodb.event.CommandListener;
 import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry;
+import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
+import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
 
 public final class MongoInstrumentationSingletons {
 
   public static final CommandListener LISTENER =
-      MongoTelemetry.create(GlobalOpenTelemetry.get()).newCommandListener();
+      MongoTelemetry.builder(GlobalOpenTelemetry.get())
+          .setStatementSanitizationEnabled(
+              InstrumentationConfig.get()
+                  .getBoolean(
+                      "otel.instrumentation.mongo.statement-sanitizer.enabled",
+                      CommonConfig.get().isStatementSanitizationEnabled()))
+          .build()
+          .newCommandListener();
 
   public static boolean isTracingListener(CommandListener listener) {
     return listener.getClass().getName().equals(LISTENER.getClass().getName());

+ 10 - 1
instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoInstrumentationSingletons.java

@@ -8,11 +8,20 @@ package io.opentelemetry.javaagent.instrumentation.mongo.v4_0;
 import com.mongodb.event.CommandListener;
 import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry;
+import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
+import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
 
 public final class MongoInstrumentationSingletons {
 
   public static final CommandListener LISTENER =
-      MongoTelemetry.create(GlobalOpenTelemetry.get()).newCommandListener();
+      MongoTelemetry.builder(GlobalOpenTelemetry.get())
+          .setStatementSanitizationEnabled(
+              InstrumentationConfig.get()
+                  .getBoolean(
+                      "otel.instrumentation.mongo.statement-sanitizer.enabled",
+                      CommonConfig.get().isStatementSanitizationEnabled()))
+          .build()
+          .newCommandListener();
 
   public static boolean isTracingListener(CommandListener listener) {
     return listener.getClass().getName().equals(LISTENER.getClass().getName());

+ 10 - 1
instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoInstrumentationSingletons.java

@@ -8,11 +8,20 @@ package io.opentelemetry.javaagent.instrumentation.mongoasync.v3_3;
 import com.mongodb.event.CommandListener;
 import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry;
+import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
+import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
 
 public final class MongoInstrumentationSingletons {
 
   public static final CommandListener LISTENER =
-      MongoTelemetry.create(GlobalOpenTelemetry.get()).newCommandListener();
+      MongoTelemetry.builder(GlobalOpenTelemetry.get())
+          .setStatementSanitizationEnabled(
+              InstrumentationConfig.get()
+                  .getBoolean(
+                      "otel.instrumentation.mongo.statement-sanitizer.enabled",
+                      CommonConfig.get().isStatementSanitizationEnabled()))
+          .build()
+          .newCommandListener();
 
   public static boolean isTracingListener(CommandListener listener) {
     return listener.getClass().getName().equals(LISTENER.getClass().getName());