Skip to content

Commit 5ef6358

Browse files
committed
Add new features for trace tagging rules
Signed-off-by: sezen.leblay <[email protected]>
1 parent 036d103 commit 5ef6358

File tree

11 files changed

+1025
-40
lines changed

11 files changed

+1025
-40
lines changed

dd-java-agent/appsec/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ dependencies {
1515
implementation project(':internal-api')
1616
implementation project(':communication')
1717
implementation project(':telemetry')
18-
implementation group: 'io.sqreen', name: 'libsqreen', version: '15.0.0'
18+
implementation group: 'io.sqreen', name: 'libsqreen', version: '15.0.2'
1919
implementation libs.moshi
2020

2121
testImplementation libs.bytebuddy

dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecConfigServiceImpl.java

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_AUTO_USER_INSTRUM_MODE;
66
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_CUSTOM_BLOCKING_RESPONSE;
77
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_CUSTOM_RULES;
8+
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_DD_MULTICONFIG;
89
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_DD_RULES;
910
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_EXCLUSIONS;
1011
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_EXCLUSION_DATA;
@@ -18,6 +19,7 @@
1819
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_SSRF;
1920
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_REQUEST_BLOCKING;
2021
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_SESSION_FINGERPRINT;
22+
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_TRACE_TAGGING_RULES;
2123
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_TRUSTED_IPS;
2224
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_USER_BLOCKING;
2325
import static datadog.remoteconfig.Capabilities.CAPABILITY_ENDPOINT_FINGERPRINT;
@@ -37,8 +39,8 @@
3739
import com.datadog.ddwaf.exception.InvalidRuleSetException;
3840
import com.datadog.ddwaf.exception.UnclassifiedWafException;
3941
import com.squareup.moshi.JsonAdapter;
40-
import com.squareup.moshi.Moshi;
41-
import com.squareup.moshi.Types;
42+
import com.squareup.moshi.JsonReader;
43+
import com.squareup.moshi.JsonWriter;
4244
import datadog.remoteconfig.ConfigurationEndListener;
4345
import datadog.remoteconfig.ConfigurationPoller;
4446
import datadog.remoteconfig.PollingRateHinter;
@@ -61,6 +63,7 @@
6163
import java.util.Collections;
6264
import java.util.HashMap;
6365
import java.util.HashSet;
66+
import java.util.LinkedHashMap;
6467
import java.util.List;
6568
import java.util.Map;
6669
import java.util.Set;
@@ -92,15 +95,12 @@ public class AppSecConfigServiceImpl implements AppSecConfigService {
9295
new WAFInitializationResultReporter();
9396
private final WAFStatsReporter statsReporter = new WAFStatsReporter();
9497

95-
private static final JsonAdapter<Map<String, Object>> ADAPTER =
96-
new Moshi.Builder()
97-
.build()
98-
.adapter(Types.newParameterizedType(Map.class, String.class, Object.class));
98+
private static final JsonAdapter<Object> ADAPTER = new SafeMapAdapter();
9999

100100
private boolean hasUserWafConfig;
101101
private boolean defaultConfigActivated;
102102
private final Set<String> usedDDWafConfigKeys = new HashSet<>();
103-
private final String DEFAULT_WAF_CONFIG_RULE = "DEFAULT_WAF_CONFIG";
103+
private final String DEFAULT_WAF_CONFIG_RULE = "ASM_DD/default";
104104
private String currentRuleVersion;
105105
private List<AppSecModule> modulesToUpdateVersionIn;
106106

@@ -131,6 +131,7 @@ private void subscribeConfigurationPoller() {
131131

132132
long capabilities =
133133
CAPABILITY_ASM_DD_RULES
134+
| CAPABILITY_ASM_DD_MULTICONFIG
134135
| CAPABILITY_ASM_IP_BLOCKING
135136
| CAPABILITY_ASM_EXCLUSIONS
136137
| CAPABILITY_ASM_EXCLUSION_DATA
@@ -142,7 +143,8 @@ private void subscribeConfigurationPoller() {
142143
| CAPABILITY_ENDPOINT_FINGERPRINT
143144
| CAPABILITY_ASM_SESSION_FINGERPRINT
144145
| CAPABILITY_ASM_NETWORK_FINGERPRINT
145-
| CAPABILITY_ASM_HEADER_FINGERPRINT;
146+
| CAPABILITY_ASM_HEADER_FINGERPRINT
147+
| CAPABILITY_ASM_TRACE_TAGGING_RULES;
146148
if (tracerConfig.isAppSecRaspEnabled()) {
147149
capabilities |= CAPABILITY_ASM_RASP_SQLI;
148150
capabilities |= CAPABILITY_ASM_RASP_SSRF;
@@ -185,7 +187,8 @@ public void accept(ConfigKey configKey, byte[] content, PollingRateHinter pollin
185187
}
186188
} else {
187189
Map<String, Object> contentMap =
188-
ADAPTER.fromJson(Okio.buffer(Okio.source(new ByteArrayInputStream(content))));
190+
(Map<String, Object>)
191+
ADAPTER.fromJson(Okio.buffer(Okio.source(new ByteArrayInputStream(content))));
189192
try {
190193
handleWafUpdateResultReport(configKey.toString(), contentMap);
191194
} catch (AppSecModule.AppSecModuleActivationException e) {
@@ -211,7 +214,7 @@ private class AppSecConfigChangesDDListener extends AppSecConfigChangesListener
211214
public void accept(ConfigKey configKey, byte[] content, PollingRateHinter pollingRateHinter)
212215
throws IOException {
213216
if (defaultConfigActivated) { // if we get any config, remove the default one
214-
log.debug("Removing default config");
217+
log.debug("Removing default config ASM_DD/default");
215218
try {
216219
wafBuilder.removeConfig(DEFAULT_WAF_CONFIG_RULE);
217220
} catch (UnclassifiedWafException e) {
@@ -425,7 +428,8 @@ private static Map<String, Object> loadDefaultWafConfig() throws IOException {
425428
throw new IOException("Resource " + DEFAULT_CONFIG_LOCATION + " not found");
426429
}
427430

428-
Map<String, Object> ret = ADAPTER.fromJson(Okio.buffer(Okio.source(is)));
431+
Map<String, Object> ret =
432+
(Map<String, Object>) ADAPTER.fromJson(Okio.buffer(Okio.source(is)));
429433

430434
StandardizedLogging._initialConfigSourceAndLibddwafVersion(log, "<bundled config>");
431435
if (log.isInfoEnabled()) {
@@ -442,7 +446,8 @@ private static Map<String, Object> loadUserWafConfig(Config tracerConfig) throws
442446
return null;
443447
}
444448
try (InputStream is = new FileInputStream(filename)) {
445-
Map<String, Object> ret = ADAPTER.fromJson(Okio.buffer(Okio.source(is)));
449+
Map<String, Object> ret =
450+
(Map<String, Object>) ADAPTER.fromJson(Okio.buffer(Okio.source(is)));
446451

447452
StandardizedLogging._initialConfigSourceAndLibddwafVersion(log, filename);
448453
if (log.isInfoEnabled()) {
@@ -471,6 +476,7 @@ public void close() {
471476
this.configurationPoller.removeCapabilities(
472477
CAPABILITY_ASM_ACTIVATION
473478
| CAPABILITY_ASM_DD_RULES
479+
| CAPABILITY_ASM_DD_MULTICONFIG
474480
| CAPABILITY_ASM_IP_BLOCKING
475481
| CAPABILITY_ASM_EXCLUSIONS
476482
| CAPABILITY_ASM_EXCLUSION_DATA
@@ -488,7 +494,8 @@ public void close() {
488494
| CAPABILITY_ENDPOINT_FINGERPRINT
489495
| CAPABILITY_ASM_SESSION_FINGERPRINT
490496
| CAPABILITY_ASM_NETWORK_FINGERPRINT
491-
| CAPABILITY_ASM_HEADER_FINGERPRINT);
497+
| CAPABILITY_ASM_HEADER_FINGERPRINT
498+
| CAPABILITY_ASM_TRACE_TAGGING_RULES);
492499
this.configurationPoller.removeListeners(Product.ASM_DD);
493500
this.configurationPoller.removeListeners(Product.ASM_DATA);
494501
this.configurationPoller.removeListeners(Product.ASM);
@@ -558,4 +565,59 @@ private static WafConfig createWafConfig(Config config) {
558565
}
559566
return wafConfig;
560567
}
568+
569+
private static class SafeMapAdapter extends JsonAdapter<Object> {
570+
@Override
571+
public Object fromJson(JsonReader reader) throws IOException {
572+
switch (reader.peek()) {
573+
case BEGIN_OBJECT:
574+
Map<String, Object> map = new LinkedHashMap<>();
575+
reader.beginObject();
576+
while (reader.hasNext()) {
577+
map.put(reader.nextName(), fromJson(reader));
578+
}
579+
reader.endObject();
580+
return map;
581+
582+
case BEGIN_ARRAY:
583+
List<Object> list = new ArrayList<>();
584+
reader.beginArray();
585+
while (reader.hasNext()) {
586+
list.add(fromJson(reader));
587+
}
588+
reader.endArray();
589+
return list;
590+
591+
case STRING:
592+
return reader.nextString();
593+
case NUMBER:
594+
String numberStr = reader.nextString();
595+
try {
596+
if (numberStr.contains(".")) {
597+
return Double.parseDouble(numberStr);
598+
} else {
599+
return Long.parseLong(numberStr);
600+
}
601+
} catch (NumberFormatException e) {
602+
// Fallback to string if parsing fails
603+
return numberStr;
604+
}
605+
606+
case BOOLEAN:
607+
return reader.nextBoolean();
608+
609+
case NULL:
610+
reader.nextNull();
611+
return null;
612+
613+
default:
614+
throw new IllegalStateException("Unexpected token: " + reader.peek());
615+
}
616+
}
617+
618+
@Override
619+
public void toJson(JsonWriter writer, Object value) throws IOException {
620+
throw new UnsupportedOperationException("Serialization not supported");
621+
}
622+
}
561623
}

dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ private void initOrUpdateWafHandle(AppSecModuleConfigurer.Reconfiguration reconf
223223
reconf.reloadSubscriptions();
224224
}
225225

226+
/**
227+
* Creates a rate limiter for AppSec events. The rate limiter accounts for when libddwaf returns
228+
* keep with a value of true, rather than when events are present, as specified in the technical
229+
* specification.
230+
*/
226231
private static RateLimiter getRateLimiter(Monitoring monitoring) {
227232
if (monitoring == null) {
228233
return null;
@@ -401,12 +406,13 @@ public void onDataAvailable(
401406
}
402407
}
403408
Collection<AppSecEvent> events = buildEvents(resultWithData);
409+
boolean isThrottled = reqCtx.isThrottled(rateLimiter);
404410

405-
if (!events.isEmpty()) {
406-
if (!reqCtx.isThrottled(rateLimiter)) {
411+
if (resultWithData.keep) {
412+
if (!isThrottled) {
407413
AgentSpan activeSpan = AgentTracer.get().activeSpan();
408414
if (activeSpan != null) {
409-
log.debug("Setting force-keep tag on the current span");
415+
log.debug("Setting force-keep tag and manual keep tag on the current span");
410416
// Keep event related span, because it could be ignored in case of
411417
// reduced datadog sampling rate.
412418
activeSpan.getLocalRootSpan().setTag(Tags.ASM_KEEP, true);
@@ -417,18 +423,19 @@ public void onDataAvailable(
417423
.getLocalRootSpan()
418424
.setTag(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM);
419425
} else {
420-
// If active span is not available the ASM_KEEP tag will be set in the GatewayBridge
421-
// when the request ends
426+
// If active span is not available then we need to set manual keep in GatewayBridge
422427
log.debug("There is no active span available");
423428
}
424-
reqCtx.reportEvents(events);
425429
} else {
426430
log.debug("Rate limited WAF events");
427431
if (!gwCtx.isRasp) {
428432
reqCtx.setWafRateLimited();
429433
}
430434
}
431435
}
436+
if (resultWithData.events && !events.isEmpty() && !isThrottled) {
437+
reqCtx.reportEvents(events);
438+
}
432439

433440
if (flow.isBlocking()) {
434441
if (!gwCtx.isRasp) {
@@ -437,8 +444,8 @@ public void onDataAvailable(
437444
}
438445
}
439446

440-
if (resultWithData.derivatives != null) {
441-
reqCtx.reportDerivatives(resultWithData.derivatives);
447+
if (resultWithData.attributes != null && !resultWithData.attributes.isEmpty()) {
448+
reqCtx.reportDerivatives(resultWithData.attributes);
442449
}
443450
}
444451

@@ -559,6 +566,10 @@ private Waf.ResultWithData runWafTransient(
559566
private Collection<AppSecEvent> buildEvents(Waf.ResultWithData actionWithData) {
560567
Collection<WAFResultData> listResults;
561568
try {
569+
if (actionWithData.data == null || actionWithData.data.isEmpty()) {
570+
log.debug("WAF returned no data");
571+
return emptyList();
572+
}
562573
listResults = RES_JSON_ADAPTER.fromJson(actionWithData.data);
563574
} catch (IOException e) {
564575
throw new UndeclaredThrowableException(e);

0 commit comments

Comments
 (0)