Skip to content

Commit

Permalink
Add mongo sanitization configuration (open-telemetry#6541)
Browse files Browse the repository at this point in the history
  • Loading branch information
trask authored and LironKS committed Dec 4, 2022
1 parent b3f24c2 commit b1c7ca0
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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;
Expand All @@ -20,6 +19,8 @@
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;

Expand All @@ -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);
}
Expand Down Expand Up @@ -90,20 +93,24 @@ public String operation(CommandStartedEvent event) {
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ public static MongoTelemetryBuilder builder(OpenTelemetry openTelemetry) {

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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -34,6 +46,7 @@ public MongoTelemetryBuilder setMaxNormalizedQueryLength(int maxNormalizedQueryL
* 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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"))
Expand All @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
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());
Expand Down

0 comments on commit b1c7ca0

Please sign in to comment.