diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java index d917ac12f40..faec447c2c4 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java @@ -122,7 +122,8 @@ public class AppSecRequestContext implements DataBundle, Closeable { private volatile PowerwafMetrics raspMetrics; private final AtomicInteger raspMetricsCounter = new AtomicInteger(0); private volatile boolean blocked; - private volatile int timeouts; + private volatile int wafTimeouts; + private volatile int raspTimeouts; // keep a reference to the last published usr.id private volatile String userId; @@ -133,8 +134,10 @@ public class AppSecRequestContext implements DataBundle, Closeable { // keep a reference to the last published usr.session_id private volatile String sessionId; - private static final AtomicIntegerFieldUpdater TIMEOUTS_UPDATER = - AtomicIntegerFieldUpdater.newUpdater(AppSecRequestContext.class, "timeouts"); + private static final AtomicIntegerFieldUpdater WAF_TIMEOUTS_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(AppSecRequestContext.class, "wafTimeouts"); + private static final AtomicIntegerFieldUpdater RASP_TIMEOUTS_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(AppSecRequestContext.class, "raspTimeouts"); // to be called by the Event Dispatcher public void addAll(DataBundle newData) { @@ -177,12 +180,20 @@ public boolean isBlocked() { return blocked; } - public void increaseTimeouts() { - TIMEOUTS_UPDATER.incrementAndGet(this); + public void increaseWafTimeouts() { + WAF_TIMEOUTS_UPDATER.incrementAndGet(this); } - public int getTimeouts() { - return timeouts; + public void increaseRaspTimeouts() { + RASP_TIMEOUTS_UPDATER.incrementAndGet(this); + } + + public int getWafTimeouts() { + return wafTimeouts; + } + + public int getRaspTimeouts() { + return raspTimeouts; } public Additive getOrCreateAdditive(PowerwafContext ctx, boolean createMetrics, boolean isRasp) { diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFModule.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFModule.java index 2e6f929ab3e..b57d6d2c2f9 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFModule.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFModule.java @@ -428,11 +428,13 @@ public void onDataAvailable( try { resultWithData = doRunPowerwaf(reqCtx, newData, ctxAndAddr, gwCtx); } catch (TimeoutPowerwafException tpe) { - reqCtx.increaseTimeouts(); - WafMetricCollector.get().wafRequestTimeout(); - log.debug(LogCollector.EXCLUDE_TELEMETRY, "Timeout calling the WAF", tpe); if (gwCtx.isRasp) { + reqCtx.increaseRaspTimeouts(); WafMetricCollector.get().raspTimeout(gwCtx.raspRuleType); + } else { + reqCtx.increaseWafTimeouts(); + WafMetricCollector.get().wafRequestTimeout(); + log.debug(LogCollector.EXCLUDE_TELEMETRY, "Timeout calling the WAF", tpe); } return; } catch (AbstractPowerwafException e) { diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFStatsReporter.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFStatsReporter.java index e2fdb7de5d5..4d60b1cf3af 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFStatsReporter.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFStatsReporter.java @@ -14,7 +14,8 @@ public class PowerWAFStatsReporter implements TraceSegmentPostProcessor { private static final String RASP_TOTAL_DDWAF_RUN_DURATION_US_TAG = "_dd.appsec.rasp.duration"; private static final String RASP_RULE_EVAL = "_dd.appsec.rasp.rule.eval"; private static final String RULE_FILE_VERSION = "_dd.appsec.event_rules.version"; - public static final String TIMEOUTS_TAG = "_dd.appsec.waf.timeouts"; + public static final String WAF_TIMEOUTS_TAG = "_dd.appsec.waf.timeouts"; + public static final String RASP_TIMEOUT_TAG = "_dd.appsec.rasp.timeout"; // XXX: if config is updated, this may not match the actual version run during this request // However, as of this point, we don't update rules at runtime. @@ -46,8 +47,12 @@ public void processTraceSegment( segment.setTagTop(RULE_FILE_VERSION, rulesVersion); } - if (ctx.getTimeouts() > 0) { - segment.setTagTop(TIMEOUTS_TAG, ctx.getTimeouts()); + if (ctx.getWafTimeouts() > 0) { + segment.setTagTop(WAF_TIMEOUTS_TAG, ctx.getWafTimeouts()); + } + + if (ctx.getRaspTimeouts() > 0) { + segment.setTagTop(RASP_TIMEOUT_TAG, ctx.getRaspTimeouts()); } } } diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/AppSecRequestContextSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/AppSecRequestContextSpecification.groovy index 3ba7eee1971..9552feba11d 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/AppSecRequestContextSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/AppSecRequestContextSpecification.groovy @@ -272,4 +272,22 @@ class AppSecRequestContextSpecification extends DDSpecification { where: postProcessing << [true, false] } + + def "test increase and get WafTimeouts"() { + when: + ctx.increaseWafTimeouts() + ctx.increaseWafTimeouts() + + then: + ctx.getWafTimeouts() == 2 + } + + def "test increase and get RaspTimeouts"() { + when: + ctx.increaseRaspTimeouts() + ctx.increaseRaspTimeouts() + + then: + ctx.getRaspTimeouts() == 2 + } } diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFModuleSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFModuleSpecification.groovy index 11e1c3ad899..efe94056180 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFModuleSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFModuleSpecification.groovy @@ -17,6 +17,7 @@ import com.datadog.appsec.event.data.MapDataBundle import com.datadog.appsec.gateway.AppSecRequestContext import com.datadog.appsec.gateway.GatewayContext import com.datadog.appsec.report.AppSecEvent +import datadog.trace.api.telemetry.RuleType import datadog.trace.util.stacktrace.StackTraceEvent import com.datadog.appsec.test.StubAppSecConfigService import datadog.communication.monitor.Monitoring @@ -959,7 +960,7 @@ class PowerWAFModuleSpecification extends DDSpecification { assert !flow.blocking } - void 'timeout is honored'() { + void 'timeout is honored (waf)'() { setup: injectSysConfig('appsec.waf.timeout', '1') PowerWAFModule.createLimitsObject() @@ -981,8 +982,13 @@ class PowerWAFModuleSpecification extends DDSpecification { ctx.getOrCreateAdditive(_, true) >> { pwafAdditive = it[0].openAdditive() } assert !flow.blocking - 1 * ctx.increaseTimeouts() + 1 * ctx.isAdditiveClosed() + 1 * ctx.getOrCreateAdditive(_, true, false) >> { + pwafAdditive = it[0].openAdditive() } + 1 * ctx.getWafMetrics() + 1 * ctx.increaseWafTimeouts() 1 * mockWafMetricCollector.get().wafRequestTimeout() + 0 * _ when: pp.processTraceSegment(segment, ctx, []) @@ -996,6 +1002,53 @@ class PowerWAFModuleSpecification extends DDSpecification { PowerWAFModule.createLimitsObject() } + void 'timeout is honored (rasp)'() { + setup: + injectSysConfig('appsec.waf.timeout', '1') + PowerWAFModule.createLimitsObject() + setupWithStubConfigService() + DataBundle db = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES, + new CaseInsensitiveMap>(['user-agent': 'Arachni/v' + ('a' * 4000)])) + ChangeableFlow flow = new ChangeableFlow() + + TraceSegment segment = Mock() + TraceSegmentPostProcessor pp = service.traceSegmentPostProcessors.last() + + def mockWafMetricCollector = Mock(WafMetricCollector) + WafMetricCollector.INSTANCE = mockWafMetricCollector + + gwCtx = new GatewayContext(false, RuleType.SQL_INJECTION) + + when: + dataListener.onDataAvailable(flow, ctx, db, gwCtx) + + then: + ctx.getOrCreateAdditive(_, true) >> { + pwafAdditive = it[0].openAdditive() } + assert !flow.blocking + 1 * ctx.isAdditiveClosed() + 1 * ctx.getOrCreateAdditive(_, true, true) >> { + pwafAdditive = it[0].openAdditive() } + 1 * ctx.getRaspMetrics() + 1 * ctx.getRaspMetricsCounter() + 1 * ctx.increaseRaspTimeouts() + 1 * mockWafMetricCollector.get().raspTimeout(gwCtx.raspRuleType) + 1 * mockWafMetricCollector.raspRuleEval(RuleType.SQL_INJECTION) + 0 * _ + + when: + pp.processTraceSegment(segment, ctx, []) + + then: + 1 * segment.setTagTop('_dd.appsec.rasp.timeout', 1L) + _ * segment.setTagTop(_, _) + + cleanup: + injectSysConfig('appsec.waf.timeout', ConfigDefaults.DEFAULT_APPSEC_WAF_TIMEOUT as String) + PowerWAFModule.createLimitsObject() + gwCtx = new GatewayContext(false) + } + void 'configuration can be given later'() { def cfgService = new StubAppSecConfigService([waf: null]) AppSecModuleConfigurer.Reconfiguration reconf = Mock() @@ -1112,7 +1165,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> { pwafAdditive.close() } - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 0 * _ when: 'removing data and override config' @@ -1136,7 +1190,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} 1 * reconf.reloadSubscriptions() - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 0 * _ when: 'data is readded' @@ -1162,7 +1217,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.closeAdditive() >> {pwafAdditive.close()} 1 * flow.isBlocking() 1 * ctx.isThrottled(null) - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 0 * _ when: 'toggling the rule off' @@ -1184,7 +1240,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 0 * _ } @@ -1214,7 +1271,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 0 * _ when: 'rule enabled in config a has no effect' @@ -1238,7 +1296,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 0 * _ when: 'rule enabled in config c overrides b' @@ -1266,7 +1325,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.reportEvents(_ as Collection) 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 1 * ctx.isThrottled(null) 0 * _ @@ -1289,7 +1349,8 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() - _ * ctx.increaseTimeouts() + _ * ctx.increaseWafTimeouts() + _ * ctx.increaseRaspTimeouts() 0 * _ } diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFStatsReporterSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFStatsReporterSpecification.groovy index 4a96ec9c6d0..4def8e88d83 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFStatsReporterSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFStatsReporterSpecification.groovy @@ -18,6 +18,10 @@ class PowerWAFStatsReporterSpecification extends DDSpecification { metrics.totalDdwafRunTimeNs = 1_000 TraceSegment segment = Mock() reporter.rulesVersion = '1.2.3' + def wafTimeouts = 1 + + and: + ctx.getWafTimeouts() >> wafTimeouts when: reporter.processTraceSegment(segment, ctx, []) @@ -27,6 +31,7 @@ class PowerWAFStatsReporterSpecification extends DDSpecification { 1 * segment.setTagTop('_dd.appsec.waf.duration', 1) 1 * segment.setTagTop('_dd.appsec.waf.duration_ext', 2) 1 * segment.setTagTop('_dd.appsec.event_rules.version', '1.2.3') + 1 * segment.setTagTop('_dd.appsec.waf.timeouts', wafTimeouts) } void 'reporter reports rasp timings and version'() { @@ -40,6 +45,10 @@ class PowerWAFStatsReporterSpecification extends DDSpecification { raspMetrics.totalDdwafRunTimeNs = 3_000 TraceSegment segment = Mock() reporter.rulesVersion = '1.2.3' + def raspTimeouts = 1 + + and: + ctx.getRaspTimeouts() >> raspTimeouts when: reporter.processTraceSegment(segment, ctx, []) @@ -52,6 +61,7 @@ class PowerWAFStatsReporterSpecification extends DDSpecification { 1 * segment.setTagTop('_dd.appsec.rasp.duration_ext', 4) 1 * segment.setTagTop('_dd.appsec.rasp.rule.eval', 5) 1 * segment.setTagTop('_dd.appsec.event_rules.version', '1.2.3') + 1 * segment.setTagTop('_dd.appsec.rasp.timeout', raspTimeouts) } void 'reports nothing if metrics are null'() {