Skip to content

Add new features for trace tagging rules #9131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dd-java-agent/appsec/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies {
implementation project(':internal-api')
implementation project(':communication')
implementation project(':telemetry')
implementation group: 'io.sqreen', name: 'libsqreen', version: '15.0.0'
implementation group: 'io.sqreen', name: 'libsqreen', version: '15.0.2'
implementation libs.moshi

testImplementation libs.bytebuddy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_AUTO_USER_INSTRUM_MODE;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_CUSTOM_BLOCKING_RESPONSE;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_CUSTOM_RULES;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_DD_MULTICONFIG;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_DD_RULES;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_EXCLUSIONS;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_EXCLUSION_DATA;
Expand All @@ -18,6 +19,7 @@
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_SSRF;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_REQUEST_BLOCKING;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_SESSION_FINGERPRINT;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_TRACE_TAGGING_RULES;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_TRUSTED_IPS;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_USER_BLOCKING;
import static datadog.remoteconfig.Capabilities.CAPABILITY_ENDPOINT_FINGERPRINT;
Expand All @@ -37,8 +39,8 @@
import com.datadog.ddwaf.exception.InvalidRuleSetException;
import com.datadog.ddwaf.exception.UnclassifiedWafException;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter;
import datadog.remoteconfig.ConfigurationEndListener;
import datadog.remoteconfig.ConfigurationPoller;
import datadog.remoteconfig.PollingRateHinter;
Expand All @@ -61,6 +63,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -92,15 +95,12 @@ public class AppSecConfigServiceImpl implements AppSecConfigService {
new WAFInitializationResultReporter();
private final WAFStatsReporter statsReporter = new WAFStatsReporter();

private static final JsonAdapter<Map<String, Object>> ADAPTER =
new Moshi.Builder()
.build()
.adapter(Types.newParameterizedType(Map.class, String.class, Object.class));
private static final JsonAdapter<Object> ADAPTER = new SafeMapAdapter();

private boolean hasUserWafConfig;
private boolean defaultConfigActivated;
private final Set<String> usedDDWafConfigKeys = new HashSet<>();
private final String DEFAULT_WAF_CONFIG_RULE = "DEFAULT_WAF_CONFIG";
private final String DEFAULT_WAF_CONFIG_RULE = "ASM_DD/default";
private String currentRuleVersion;
private List<AppSecModule> modulesToUpdateVersionIn;

Expand Down Expand Up @@ -131,6 +131,7 @@ private void subscribeConfigurationPoller() {

long capabilities =
CAPABILITY_ASM_DD_RULES
| CAPABILITY_ASM_DD_MULTICONFIG
| CAPABILITY_ASM_IP_BLOCKING
| CAPABILITY_ASM_EXCLUSIONS
| CAPABILITY_ASM_EXCLUSION_DATA
Expand All @@ -142,7 +143,8 @@ private void subscribeConfigurationPoller() {
| CAPABILITY_ENDPOINT_FINGERPRINT
| CAPABILITY_ASM_SESSION_FINGERPRINT
| CAPABILITY_ASM_NETWORK_FINGERPRINT
| CAPABILITY_ASM_HEADER_FINGERPRINT;
| CAPABILITY_ASM_HEADER_FINGERPRINT
| CAPABILITY_ASM_TRACE_TAGGING_RULES;
if (tracerConfig.isAppSecRaspEnabled()) {
capabilities |= CAPABILITY_ASM_RASP_SQLI;
capabilities |= CAPABILITY_ASM_RASP_SSRF;
Expand Down Expand Up @@ -185,7 +187,8 @@ public void accept(ConfigKey configKey, byte[] content, PollingRateHinter pollin
}
} else {
Map<String, Object> contentMap =
ADAPTER.fromJson(Okio.buffer(Okio.source(new ByteArrayInputStream(content))));
(Map<String, Object>)
ADAPTER.fromJson(Okio.buffer(Okio.source(new ByteArrayInputStream(content))));
try {
handleWafUpdateResultReport(configKey.toString(), contentMap);
} catch (AppSecModule.AppSecModuleActivationException e) {
Expand All @@ -211,7 +214,7 @@ private class AppSecConfigChangesDDListener extends AppSecConfigChangesListener
public void accept(ConfigKey configKey, byte[] content, PollingRateHinter pollingRateHinter)
throws IOException {
if (defaultConfigActivated) { // if we get any config, remove the default one
log.debug("Removing default config");
log.debug("Removing default config ASM_DD/default");
try {
wafBuilder.removeConfig(DEFAULT_WAF_CONFIG_RULE);
} catch (UnclassifiedWafException e) {
Expand Down Expand Up @@ -425,7 +428,8 @@ private static Map<String, Object> loadDefaultWafConfig() throws IOException {
throw new IOException("Resource " + DEFAULT_CONFIG_LOCATION + " not found");
}

Map<String, Object> ret = ADAPTER.fromJson(Okio.buffer(Okio.source(is)));
Map<String, Object> ret =
(Map<String, Object>) ADAPTER.fromJson(Okio.buffer(Okio.source(is)));

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

StandardizedLogging._initialConfigSourceAndLibddwafVersion(log, filename);
if (log.isInfoEnabled()) {
Expand Down Expand Up @@ -471,6 +476,7 @@ public void close() {
this.configurationPoller.removeCapabilities(
CAPABILITY_ASM_ACTIVATION
| CAPABILITY_ASM_DD_RULES
| CAPABILITY_ASM_DD_MULTICONFIG
| CAPABILITY_ASM_IP_BLOCKING
| CAPABILITY_ASM_EXCLUSIONS
| CAPABILITY_ASM_EXCLUSION_DATA
Expand All @@ -488,7 +494,8 @@ public void close() {
| CAPABILITY_ENDPOINT_FINGERPRINT
| CAPABILITY_ASM_SESSION_FINGERPRINT
| CAPABILITY_ASM_NETWORK_FINGERPRINT
| CAPABILITY_ASM_HEADER_FINGERPRINT);
| CAPABILITY_ASM_HEADER_FINGERPRINT
| CAPABILITY_ASM_TRACE_TAGGING_RULES);
this.configurationPoller.removeListeners(Product.ASM_DD);
this.configurationPoller.removeListeners(Product.ASM_DATA);
this.configurationPoller.removeListeners(Product.ASM);
Expand Down Expand Up @@ -558,4 +565,59 @@ private static WafConfig createWafConfig(Config config) {
}
return wafConfig;
}

private static class SafeMapAdapter extends JsonAdapter<Object> {
@Override
public Object fromJson(JsonReader reader) throws IOException {
switch (reader.peek()) {
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedHashMap<>();
reader.beginObject();
while (reader.hasNext()) {
map.put(reader.nextName(), fromJson(reader));
}
reader.endObject();
return map;

case BEGIN_ARRAY:
List<Object> list = new ArrayList<>();
reader.beginArray();
while (reader.hasNext()) {
list.add(fromJson(reader));
}
reader.endArray();
return list;

case STRING:
return reader.nextString();
case NUMBER:
String numberStr = reader.nextString();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

necessary for this part

try {
if (numberStr.contains(".")) {
return Double.parseDouble(numberStr);
} else {
return Long.parseLong(numberStr);
}
} catch (NumberFormatException e) {
// Fallback to string if parsing fails
return numberStr;
}

case BOOLEAN:
return reader.nextBoolean();

case NULL:
reader.nextNull();
return null;

default:
throw new IllegalStateException("Unexpected token: " + reader.peek());
}
}

@Override
public void toJson(JsonWriter writer, Object value) throws IOException {
throw new UnsupportedOperationException("Serialization not supported");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ private void initOrUpdateWafHandle(AppSecModuleConfigurer.Reconfiguration reconf
reconf.reloadSubscriptions();
}

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

if (!events.isEmpty()) {
if (!reqCtx.isThrottled(rateLimiter)) {
if (resultWithData.keep) {
if (!isThrottled) {
AgentSpan activeSpan = AgentTracer.get().activeSpan();
if (activeSpan != null) {
log.debug("Setting force-keep tag on the current span");
log.debug("Setting force-keep tag and manual keep tag on the current span");
// Keep event related span, because it could be ignored in case of
// reduced datadog sampling rate.
activeSpan.getLocalRootSpan().setTag(Tags.ASM_KEEP, true);
Expand All @@ -417,18 +423,19 @@ public void onDataAvailable(
.getLocalRootSpan()
.setTag(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM);
} else {
// If active span is not available the ASM_KEEP tag will be set in the GatewayBridge
// when the request ends
// If active span is not available then we need to set manual keep in GatewayBridge
log.debug("There is no active span available");
}
reqCtx.reportEvents(events);
} else {
log.debug("Rate limited WAF events");
if (!gwCtx.isRasp) {
reqCtx.setWafRateLimited();
}
}
}
if (resultWithData.events && !events.isEmpty() && !isThrottled) {
reqCtx.reportEvents(events);
}

if (flow.isBlocking()) {
if (!gwCtx.isRasp) {
Expand All @@ -437,8 +444,8 @@ public void onDataAvailable(
}
}

if (resultWithData.derivatives != null) {
reqCtx.reportDerivatives(resultWithData.derivatives);
if (resultWithData.attributes != null && !resultWithData.attributes.isEmpty()) {
reqCtx.reportDerivatives(resultWithData.attributes);
}
}

Expand Down Expand Up @@ -559,6 +566,10 @@ private Waf.ResultWithData runWafTransient(
private Collection<AppSecEvent> buildEvents(Waf.ResultWithData actionWithData) {
Collection<WAFResultData> listResults;
try {
if (actionWithData.data == null || actionWithData.data.isEmpty()) {
log.debug("WAF returned no data");
return emptyList();
}
listResults = RES_JSON_ADAPTER.fromJson(actionWithData.data);
} catch (IOException e) {
throw new UndeclaredThrowableException(e);
Expand Down
Loading
Loading