Skip to content

Commit ce40cbf

Browse files
authored
Merge pull request #152 from DataDog/ark/mongo_quickfix
Fix Mongo Query Sanitizing
2 parents 96c39d1 + b85b9b0 commit ce40cbf

File tree

7 files changed

+161
-83
lines changed

7 files changed

+161
-83
lines changed

dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/MongoClientInstrumentationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public void test() {
1919
.get(0)
2020
.getClass()
2121
.getSimpleName())
22-
.isEqualTo("TracingCommandListener");
22+
.isEqualTo("DDTracingCommandListener");
2323

2424
mongoClient.close();
2525
}

dd-java-agent/integrations/helpers/helpers.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ dependencies {
1414
compileOnly group: 'org.jboss.byteman', name: 'byteman', version: '4.0.0-BETA5'
1515

1616
compile group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9'
17-
compile group: 'io.opentracing.contrib', name: 'opentracing-mongo-driver', version: '0.0.3'
1817
compile group: 'io.opentracing.contrib', name: 'opentracing-okhttp3', version: '0.0.5'
1918
compile group: 'io.opentracing.contrib', name: 'opentracing-jms-common', version: '0.0.3'
2019
compile group: 'io.opentracing.contrib', name: 'opentracing-jms-2', version: '0.0.3'

dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/MongoHelper.java

Lines changed: 122 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22

33
import com.datadoghq.trace.DDTags;
44
import com.mongodb.MongoClientOptions;
5+
import com.mongodb.event.CommandFailedEvent;
6+
import com.mongodb.event.CommandListener;
57
import com.mongodb.event.CommandStartedEvent;
8+
import com.mongodb.event.CommandSucceededEvent;
69
import io.opentracing.Span;
7-
import io.opentracing.contrib.mongo.TracingCommandListener;
8-
import io.opentracing.contrib.mongo.TracingCommandListenerFactory;
10+
import io.opentracing.Tracer;
911
import io.opentracing.tag.Tags;
12+
import java.net.Inet4Address;
13+
import java.net.InetAddress;
14+
import java.nio.ByteBuffer;
1015
import java.util.Arrays;
16+
import java.util.Collections;
1117
import java.util.List;
1218
import java.util.Map;
19+
import java.util.concurrent.ConcurrentHashMap;
1320
import lombok.extern.slf4j.Slf4j;
1421
import org.bson.BsonArray;
1522
import org.bson.BsonDocument;
@@ -21,10 +28,6 @@
2128
@Slf4j
2229
public class MongoHelper extends DDAgentTracingHelper<MongoClientOptions.Builder> {
2330

24-
private static final List<String> WHILDCARD_FIELDS =
25-
Arrays.asList("ordered", "insert", "count", "find");
26-
private static final BsonValue HIDDEN_CAR = new BsonString("?");
27-
2831
public MongoHelper(final Rule rule) {
2932
super(rule);
3033
}
@@ -42,62 +45,137 @@ public MongoHelper(final Rule rule) {
4245
protected MongoClientOptions.Builder doPatch(final MongoClientOptions.Builder builder)
4346
throws Exception {
4447

45-
final TracingCommandListener listener = TracingCommandListenerFactory.create(tracer);
48+
final DDTracingCommandListener listener = new DDTracingCommandListener(tracer);
4649
builder.addCommandListener(listener);
4750

4851
setState(builder, 1);
4952

5053
return builder;
5154
}
5255

53-
public void decorate(final Span span, final CommandStartedEvent event) {
54-
try {
55-
// normalize the Mongo command so that parameters are removed from the string
56-
final BsonDocument normalized = norm(event.getCommand());
57-
final String mongoCmd = normalized.toString();
56+
public static class DDTracingCommandListener implements CommandListener {
57+
/**
58+
* The values of these mongo fields will not be scrubbed out. This allows the non-sensitive
59+
* collection names to be captured.
60+
*/
61+
private static final List<String> UNSCRUBBED_FIELDS =
62+
Arrays.asList("ordered", "insert", "count", "find", "create");
63+
64+
private static final BsonValue HIDDEN_CHAR = new BsonString("?");
65+
private static final String MONGO_OPERATION = "mongo.query";
66+
67+
static final String COMPONENT_NAME = "java-mongo";
68+
private final Tracer tracer;
69+
/** Cache for (request id, span) pairs */
70+
private final Map<Integer, Span> cache = new ConcurrentHashMap<>();
71+
72+
public DDTracingCommandListener(Tracer tracer) {
73+
this.tracer = tracer;
74+
}
5875

59-
// add specific resource name and replace the `db.statement` OpenTracing
60-
// tag with the quantized version of the Mongo command
76+
@Override
77+
public void commandStarted(CommandStartedEvent event) {
78+
Span span = buildSpan(event);
79+
cache.put(event.getRequestId(), span);
80+
}
81+
82+
@Override
83+
public void commandSucceeded(CommandSucceededEvent event) {
84+
Span span = cache.remove(event.getRequestId());
85+
if (span != null) {
86+
span.finish();
87+
}
88+
}
89+
90+
@Override
91+
public void commandFailed(CommandFailedEvent event) {
92+
Span span = cache.remove(event.getRequestId());
93+
if (span != null) {
94+
onError(span, event.getThrowable());
95+
span.finish();
96+
}
97+
}
98+
99+
private Span buildSpan(CommandStartedEvent event) {
100+
Tracer.SpanBuilder spanBuilder =
101+
tracer.buildSpan(MONGO_OPERATION).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT);
102+
103+
Span span = spanBuilder.startManual();
104+
try {
105+
decorate(span, event);
106+
} catch (final Throwable e) {
107+
log.warn("Couldn't decorate the mongo query: " + e.getMessage(), e);
108+
}
109+
110+
return span;
111+
}
112+
113+
private static void onError(Span span, Throwable throwable) {
114+
Tags.ERROR.set(span, Boolean.TRUE);
115+
span.log(Collections.singletonMap("error.object", throwable));
116+
}
117+
118+
public static void decorate(Span span, CommandStartedEvent event) {
119+
// scrub the Mongo command so that parameters are removed from the string
120+
final BsonDocument scrubbed = scrub(event.getCommand());
121+
final String mongoCmd = scrubbed.toString();
122+
123+
Tags.COMPONENT.set(span, COMPONENT_NAME);
124+
Tags.DB_STATEMENT.set(span, mongoCmd);
125+
Tags.DB_INSTANCE.set(span, event.getDatabaseName());
126+
// add specific resource name
61127
span.setTag(DDTags.RESOURCE_NAME, mongoCmd);
62-
span.setTag(Tags.DB_STATEMENT.getKey(), mongoCmd);
63128
span.setTag(DDTags.SPAN_TYPE, "mongodb");
64-
} catch (final Throwable e) {
65-
log.warn("Couldn't decorate the mongo query: " + e.getMessage(), e);
66-
}
67-
}
129+
span.setTag(DDTags.SERVICE_NAME, "mongo");
130+
131+
Tags.PEER_HOSTNAME.set(span, event.getConnectionDescription().getServerAddress().getHost());
132+
133+
InetAddress inetAddress =
134+
event.getConnectionDescription().getServerAddress().getSocketAddress().getAddress();
68135

69-
private BsonDocument norm(final BsonDocument origin) {
70-
final BsonDocument normalized = new BsonDocument();
71-
for (final Map.Entry<String, BsonValue> entry : origin.entrySet()) {
72-
if (WHILDCARD_FIELDS.contains(entry.getKey())) {
73-
normalized.put(entry.getKey(), entry.getValue());
136+
if (inetAddress instanceof Inet4Address) {
137+
byte[] address = inetAddress.getAddress();
138+
Tags.PEER_HOST_IPV4.set(span, ByteBuffer.wrap(address).getInt());
74139
} else {
75-
final BsonValue child = norm(entry.getValue());
76-
normalized.put(entry.getKey(), child);
140+
Tags.PEER_HOST_IPV6.set(span, inetAddress.getHostAddress());
77141
}
142+
143+
Tags.PEER_PORT.set(span, event.getConnectionDescription().getServerAddress().getPort());
144+
Tags.DB_TYPE.set(span, "mongo");
78145
}
79-
return normalized;
80-
}
81146

82-
private BsonValue norm(final BsonArray origin) {
83-
final BsonArray normalized = new BsonArray();
84-
for (final BsonValue value : origin) {
85-
final BsonValue child = norm(value);
86-
normalized.add(child);
147+
private static BsonDocument scrub(final BsonDocument origin) {
148+
final BsonDocument scrub = new BsonDocument();
149+
for (final Map.Entry<String, BsonValue> entry : origin.entrySet()) {
150+
if (UNSCRUBBED_FIELDS.contains(entry.getKey()) && entry.getValue().isString()) {
151+
scrub.put(entry.getKey(), entry.getValue());
152+
} else {
153+
final BsonValue child = scrub(entry.getValue());
154+
scrub.put(entry.getKey(), child);
155+
}
156+
}
157+
return scrub;
87158
}
88-
return normalized;
89-
}
90159

91-
private BsonValue norm(final BsonValue origin) {
160+
private static BsonValue scrub(final BsonArray origin) {
161+
final BsonArray scrub = new BsonArray();
162+
for (final BsonValue value : origin) {
163+
final BsonValue child = scrub(value);
164+
scrub.add(child);
165+
}
166+
return scrub;
167+
}
92168

93-
final BsonValue normalized;
94-
if (origin.isDocument()) {
95-
normalized = norm(origin.asDocument());
96-
} else if (origin.isArray()) {
97-
normalized = norm(origin.asArray());
98-
} else {
99-
normalized = HIDDEN_CAR;
169+
private static BsonValue scrub(final BsonValue origin) {
170+
final BsonValue scrubbed;
171+
if (origin.isDocument()) {
172+
scrubbed = scrub(origin.asDocument());
173+
} else if (origin.isArray()) {
174+
scrubbed = scrub(origin.asArray());
175+
} else {
176+
scrubbed = HIDDEN_CHAR;
177+
}
178+
return scrubbed;
100179
}
101-
return normalized;
102180
}
103181
}

dd-java-agent/integrations/helpers/src/main/java/io/opentracing/contrib/mongo/TracingCommandListenerFactory.java

Lines changed: 0 additions & 14 deletions
This file was deleted.

dd-java-agent/src/main/resources/initializer-rules.btm

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,6 @@ DO
4848
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
4949
ENDRULE
5050

51-
RULE TracingCommandListener-init
52-
CLASS io.opentracing.contrib.mongo.TracingCommandListener
53-
METHOD <init>
54-
AT EXIT
55-
IF TRUE
56-
DO
57-
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
58-
ENDRULE
59-
6051

6152
# Instrument OkHttp
6253
# ===========================

dd-java-agent/src/main/resources/integration-rules.btm

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,6 @@ DO
7070
patch($0);
7171
ENDRULE
7272

73-
RULE opentracing-mongo-driver-helper
74-
CLASS io.opentracing.contrib.mongo.TracingCommandListener
75-
METHOD decorate
76-
HELPER com.datadoghq.agent.integration.MongoHelper
77-
AT EXIT
78-
IF TRUE
79-
DO
80-
decorate($1, $2);
81-
ENDRULE
82-
8373

8474
# Instrument OkHttp
8575
# ===========================

dd-java-agent/src/test/java/com/datadoghq/agent/integration/MongoHelperTest.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,57 @@
44

55
import com.datadoghq.trace.DDSpan;
66
import com.datadoghq.trace.DDTracer;
7+
import com.mongodb.ServerAddress;
8+
import com.mongodb.connection.ClusterId;
9+
import com.mongodb.connection.ConnectionDescription;
10+
import com.mongodb.connection.ServerId;
711
import com.mongodb.event.CommandStartedEvent;
12+
import io.opentracing.tag.Tags;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import org.bson.BsonArray;
816
import org.bson.BsonDocument;
17+
import org.bson.BsonString;
918
import org.junit.Test;
1019

1120
public class MongoHelperTest {
1221

13-
@Test
14-
public void test() {
22+
private static ConnectionDescription makeConnection() {
23+
return new ConnectionDescription(new ServerId(new ClusterId(), new ServerAddress()));
24+
}
1525

26+
@Test
27+
public void mongoSpan() {
1628
final CommandStartedEvent cmd =
17-
new CommandStartedEvent(1, null, "databasename", "query", new BsonDocument());
29+
new CommandStartedEvent(1, makeConnection(), "databasename", "query", new BsonDocument());
1830

1931
final DDSpan span = new DDTracer().buildSpan("foo").startManual();
20-
new MongoHelper(null).decorate(span, cmd);
32+
MongoHelper.DDTracingCommandListener.decorate(span, cmd);
2133

2234
assertThat(span.context().getSpanType()).isEqualTo("mongodb");
2335
assertThat(span.context().getResourceName())
2436
.isEqualTo(span.context().getTags().get("db.statement"));
2537
}
38+
39+
@Test
40+
public void queryScrubbing() {
41+
// all "secret" strings should be scrubbed out of these queries
42+
BsonDocument query1 = new BsonDocument("find", new BsonString("show"));
43+
query1.put("stuff", new BsonString("secret"));
44+
BsonDocument query2 = new BsonDocument("insert", new BsonString("table"));
45+
BsonDocument query2_1 = new BsonDocument("count", new BsonString("show"));
46+
query2_1.put("id", new BsonString("secret"));
47+
query2.put("docs", new BsonArray(Arrays.asList(new BsonString("secret"), query2_1)));
48+
List<BsonDocument> queries = Arrays.asList(query1, query2);
49+
for (BsonDocument query : queries) {
50+
final CommandStartedEvent cmd =
51+
new CommandStartedEvent(1, makeConnection(), "databasename", "query", query);
52+
53+
final DDSpan span = new DDTracer().buildSpan("foo").startManual();
54+
MongoHelper.DDTracingCommandListener.decorate(span, cmd);
55+
56+
assertThat(span.getTags().get(Tags.DB_STATEMENT.getKey()))
57+
.isEqualTo(query.toString().replaceAll("secret", "?"));
58+
}
59+
}
2660
}

0 commit comments

Comments
 (0)