Skip to content

Commit 4eeed47

Browse files
Prevent publishing the same usr.id to the WAF twice (#7699)
1 parent 0a80d67 commit 4eeed47

File tree

4 files changed

+61
-9
lines changed

4 files changed

+61
-9
lines changed

dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ public class AppSecRequestContext implements DataBundle, Closeable {
124124
private volatile boolean blocked;
125125
private volatile int timeouts;
126126

127+
// keep a reference to the last published usr.id
128+
private volatile String userId;
129+
127130
private static final AtomicIntegerFieldUpdater<AppSecRequestContext> TIMEOUTS_UPDATER =
128131
AtomicIntegerFieldUpdater.newUpdater(AppSecRequestContext.class, "timeouts");
129132

@@ -423,6 +426,14 @@ public void setRespDataPublished(boolean respDataPublished) {
423426
this.respDataPublished = respDataPublished;
424427
}
425428

429+
public String getUserId() {
430+
return userId;
431+
}
432+
433+
public void setUserId(String userId) {
434+
this.userId = userId;
435+
}
436+
426437
@Override
427438
public void close() {
428439
final AgentSpan span = AgentTracer.activeSpan();

dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,21 +156,35 @@ private TriFunction<RequestContext, UserIdCollectionMode, String, Flow<Void>> on
156156
return NoopFlow.INSTANCE;
157157
}
158158
final TraceSegment segment = ctx_.getTraceSegment();
159+
// user id can be set by the SDK overriding the auto event, always update the segment
159160
segment.setTagTop("usr.id", userId);
160161
segment.setTagTop("_dd.appsec.user.collection_mode", mode.shortName());
161-
final Address<?>[] addresses =
162-
address == KnownAddresses.USER_ID
163-
? new Address[] {KnownAddresses.USER_ID}
164-
: new Address[] {KnownAddresses.USER_ID, address};
162+
final List<Address<?>> addresses = new ArrayList<>(2);
163+
final boolean newUserId = !userId.equals(ctx.getUserId());
164+
if (newUserId) {
165+
// unlikely that multiple threads will update the value at the same time
166+
ctx.setUserId(userId);
167+
addresses.add(KnownAddresses.USER_ID);
168+
}
169+
if (address != KnownAddresses.USER_ID) {
170+
addresses.add(address);
171+
}
172+
if (addresses.isEmpty()) {
173+
// nothing to publish so short-circuit here
174+
return NoopFlow.INSTANCE;
175+
}
176+
final Address<?>[] addressArray = addresses.toArray(new Address[0]);
165177
while (true) {
166178
DataSubscriberInfo subInfo =
167179
userIdSubInfo.computeIfAbsent(
168-
address, k -> producerService.getDataSubscribers(addresses));
180+
address, k -> producerService.getDataSubscribers(addressArray));
169181
if (subInfo == null || subInfo.isEmpty()) {
170182
return NoopFlow.INSTANCE;
171183
}
172-
MapDataBundle.Builder bundle =
173-
new MapDataBundle.Builder(CAPACITY_0_2).add(KnownAddresses.USER_ID, userId);
184+
MapDataBundle.Builder bundle = new MapDataBundle.Builder(CAPACITY_0_2);
185+
if (newUserId) {
186+
bundle.add(KnownAddresses.USER_ID, userId);
187+
}
174188
if (address != KnownAddresses.USER_ID) {
175189
// we don't support null values for the address so we use an invalid placeholder here
176190
bundle.add(address, "invalid");

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,4 +1020,20 @@ class GatewayBridgeSpecification extends DDSpecification {
10201020
'loginSuccessCB' | KnownAddresses.LOGIN_SUCCESS
10211021
'loginFailureCB' | KnownAddresses.LOGIN_FAILURE
10221022
}
1023+
1024+
void 'ensure that the same user id is not published twice'() {
1025+
setup:
1026+
eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo
1027+
final userId = 'admin'
1028+
1029+
when:
1030+
userIdCB.apply(ctx, UserIdCollectionMode.IDENTIFICATION, userId)
1031+
userIdCB.apply(ctx, UserIdCollectionMode.SDK, userId)
1032+
1033+
then:
1034+
2 * traceSegment.setTagTop('usr.id', userId)
1035+
1 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', UserIdCollectionMode.IDENTIFICATION.shortName())
1036+
1 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', UserIdCollectionMode.SDK.shortName())
1037+
1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext)
1038+
}
10231039
}

dd-java-agent/instrumentation/spring-security-5/src/test/groovy/datadog/trace/instrumentation/springsecurity5/SpringBootBasedTest.groovy

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ class SpringBootBasedTest extends AppSecHttpServerTest<ConfigurableApplicationCo
315315

316316
void 'test multiple user ids do not cause warn messages'() {
317317
setup:
318+
def logMessagePrefix = 'Attempt to replace'
318319
def client = clientBuilder().cookieJar(cookieJar()).followRedirects(false).build()
319320
def formBody = new FormBody.Builder()
320321
.add('username', 'admin')
@@ -324,14 +325,24 @@ class SpringBootBasedTest extends AppSecHttpServerTest<ConfigurableApplicationCo
324325
def loginResponse = client.newCall(loginRequest).execute()
325326
assert loginResponse.code() == LOGIN.status
326327

327-
when:
328+
when: 'sdk with different user'
328329
def sdkBody = new FormBody.Builder().add("sdkUser", "sdkUser").build()
329330
def sdkRequest = request(SDK, 'POST', sdkBody).build()
330331
client.newCall(sdkRequest).execute()
331332

332333
then:
333334
1 * reqCtxLogAppender.doAppend({ LoggingEvent event ->
334-
event.level.levelInt == Level.DEBUG_INT && event.message.startsWith('Attempt to replace')
335+
event.level.levelInt == Level.DEBUG_INT && event.message.startsWith(logMessagePrefix)
336+
})
337+
338+
when: 'sdk with same user'
339+
sdkBody = new FormBody.Builder().add("sdkUser", "admin").build()
340+
sdkRequest = request(SDK, 'POST', sdkBody).build()
341+
client.newCall(sdkRequest).execute()
342+
343+
then:
344+
0 * reqCtxLogAppender.doAppend({ LoggingEvent event ->
345+
event.message.startsWith(logMessagePrefix)
335346
})
336347
}
337348
}

0 commit comments

Comments
 (0)