Skip to content

Commit c4329c6

Browse files
Allow login event SDK to be used with appsec disabled
1 parent 99139bb commit c4329c6

File tree

15 files changed

+607
-545
lines changed

15 files changed

+607
-545
lines changed

.circleci/config.continue.yml.j2

+2
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,8 @@ jobs:
860860
APPSEC_API_SECURITY
861861
APPSEC_API_SECURITY_RC
862862
APPSEC_API_SECURITY_WITH_SAMPLING
863+
APPSEC_AUTO_EVENTS_RC
864+
APPSEC_AUTO_EVENTS_EXTENDED
863865
APPSEC_WAF_TELEMETRY
864866
APPSEC_STANDALONE_V2
865867
IAST_STANDALONE_V2

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import datadog.trace.api.Platform;
1717
import datadog.trace.api.StatsDClientManager;
1818
import datadog.trace.api.WithGlobalTracer;
19+
import datadog.trace.api.appsec.AppSecEventTracker;
1920
import datadog.trace.api.config.AppSecConfig;
2021
import datadog.trace.api.config.CiVisibilityConfig;
2122
import datadog.trace.api.config.CwsConfig;
@@ -812,6 +813,10 @@ private static StatsDClientManager statsDClientManager() throws Exception {
812813
}
813814

814815
private static void maybeStartAppSec(Class<?> scoClass, Object o) {
816+
817+
// event tracking SDK must be available for customers even if AppSec is fully disabled
818+
AppSecEventTracker.install();
819+
815820
if (!(appSecEnabled || (remoteConfigEnabled && !appSecFullyDisabled))) {
816821
return;
817822
}

dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java

-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import datadog.remoteconfig.ConfigurationPoller;
1818
import datadog.trace.api.Config;
1919
import datadog.trace.api.ProductActivation;
20-
import datadog.trace.api.appsec.AppSecEventTracker;
2120
import datadog.trace.api.gateway.SubscriptionService;
2221
import datadog.trace.api.telemetry.ProductChange;
2322
import datadog.trace.api.telemetry.ProductChangeCollector;
@@ -100,8 +99,6 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s
10099

101100
Blocking.setBlockingService(new BlockingServiceImpl(REPLACEABLE_EVENT_PRODUCER));
102101

103-
AppSecEventTracker.setEventTracker(new AppSecEventTracker());
104-
105102
STARTED.set(true);
106103

107104
String startedAppSecModules = String.join(", ", STARTED_MODULES_INFO.values());

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

+20-29
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import com.datadog.appsec.report.AppSecEvent;
99
import com.datadog.appsec.util.StandardizedLogging;
1010
import datadog.trace.api.Config;
11-
import datadog.trace.api.UserIdCollectionMode;
1211
import datadog.trace.api.http.StoredBodySupplier;
1312
import datadog.trace.api.internal.TraceSegment;
1413
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
@@ -136,10 +135,8 @@ public class AppSecRequestContext implements DataBundle, Closeable {
136135

137136
// keep a reference to the last published usr.id
138137
private volatile String userId;
139-
private volatile UserIdCollectionMode userIdSource;
140138
// keep a reference to the last published usr.login
141139
private volatile String userLogin;
142-
private volatile UserIdCollectionMode userLoginSource;
143140
// keep a reference to the last published usr.session_id
144141
private volatile String sessionId;
145142

@@ -533,36 +530,30 @@ public void setRespDataPublished(boolean respDataPublished) {
533530
this.respDataPublished = respDataPublished;
534531
}
535532

536-
public String getUserId() {
537-
return userId;
538-
}
539-
540-
public void setUserId(String userId) {
533+
/**
534+
* Updates the current used usr.id
535+
*
536+
* @return {@code false} if the user id has not been updated
537+
*/
538+
public boolean updateUserId(String userId) {
539+
if (Objects.equals(this.userId, userId)) {
540+
return false;
541+
}
541542
this.userId = userId;
543+
return true;
542544
}
543545

544-
public UserIdCollectionMode getUserIdSource() {
545-
return userIdSource;
546-
}
547-
548-
public void setUserIdSource(UserIdCollectionMode userIdSource) {
549-
this.userIdSource = userIdSource;
550-
}
551-
552-
public String getUserLogin() {
553-
return userLogin;
554-
}
555-
556-
public void setUserLogin(String userLogin) {
546+
/**
547+
* Updates current used usr.login
548+
*
549+
* @return {@code false} if the user login has not been updated
550+
*/
551+
public boolean updateUserLogin(String userLogin) {
552+
if (Objects.equals(this.userLogin, userLogin)) {
553+
return false;
554+
}
557555
this.userLogin = userLogin;
558-
}
559-
560-
public UserIdCollectionMode getUserLoginSource() {
561-
return userLoginSource;
562-
}
563-
564-
public void setUserLoginSource(UserIdCollectionMode userLoginSource) {
565-
this.userLoginSource = userLoginSource;
556+
return true;
566557
}
567558

568559
public void setSessionId(String sessionId) {

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

+18-154
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
package com.datadog.appsec.gateway;
22

33
import static com.datadog.appsec.event.data.MapDataBundle.Builder.CAPACITY_0_2;
4-
import static com.datadog.appsec.event.data.MapDataBundle.Builder.CAPACITY_3_4;
54
import static com.datadog.appsec.event.data.MapDataBundle.Builder.CAPACITY_6_10;
65
import static com.datadog.appsec.gateway.AppSecRequestContext.DEFAULT_REQUEST_HEADERS_ALLOW_LIST;
76
import static com.datadog.appsec.gateway.AppSecRequestContext.REQUEST_HEADERS_ALLOW_LIST;
87
import static com.datadog.appsec.gateway.AppSecRequestContext.RESPONSE_HEADERS_ALLOW_LIST;
9-
import static datadog.trace.api.UserIdCollectionMode.ANONYMIZATION;
10-
import static datadog.trace.api.UserIdCollectionMode.DISABLED;
11-
import static datadog.trace.api.UserIdCollectionMode.SDK;
12-
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
13-
import static datadog.trace.util.Strings.toHexString;
148

159
import com.datadog.appsec.AppSecSystem;
1610
import com.datadog.appsec.api.security.ApiSecurityRequestSampler;
@@ -28,7 +22,6 @@
2822
import com.datadog.appsec.report.AppSecEventWrapper;
2923
import datadog.trace.api.Config;
3024
import datadog.trace.api.ProductTraceSource;
31-
import datadog.trace.api.UserIdCollectionMode;
3225
import datadog.trace.api.gateway.Events;
3326
import datadog.trace.api.gateway.Flow;
3427
import datadog.trace.api.gateway.IGSpanInfo;
@@ -48,19 +41,17 @@
4841
import java.net.URISyntaxException;
4942
import java.nio.charset.Charset;
5043
import java.nio.charset.StandardCharsets;
51-
import java.security.MessageDigest;
52-
import java.security.NoSuchAlgorithmException;
5344
import java.util.ArrayList;
5445
import java.util.Arrays;
5546
import java.util.Collection;
5647
import java.util.Collections;
48+
import java.util.EnumMap;
5749
import java.util.HashMap;
5850
import java.util.HashSet;
5951
import java.util.List;
6052
import java.util.Map;
6153
import java.util.Set;
6254
import java.util.concurrent.ConcurrentHashMap;
63-
import java.util.concurrent.atomic.AtomicBoolean;
6455
import java.util.regex.Pattern;
6556
import java.util.stream.Collectors;
6657
import org.slf4j.Logger;
@@ -76,21 +67,16 @@ public class GatewayBridge {
7667
private static final Pattern QUERY_PARAM_SPLITTER = Pattern.compile("&");
7768
private static final Map<String, List<String>> EMPTY_QUERY_PARAMS = Collections.emptyMap();
7869

79-
private static final int HASH_SIZE_BYTES = 16; // 128 bits
80-
private static final String ANON_PREFIX = "anon_";
81-
private static final AtomicBoolean SHA_MISSING_REPORTED = new AtomicBoolean(false);
82-
8370
/** User tracking tags that will force the collection of request headers */
8471
private static final String[] USER_TRACKING_TAGS = {
8572
"appsec.events.users.login.success.track", "appsec.events.users.login.failure.track"
8673
};
8774

88-
private static final Map<String, LoginEvent> EVENT_MAPPINGS = new HashMap<>();
75+
private static final Map<LoginEvent, Address<?>> EVENT_MAPPINGS = new EnumMap<>(LoginEvent.class);
8976

9077
static {
91-
EVENT_MAPPINGS.put("users.login.success", LoginEvent.LOGIN_SUCCESS);
92-
EVENT_MAPPINGS.put("users.login.failure", LoginEvent.LOGIN_FAILURE);
93-
EVENT_MAPPINGS.put("users.signup", LoginEvent.SIGN_UP);
78+
EVENT_MAPPINGS.put(LoginEvent.LOGIN_SUCCESS, KnownAddresses.LOGIN_SUCCESS);
79+
EVENT_MAPPINGS.put(LoginEvent.LOGIN_FAILURE, KnownAddresses.LOGIN_FAILURE);
9480
}
9581

9682
private static final String METASTRUCT_EXPLOIT = "exploit";
@@ -198,44 +184,16 @@ public void reset() {
198184
shellCmdSubInfo = null;
199185
}
200186

201-
private Flow<Void> onUser(
202-
final RequestContext ctx_, final UserIdCollectionMode mode, final String originalUser) {
203-
if (mode == DISABLED) {
204-
return NoopFlow.INSTANCE;
205-
}
206-
final String user = anonymizeUser(mode, originalUser);
207-
if (user == null) {
208-
return NoopFlow.INSTANCE;
209-
}
187+
private Flow<Void> onUser(final RequestContext ctx_, final String user) {
210188
final AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC);
211189
if (ctx == null) {
212190
return NoopFlow.INSTANCE;
213191
}
214-
final TraceSegment segment = ctx_.getTraceSegment();
215-
216-
// span with ASM data
217-
segment.setTagTop(Tags.ASM_KEEP, true);
218-
segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM);
219-
220-
// skip event if we have an SDK one
221-
if (mode != SDK) {
222-
segment.setTagTop("_dd.appsec.usr.id", user);
223-
if (ctx.getUserIdSource() == SDK) {
224-
return NoopFlow.INSTANCE;
225-
}
226-
}
227-
228-
// update span tags
229-
segment.setTagTop("usr.id", user);
230-
segment.setTagTop("_dd.appsec.user.collection_mode", mode.fullName());
231192

232193
// update current context with new user id
233-
ctx.setUserIdSource(mode);
234-
final boolean newUserId = !user.equals(ctx.getUserId());
235-
if (!newUserId) {
194+
if (!ctx.updateUserId(user)) {
236195
return NoopFlow.INSTANCE;
237196
}
238-
ctx.setUserId(user);
239197

240198
// call waf if we have a new user id
241199
while (true) {
@@ -259,96 +217,29 @@ private Flow<Void> onUser(
259217
}
260218

261219
private Flow<Void> onLoginEvent(
262-
final RequestContext ctx_,
263-
final UserIdCollectionMode mode,
264-
final String eventName,
265-
final Boolean exists,
266-
final String originalUser,
267-
final Map<String, String> metadata) {
268-
if (mode == DISABLED) {
269-
return NoopFlow.INSTANCE;
270-
}
220+
final RequestContext ctx_, final LoginEvent event, final String login) {
271221
final AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC);
272222
if (ctx == null) {
273223
return NoopFlow.INSTANCE;
274224
}
275-
final TraceSegment segment = ctx_.getTraceSegment();
276-
277-
// span with ASM data
278-
segment.setTagTop(Tags.ASM_KEEP, true);
279-
segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM);
280-
281-
// update span tags
282-
segment.setTagTop("appsec.events." + eventName + ".track", true, true);
283-
if (metadata != null && !metadata.isEmpty()) {
284-
segment.setTagTop("appsec.events." + eventName, metadata, true);
285-
}
286-
if (mode == SDK) {
287-
segment.setTagTop("_dd.appsec.events." + eventName + ".sdk", true, true);
288-
} else {
289-
segment.setTagTop("_dd.appsec.events." + eventName + ".auto.mode", mode.fullName(), true);
290-
}
291-
292-
if (exists != null) {
293-
if (mode == SDK || ctx.getUserLoginSource() != SDK) {
294-
segment.setTagTop("appsec.events." + eventName + ".usr.exists", exists, true);
295-
}
296-
}
297-
298-
final String user = anonymizeUser(mode, originalUser);
299-
if (user == null) {
300-
// can happen in custom events
301-
return NoopFlow.INSTANCE;
302-
}
303-
304-
// parse the event (might be null for custom events sent via the SDK)
305-
final LoginEvent sourceEvent = EVENT_MAPPINGS.get(eventName);
306-
307-
// skip event if we have an SDK one
308-
if (mode != SDK) {
309-
segment.setTagTop("_dd.appsec.usr.login", user);
310-
if (ctx.getUserLoginSource() == SDK) {
311-
return NoopFlow.INSTANCE;
312-
}
313-
} else {
314-
if (sourceEvent == LoginEvent.LOGIN_SUCCESS) {
315-
segment.setTagTop("usr.id", user, false);
316-
} else {
317-
segment.setTagTop("appsec.events." + eventName + ".usr.id", user, true);
318-
}
319-
segment.setTagTop("_dd.appsec.user.collection_mode", mode.fullName());
320-
}
321-
322-
// update user span tags
323-
segment.setTagTop("appsec.events." + eventName + ".usr.login", user, true);
324225

325226
// update current context with new user login
326-
ctx.setUserLoginSource(mode);
327-
if (mode == SDK) {
328-
ctx.setUserIdSource(mode); // we are setting the usr.id through the SDK
329-
}
330-
final boolean newUserLogin = !user.equals(ctx.getUserLogin());
331-
if (!newUserLogin) {
227+
if (!ctx.updateUserLogin(login)) {
332228
return NoopFlow.INSTANCE;
333229
}
334-
ctx.setUserLogin(user);
335230

336231
// call waf if we have a new user login
337-
final List<Address<?>> addresses = new ArrayList<>(3);
338-
final MapDataBundle.Builder bundleBuilder = new MapDataBundle.Builder(CAPACITY_3_4);
232+
final List<Address<?>> addresses = new ArrayList<>(2);
233+
final MapDataBundle.Builder bundleBuilder = new MapDataBundle.Builder(CAPACITY_0_2);
339234
addresses.add(KnownAddresses.USER_LOGIN);
340-
bundleBuilder.add(KnownAddresses.USER_LOGIN, user);
341-
if (mode == SDK) {
342-
addresses.add(KnownAddresses.USER_ID);
343-
bundleBuilder.add(KnownAddresses.USER_ID, user);
344-
}
345-
// we don't support null values for the address so we use an invalid placeholder here
346-
if (sourceEvent == LoginEvent.LOGIN_SUCCESS) {
347-
addresses.add(KnownAddresses.LOGIN_SUCCESS);
348-
bundleBuilder.add(KnownAddresses.LOGIN_SUCCESS, "invalid");
349-
} else if (sourceEvent == LoginEvent.LOGIN_FAILURE) {
350-
addresses.add(KnownAddresses.LOGIN_FAILURE);
351-
bundleBuilder.add(KnownAddresses.LOGIN_FAILURE, "invalid");
235+
bundleBuilder.add(KnownAddresses.USER_LOGIN, login);
236+
237+
// parse the event
238+
Address<?> address = EVENT_MAPPINGS.get(event);
239+
if (address != null) {
240+
addresses.add(address);
241+
// we don't support null values for the address so we use an invalid placeholder here
242+
bundleBuilder.add(address, "invalid");
352243
}
353244
final DataBundle bundle = bundleBuilder.build();
354245
final String subInfoKey =
@@ -1158,33 +1049,6 @@ private static int byteToDigit(byte b) {
11581049
return -1;
11591050
}
11601051

1161-
protected static String anonymizeUser(final UserIdCollectionMode mode, final String userId) {
1162-
if (mode != ANONYMIZATION || userId == null) {
1163-
return userId;
1164-
}
1165-
MessageDigest digest;
1166-
try {
1167-
// TODO avoid lookup a new instance every time
1168-
digest = MessageDigest.getInstance("SHA-256");
1169-
} catch (NoSuchAlgorithmException e) {
1170-
if (!SHA_MISSING_REPORTED.getAndSet(true)) {
1171-
log.error(
1172-
SEND_TELEMETRY,
1173-
"Missing SHA-256 digest, user collection in 'anon' mode cannot continue",
1174-
e);
1175-
}
1176-
return null;
1177-
}
1178-
digest.update(userId.getBytes());
1179-
byte[] hash = digest.digest();
1180-
if (hash.length > HASH_SIZE_BYTES) {
1181-
byte[] temp = new byte[HASH_SIZE_BYTES];
1182-
System.arraycopy(hash, 0, temp, 0, temp.length);
1183-
hash = temp;
1184-
}
1185-
return ANON_PREFIX + toHexString(hash);
1186-
}
1187-
11881052
private static class IGAppSecEventDependencies {
11891053

11901054
private static final Map<Address<?>, Collection<datadog.trace.api.gateway.EventType<?>>>

0 commit comments

Comments
 (0)