Skip to content

Commit 7e831d5

Browse files
Allow login events SDK to be used with appsec disabled (#8464)
Allow login event SDK to be used with appsec disabled
1 parent a39a27f commit 7e831d5

File tree

15 files changed

+611
-545
lines changed

15 files changed

+611
-545
lines changed

.circleci/config.continue.yml.j2

+2
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,8 @@ jobs:
862862
APPSEC_API_SECURITY
863863
APPSEC_API_SECURITY_RC
864864
APPSEC_API_SECURITY_WITH_SAMPLING
865+
APPSEC_AUTO_EVENTS_RC
866+
APPSEC_AUTO_EVENTS_EXTENDED
865867
APPSEC_WAF_TELEMETRY
866868
APPSEC_STANDALONE_V2
867869
IAST_STANDALONE_V2

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

+9
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;
@@ -816,6 +817,14 @@ private static StatsDClientManager statsDClientManager() throws Exception {
816817
}
817818

818819
private static void maybeStartAppSec(Class<?> scoClass, Object o) {
820+
821+
try {
822+
// event tracking SDK must be available for customers even if AppSec is fully disabled
823+
AppSecEventTracker.install();
824+
} catch (final Exception e) {
825+
log.debug("Error starting AppSec Event Tracker", e);
826+
}
827+
819828
if (!(appSecEnabled || (remoteConfigEnabled && !appSecFullyDisabled))) {
820829
return;
821830
}

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;
@@ -97,8 +96,6 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s
9796

9897
Blocking.setBlockingService(new BlockingServiceImpl(REPLACEABLE_EVENT_PRODUCER));
9998

100-
AppSecEventTracker.setEventTracker(new AppSecEventTracker());
101-
10299
STARTED.set(true);
103100

104101
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

@@ -536,36 +533,30 @@ public void setRespDataPublished(boolean respDataPublished) {
536533
this.respDataPublished = respDataPublished;
537534
}
538535

539-
public String getUserId() {
540-
return userId;
541-
}
542-
543-
public void setUserId(String userId) {
536+
/**
537+
* Updates the current used usr.id
538+
*
539+
* @return {@code false} if the user id has not been updated
540+
*/
541+
public boolean updateUserId(String userId) {
542+
if (Objects.equals(this.userId, userId)) {
543+
return false;
544+
}
544545
this.userId = userId;
546+
return true;
545547
}
546548

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

571562
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 =
@@ -1159,33 +1050,6 @@ private static int byteToDigit(byte b) {
11591050
return -1;
11601051
}
11611052

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

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

0 commit comments

Comments
 (0)