diff --git a/.circleci/config.continue.yml.j2 b/.circleci/config.continue.yml.j2 index 5c1729ddcac..0e6fee848ba 100644 --- a/.circleci/config.continue.yml.j2 +++ b/.circleci/config.continue.yml.j2 @@ -36,7 +36,7 @@ instrumentation_modules: &instrumentation_modules "dd-java-agent/instrumentation debugger_modules: &debugger_modules "dd-java-agent/agent-debugger|dd-java-agent/agent-bootstrap|dd-java-agent/agent-builder|internal-api|communication|dd-trace-core" profiling_modules: &profiling_modules "dd-java-agent/agent-profiling" -default_system_tests_commit: &default_system_tests_commit 315bc8a32cc888834726397f088336ba8038277f +default_system_tests_commit: &default_system_tests_commit d4974a9d88a10a70a8688afd08697affb5e82261 parameters: nightly: diff --git a/dd-java-agent/appsec/build.gradle b/dd-java-agent/appsec/build.gradle index f501d453cec..1d99176ee93 100644 --- a/dd-java-agent/appsec/build.gradle +++ b/dd-java-agent/appsec/build.gradle @@ -15,7 +15,7 @@ dependencies { implementation project(':internal-api') implementation project(':communication') implementation project(':telemetry') - implementation group: 'io.sqreen', name: 'libsqreen', version: '11.2.0' + implementation group: 'io.sqreen', name: 'libsqreen', version: '12.0.0' implementation libs.moshi testImplementation libs.bytebuddy @@ -68,6 +68,7 @@ ext { excludedClassesCoverage = [ 'com.datadog.appsec.config.MergedAsmData.InvalidAsmDataException', 'com.datadog.appsec.powerwaf.LibSqreenInitialization', + 'com.datadog.appsec.powerwaf.PowerWAFModule.PowerWAFDataCallback', 'com.datadog.appsec.report.*', 'com.datadog.appsec.config.AppSecConfigServiceImpl.SubscribeFleetServiceRunnable.1', 'com.datadog.appsec.util.StandardizedLogging', @@ -87,7 +88,6 @@ ext { 'com.datadog.appsec.config.CurrentAppSecConfig', // equals() / hashCode() are not well covered 'com.datadog.appsec.config.AppSecConfig.Helper', - 'com.datadog.appsec.powerwaf.PowerWAFModule.PowerWAFDataCallback', 'com.datadog.appsec.powerwaf.PowerWAFModule.PowerWAFEventsCallback', // assert never fails 'com.datadog.appsec.util.StandardizedLogging', 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 23a05c3aa22..e0f75569274 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 @@ -30,6 +30,7 @@ import datadog.trace.api.gateway.Flow; import datadog.trace.api.telemetry.LogCollector; import datadog.trace.api.telemetry.WafMetricCollector; +import datadog.trace.api.telemetry.WafTruncatedType; import datadog.trace.api.time.SystemTimeSource; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; @@ -445,6 +446,7 @@ public void onDataAvailable( if (!reqCtx.isAdditiveClosed()) { log.error("Error calling WAF", e); } + WafMetricCollector.get().wafRequestError(); return; } catch (AbstractPowerwafException e) { if (gwCtx.isRasp) { @@ -460,6 +462,27 @@ public void onDataAvailable( long elapsed = System.currentTimeMillis() - start; StandardizedLogging.finishedExecutionWAF(log, elapsed); } + if (!gwCtx.isRasp) { + PowerwafMetrics wafMetrics = reqCtx.getWafMetrics(); + if (wafMetrics != null) { + final long stringTooLong = wafMetrics.getTruncatedStringTooLongCount(); + final long listMapTooLarge = wafMetrics.getTruncatedListMapTooLargeCount(); + final long objectTooDeep = wafMetrics.getTruncatedObjectTooDeepCount(); + + if (stringTooLong > 0) { + WafMetricCollector.get() + .wafInputTruncated(WafTruncatedType.STRING_TOO_LONG, stringTooLong); + } + if (listMapTooLarge > 0) { + WafMetricCollector.get() + .wafInputTruncated(WafTruncatedType.LIST_MAP_TOO_LARGE, listMapTooLarge); + } + if (objectTooDeep > 0) { + WafMetricCollector.get() + .wafInputTruncated(WafTruncatedType.OBJECT_TOO_DEEP, objectTooDeep); + } + } + } } StandardizedLogging.inAppWafReturn(log, resultWithData); @@ -495,29 +518,35 @@ public void onDataAvailable( } } else { log.info("Ignoring action with type {}", actionInfo.type); + WafMetricCollector.get().wafRequestBlockFailure(); } } Collection events = buildEvents(resultWithData); - if (!events.isEmpty() && !reqCtx.isThrottled(rateLimiter)) { - AgentSpan activeSpan = AgentTracer.get().activeSpan(); - if (activeSpan != null) { - log.debug("Setting force-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); - // If APM is disabled, inform downstream services that the current - // distributed trace contains at least one ASM event and must inherit - // the given force-keep priority - activeSpan - .getLocalRootSpan() - .setTag(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + if (!events.isEmpty()) { + if (!reqCtx.isThrottled(rateLimiter)) { + AgentSpan activeSpan = AgentTracer.get().activeSpan(); + if (activeSpan != null) { + log.debug("Setting force-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); + // If APM is disabled, inform downstream services that the current + // distributed trace contains at least one ASM event and must inherit + // the given force-keep priority + activeSpan + .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 + log.debug("There is no active span available"); + } + reqCtx.reportEvents(events); } else { - // If active span is not available the ASK_KEEP tag will be set in the GatewayBridge - // when the request ends - log.debug("There is no active span available"); + log.debug("Rate limited WAF events"); + WafMetricCollector.get().wafRequestRateLimited(); } - reqCtx.reportEvents(events); } if (flow.isBlocking()) { @@ -551,6 +580,7 @@ private Flow.Action.RequestBlockingAction createBlockRequestAction(ActionInfo ac return new Flow.Action.RequestBlockingAction(statusCode, blockingContentType); } catch (RuntimeException cce) { log.warn("Invalid blocking action data", cce); + WafMetricCollector.get().wafRequestBlockFailure(); return null; } } @@ -576,6 +606,7 @@ private Flow.Action.RequestBlockingAction createRedirectRequestAction(ActionInfo return Flow.Action.RequestBlockingAction.forRedirect(statusCode, location); } catch (RuntimeException cce) { log.warn("Invalid blocking action data", cce); + WafMetricCollector.get().wafRequestBlockFailure(); return null; } } 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 b5f5403bbd1..d6f98339511 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 @@ -37,6 +37,7 @@ import spock.lang.Shared import spock.lang.Unroll import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicLong import static datadog.trace.api.config.AppSecConfig.APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP import static datadog.trace.api.config.AppSecConfig.APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP @@ -221,7 +222,7 @@ class PowerWAFModuleSpecification extends DDSpecification { } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * flow.isBlocking() @@ -256,7 +257,7 @@ class PowerWAFModuleSpecification extends DDSpecification { } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 2 * ctx.closeAdditive() 1 * flow.isBlocking() @@ -299,7 +300,7 @@ class PowerWAFModuleSpecification extends DDSpecification { } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * flow.isBlocking() @@ -324,7 +325,7 @@ class PowerWAFModuleSpecification extends DDSpecification { } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * flow.isBlocking() @@ -382,7 +383,7 @@ class PowerWAFModuleSpecification extends DDSpecification { } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * flow.isBlocking() @@ -401,7 +402,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_ as PowerwafContext, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 0 * _ @@ -463,7 +464,7 @@ class PowerWAFModuleSpecification extends DDSpecification { } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> { pwafAdditive.close() } 1 * ctx.setBlocked() @@ -484,7 +485,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 0 * _ @@ -546,10 +547,9 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 3 * tracer.activeSpan() - // we get two events: one for origin rule, and one for the custom one - 1 * ctx.reportEvents(hasSize(2)) - 1 * ctx.getWafMetrics() + 2 * tracer.activeSpan() + 1 * ctx.reportEvents(hasSize(1)) + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * ctx.setBlocked() @@ -629,7 +629,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_, true, false) >> { it[0].openAdditive() } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * flow.isBlocking() @@ -653,7 +653,7 @@ class PowerWAFModuleSpecification extends DDSpecification { metrics = pwCtx.createMetrics() pwafAdditive } - 1 * ctx.getWafMetrics() >> metrics + 2 * ctx.getWafMetrics() >> metrics 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * ctx.reportEvents(_) @@ -719,7 +719,7 @@ class PowerWAFModuleSpecification extends DDSpecification { metrics = pwCtx.createMetrics() pwafAdditive } - 1 * ctx.getWafMetrics() >> metrics + 2 * ctx.getWafMetrics() >> metrics 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * ctx.reportEvents(_) @@ -746,7 +746,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_, false, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() >> null + 2 * ctx.getWafMetrics() >> null 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * ctx.reportEvents(_) @@ -777,7 +777,14 @@ class PowerWAFModuleSpecification extends DDSpecification { pwafAdditive } 1 * ctx.closeAdditive() - 2 * ctx.getWafMetrics() >> { metrics.with { totalDdwafRunTimeNs = 1000; totalRunTimeNs = 2000; it} } + 3 * ctx.getWafMetrics() >> { + metrics.with { + totalDdwafRunTimeNs = new AtomicLong(1000) + totalRunTimeNs = new AtomicLong(2000) + truncatedStringTooLongCount = new AtomicLong(0) + truncatedListMapTooLargeCount = new AtomicLong(0) + truncatedObjectTooDeepCount = new AtomicLong(0) + it } } 1 * segment.setTagTop('_dd.appsec.waf.duration', 1) 1 * segment.setTagTop('_dd.appsec.waf.duration_ext', 2) @@ -800,7 +807,7 @@ class PowerWAFModuleSpecification extends DDSpecification { metrics = pwCtx.createMetrics() pwafAdditive } - 1 * ctx.getWafMetrics() >> metrics + 2 * ctx.getWafMetrics() >> metrics 1 * ctx.reportEvents(*_) 1 * ctx.setBlocked() 1 * ctx.isThrottled(null) @@ -999,7 +1006,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.isAdditiveClosed() 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.increaseWafTimeouts() 1 * wafMetricCollector.get().wafRequestTimeout() 0 * _ @@ -1083,7 +1090,7 @@ class PowerWAFModuleSpecification extends DDSpecification { pwafAdditive = it[0].openAdditive() } 1 * ctx.reportEvents(_ as Collection) 1 * ctx.isAdditiveClosed() - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isThrottled(null) 1 * ctx.closeAdditive() 2 * tracer.activeSpan() @@ -1126,7 +1133,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * flow.setAction({ it.blocking }) 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() @@ -1183,7 +1190,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * reconf.reloadSubscriptions() 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> { pwafAdditive.close() } _ * ctx.increaseWafTimeouts() @@ -1207,7 +1214,7 @@ class PowerWAFModuleSpecification extends DDSpecification { then: 'no match; data was cleared (though rule is no longer disabled)' 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} 1 * wafMetricCollector.wafUpdates(_, true) @@ -1234,7 +1241,7 @@ class PowerWAFModuleSpecification extends DDSpecification { pwafAdditive = it[0].openAdditive() } 2 * tracer.activeSpan() 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * flow.setAction({ it.blocking }) 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} @@ -1261,7 +1268,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * wafMetricCollector.wafUpdates(_, true) 1 * reconf.reloadSubscriptions() 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() _ * ctx.increaseWafTimeouts() @@ -1293,7 +1300,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * reconf.reloadSubscriptions() // no attack 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} _ * ctx.increaseWafTimeouts() @@ -1319,7 +1326,7 @@ class PowerWAFModuleSpecification extends DDSpecification { // no attack 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() >> {pwafAdditive.close()} _ * ctx.increaseWafTimeouts() @@ -1345,7 +1352,7 @@ class PowerWAFModuleSpecification extends DDSpecification { // attack found 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * flow.isBlocking() 1 * flow.setAction({ it.blocking }) 2 * tracer.activeSpan() @@ -1374,7 +1381,7 @@ class PowerWAFModuleSpecification extends DDSpecification { // no attack 1 * ctx.getOrCreateAdditive(_, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() _ * ctx.increaseWafTimeouts() @@ -1491,7 +1498,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.reportEvents(_ as Collection) >> { it[0].iterator().next().ruleMatches[0].parameters[0].value == '/cybercop' } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * flow.isBlocking() 1 * ctx.isThrottled(null) 1 * ctx.isAdditiveClosed() >> false @@ -1509,7 +1516,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.reportEvents(_ as Collection) >> { it[0].iterator().next().ruleMatches[0].parameters[0].value == 'user-to-block-1' } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isAdditiveClosed() >> false 1 * ctx.closeAdditive() 1 * ctx.isThrottled(null) @@ -1588,7 +1595,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_ as PowerwafContext, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isThrottled(null) 1 * ctx.reportEvents(_ as Collection) 1 * ctx.closeAdditive() @@ -1627,7 +1634,7 @@ class PowerWAFModuleSpecification extends DDSpecification { 1 * ctx.getOrCreateAdditive(_ as PowerwafContext, true, false) >> { pwafAdditive = it[0].openAdditive() } - 1 * ctx.getWafMetrics() + 2 * ctx.getWafMetrics() 1 * ctx.isThrottled(null) 1 * ctx.reportEvents(_ as Collection) 1 * ctx.closeAdditive() 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 4def8e88d83..db75e304e5c 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 @@ -6,6 +6,7 @@ import datadog.trace.test.util.DDSpecification import io.sqreen.powerwaf.PowerwafMetrics import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong class PowerWAFStatsReporterSpecification extends DDSpecification { PowerWAFStatsReporter reporter = new PowerWAFStatsReporter() @@ -14,8 +15,8 @@ class PowerWAFStatsReporterSpecification extends DDSpecification { void 'reporter reports waf timings and version'() { setup: PowerwafMetrics metrics = new PowerwafMetrics() - metrics.totalRunTimeNs = 2_000 - metrics.totalDdwafRunTimeNs = 1_000 + metrics.totalRunTimeNs = new AtomicLong(2_000) + metrics.totalDdwafRunTimeNs = new AtomicLong(1_000) TraceSegment segment = Mock() reporter.rulesVersion = '1.2.3' def wafTimeouts = 1 @@ -37,12 +38,12 @@ class PowerWAFStatsReporterSpecification extends DDSpecification { void 'reporter reports rasp timings and version'() { setup: PowerwafMetrics metrics = new PowerwafMetrics() - metrics.totalRunTimeNs = 2_000 - metrics.totalDdwafRunTimeNs = 1_000 + metrics.totalRunTimeNs = new AtomicLong(2_000) + metrics.totalDdwafRunTimeNs = new AtomicLong(1_000) PowerwafMetrics raspMetrics = new PowerwafMetrics() - raspMetrics.totalRunTimeNs = 4_000 - raspMetrics.totalDdwafRunTimeNs = 3_000 + raspMetrics.totalRunTimeNs = new AtomicLong(4_000) + raspMetrics.totalDdwafRunTimeNs = new AtomicLong(3_000) TraceSegment segment = Mock() reporter.rulesVersion = '1.2.3' def raspTimeouts = 1 diff --git a/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java b/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java index 637cfddbee5..cfabd3d49e5 100644 --- a/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java +++ b/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java @@ -38,6 +38,13 @@ private WafMetricCollector() { private static final AtomicRequestCounter wafTriggeredRequestCounter = new AtomicRequestCounter(); private static final AtomicRequestCounter wafBlockedRequestCounter = new AtomicRequestCounter(); private static final AtomicRequestCounter wafTimeoutRequestCounter = new AtomicRequestCounter(); + private static final AtomicRequestCounter wafErrorRequestCounter = new AtomicRequestCounter(); + private static final AtomicRequestCounter wafRateLimitedRequestCounter = + new AtomicRequestCounter(); + private static final AtomicRequestCounter wafBlockFailureRequestCounter = + new AtomicRequestCounter(); + private static final AtomicLongArray wafInputTruncatedCounter = + new AtomicLongArray(WafTruncatedType.values().length); private static final AtomicLongArray raspRuleEvalCounter = new AtomicLongArray(RuleType.getNumValues()); private static final AtomicLongArray raspRuleMatchCounter = @@ -108,6 +115,22 @@ public void wafRequestTimeout() { wafTimeoutRequestCounter.increment(); } + public void wafRequestError() { + wafErrorRequestCounter.increment(); + } + + public void wafRequestRateLimited() { + wafRateLimitedRequestCounter.increment(); + } + + public void wafRequestBlockFailure() { + wafBlockFailureRequestCounter.increment(); + } + + public void wafInputTruncated(final WafTruncatedType wafTruncatedType, long increment) { + wafInputTruncatedCounter.addAndGet(wafTruncatedType.ordinal(), increment); + } + public void raspRuleEval(final RuleType ruleType) { raspRuleEvalCounter.incrementAndGet(ruleType.ordinal()); } @@ -151,6 +174,16 @@ public Collection drain() { @Override public void prepareMetrics() { + final boolean isRateLimited = wafRateLimitedRequestCounter.getAndReset() > 0; + final boolean isBlockFailure = wafBlockFailureRequestCounter.getAndReset() > 0; + boolean isWafInputTruncated = false; + for (WafTruncatedType wafTruncatedType : WafTruncatedType.values()) { + isWafInputTruncated = wafInputTruncatedCounter.getAndSet(wafTruncatedType.ordinal(), 0) > 0; + if (isWafInputTruncated) { + break; + } + } + // Requests if (wafRequestCounter.get() > 0) { if (!rawMetricsQueue.offer( @@ -160,7 +193,11 @@ public void prepareMetrics() { WafMetricCollector.rulesVersion, false, false, - false))) { + false, + false, + isBlockFailure, + isRateLimited, + isWafInputTruncated))) { return; } } @@ -174,7 +211,11 @@ public void prepareMetrics() { WafMetricCollector.rulesVersion, true, false, - false))) { + false, + false, + isBlockFailure, + isRateLimited, + isWafInputTruncated))) { return; } } @@ -188,7 +229,11 @@ public void prepareMetrics() { WafMetricCollector.rulesVersion, true, true, - false))) { + false, + false, + isBlockFailure, + isRateLimited, + isWafInputTruncated))) { return; } } @@ -202,7 +247,29 @@ public void prepareMetrics() { WafMetricCollector.rulesVersion, false, false, - true))) { + false, + true, + isBlockFailure, + isRateLimited, + isWafInputTruncated))) { + return; + } + } + + // WAF error requests + if (wafErrorRequestCounter.get() > 0) { + if (!rawMetricsQueue.offer( + new WafRequestsRawMetric( + wafErrorRequestCounter.getAndReset(), + WafMetricCollector.wafVersion, + WafMetricCollector.rulesVersion, + false, + false, + true, + false, + isBlockFailure, + isRateLimited, + isWafInputTruncated))) { return; } } @@ -348,7 +415,11 @@ public WafRequestsRawMetric( final String rulesVersion, final boolean triggered, final boolean blocked, - final boolean wafTimeout) { + final boolean wafError, + final boolean wafTimeout, + final boolean blockFailure, + final boolean rateLimited, + final boolean inputTruncated) { super( "waf.requests", counter, @@ -356,7 +427,11 @@ public WafRequestsRawMetric( "event_rules_version:" + rulesVersion, "rule_triggered:" + triggered, "request_blocked:" + blocked, - "waf_timeout:" + wafTimeout); + "waf_error:" + wafError, + "waf_timeout:" + wafTimeout, + "block_failure:" + blockFailure, + "rate_limited:" + rateLimited, + "input_truncated:" + inputTruncated); } } diff --git a/internal-api/src/main/java/datadog/trace/api/telemetry/WafTruncatedType.java b/internal-api/src/main/java/datadog/trace/api/telemetry/WafTruncatedType.java new file mode 100644 index 00000000000..e2051425fac --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/telemetry/WafTruncatedType.java @@ -0,0 +1,17 @@ +package datadog.trace.api.telemetry; + +public enum WafTruncatedType { + STRING_TOO_LONG(1), + LIST_MAP_TOO_LARGE(2), + OBJECT_TOO_DEEP(4); + + private final int value; + + WafTruncatedType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } +} diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy index c3d792968fb..4eb8e7e6f03 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy @@ -30,6 +30,10 @@ class WafMetricCollectorTest extends DDSpecification { WafMetricCollector.get().wafRequestTriggered() WafMetricCollector.get().wafRequestBlocked() WafMetricCollector.get().wafRequestTimeout() + WafMetricCollector.get().wafRequestError() + WafMetricCollector.get().wafRequestRateLimited() + WafMetricCollector.get().wafRequestBlockFailure() + WafMetricCollector.get().wafInputTruncated(WafTruncatedType.STRING_TOO_LONG, 5) WafMetricCollector.get().raspRuleEval(RuleType.SQL_INJECTION) WafMetricCollector.get().raspRuleEval(RuleType.SQL_INJECTION) WafMetricCollector.get().raspRuleMatch(RuleType.SQL_INJECTION) @@ -76,7 +80,11 @@ class WafMetricCollectorTest extends DDSpecification { 'event_rules_version:rules.3', 'rule_triggered:false', 'request_blocked:false', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ].toSet() def requestTriggeredMetric = (WafMetricCollector.WafRequestsRawMetric)metrics[4] @@ -88,7 +96,11 @@ class WafMetricCollectorTest extends DDSpecification { 'event_rules_version:rules.3', 'rule_triggered:true', 'request_blocked:false', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ].toSet() @@ -102,7 +114,11 @@ class WafMetricCollectorTest extends DDSpecification { 'event_rules_version:rules.3', 'rule_triggered:true', 'request_blocked:true', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ].toSet() def requestTimeoutMetric = (WafMetricCollector.WafRequestsRawMetric)metrics[6] @@ -115,31 +131,52 @@ class WafMetricCollectorTest extends DDSpecification { 'event_rules_version:rules.3', 'rule_triggered:false', 'request_blocked:false', - 'waf_timeout:true' + 'waf_error:false', + 'waf_timeout:true', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ].toSet() - def raspRuleEvalSqli = (WafMetricCollector.RaspRuleEval)metrics[7] + def requestWafErrorMetric = (WafMetricCollector.WafRequestsRawMetric)metrics[7] + requestWafErrorMetric.namespace == 'appsec' + requestWafErrorMetric.metricName == 'waf.requests' + requestWafErrorMetric.type == 'count' + requestWafErrorMetric.value == 1 + requestWafErrorMetric.tags.toSet() == [ + 'waf_version:waf_ver1', + 'event_rules_version:rules.3', + 'rule_triggered:false', + 'request_blocked:false', + 'waf_error:true', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', + ].toSet() + + def raspRuleEvalSqli = (WafMetricCollector.RaspRuleEval)metrics[8] raspRuleEvalSqli.type == 'count' raspRuleEvalSqli.value == 3 raspRuleEvalSqli.namespace == 'appsec' raspRuleEvalSqli.metricName == 'rasp.rule.eval' raspRuleEvalSqli.tags.toSet() == ['rule_type:sql_injection', 'waf_version:waf_ver1'].toSet() - def raspRuleMatch = (WafMetricCollector.RaspRuleMatch)metrics[8] + def raspRuleMatch = (WafMetricCollector.RaspRuleMatch)metrics[9] raspRuleMatch.type == 'count' raspRuleMatch.value == 1 raspRuleMatch.namespace == 'appsec' raspRuleMatch.metricName == 'rasp.rule.match' raspRuleMatch.tags.toSet() == ['rule_type:sql_injection', 'waf_version:waf_ver1'].toSet() - def raspTimeout = (WafMetricCollector.RaspTimeout)metrics[9] + def raspTimeout = (WafMetricCollector.RaspTimeout)metrics[10] raspTimeout.type == 'count' raspTimeout.value == 1 raspTimeout.namespace == 'appsec' raspTimeout.metricName == 'rasp.timeout' raspTimeout.tags.toSet() == ['rule_type:sql_injection', 'waf_version:waf_ver1'].toSet() - def raspInvalidCode = (WafMetricCollector.RaspError)metrics[10] + def raspInvalidCode = (WafMetricCollector.RaspError)metrics[11] raspInvalidCode.type == 'count' raspInvalidCode.value == 1 raspInvalidCode.namespace == 'appsec' @@ -152,7 +189,7 @@ class WafMetricCollectorTest extends DDSpecification { 'waf_error:' + DD_WAF_RUN_INTERNAL_ERROR ].toSet() - def wafInvalidCode = (WafMetricCollector.WafError)metrics[11] + def wafInvalidCode = (WafMetricCollector.WafError)metrics[12] wafInvalidCode.type == 'count' wafInvalidCode.value == 1 wafInvalidCode.namespace == 'appsec' @@ -165,7 +202,7 @@ class WafMetricCollectorTest extends DDSpecification { 'waf_error:' +DD_WAF_RUN_INTERNAL_ERROR ].toSet() - def raspInvalidObjectCode = (WafMetricCollector.RaspError)metrics[12] + def raspInvalidObjectCode = (WafMetricCollector.RaspError)metrics[13] raspInvalidObjectCode.type == 'count' raspInvalidObjectCode.value == 1 raspInvalidObjectCode.namespace == 'appsec' @@ -177,7 +214,7 @@ class WafMetricCollectorTest extends DDSpecification { ] .toSet() - def wafInvalidObjectCode = (WafMetricCollector.WafError)metrics[13] + def wafInvalidObjectCode = (WafMetricCollector.WafError)metrics[14] wafInvalidObjectCode.type == 'count' wafInvalidObjectCode.value == 1 wafInvalidObjectCode.namespace == 'appsec' diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy index 329dcb18c14..d4cc604fef9 100644 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy +++ b/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy @@ -2,6 +2,7 @@ package datadog.telemetry.metric import datadog.telemetry.TelemetryService import datadog.telemetry.api.Metric +import datadog.trace.api.telemetry.WafTruncatedType import datadog.trace.api.telemetry.WafMetricCollector import datadog.trace.test.util.DDSpecification @@ -49,6 +50,10 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { WafMetricCollector.get().wafRequestBlocked() WafMetricCollector.get().wafRequest() WafMetricCollector.get().wafRequestTimeout() + WafMetricCollector.get().wafRequestError() + WafMetricCollector.get().wafRequestRateLimited() + WafMetricCollector.get().wafRequestBlockFailure() + WafMetricCollector.get().wafInputTruncated(WafTruncatedType.STRING_TOO_LONG, 5) WafMetricCollector.get().prepareMetrics() periodicAction.doIteration(telemetryService) @@ -66,7 +71,11 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_1', 'rule_triggered:false', 'request_blocked:false', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -78,7 +87,11 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_1', 'rule_triggered:true', 'request_blocked:false', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -90,7 +103,11 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_1', 'rule_triggered:true', 'request_blocked:true', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -102,7 +119,27 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_1', 'rule_triggered:false', 'request_blocked:false', - 'waf_timeout:true' + 'waf_error:false', + 'waf_timeout:true', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', + ] + } ) + 1 * telemetryService.addMetric( { Metric metric -> + metric.namespace == 'appsec' && + metric.metric == 'waf.requests' && + metric.points[0][1] == 1 && + metric.tags == [ + 'waf_version:0.0.0', + 'event_rules_version:rules_ver_1', + 'rule_triggered:false', + 'request_blocked:false', + 'waf_error:true', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 0 * _._ @@ -113,6 +150,10 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { WafMetricCollector.get().wafRequestTriggered() WafMetricCollector.get().wafRequestBlocked() WafMetricCollector.get().wafRequestTimeout() + WafMetricCollector.get().wafRequestError() + WafMetricCollector.get().wafRequestRateLimited() + WafMetricCollector.get().wafRequestBlockFailure() + WafMetricCollector.get().wafInputTruncated(WafTruncatedType.STRING_TOO_LONG, 5) WafMetricCollector.get().prepareMetrics() periodicAction.doIteration(telemetryService) @@ -130,7 +171,11 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_2', 'rule_triggered:false', 'request_blocked:false', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -142,7 +187,11 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_2', 'rule_triggered:true', 'request_blocked:false', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -154,7 +203,27 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_2', 'rule_triggered:true', 'request_blocked:true', - 'waf_timeout:false' + 'waf_error:false', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', + ] + } ) + 1 * telemetryService.addMetric( { Metric metric -> + metric.namespace == 'appsec' && + metric.metric == 'waf.requests' && + metric.points[0][1] == 1 && + metric.tags == [ + 'waf_version:0.0.0', + 'event_rules_version:rules_ver_2', + 'rule_triggered:false', + 'request_blocked:false', + 'waf_error:false', + 'waf_timeout:true', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -166,7 +235,11 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'event_rules_version:rules_ver_2', 'rule_triggered:false', 'request_blocked:false', - 'waf_timeout:true' + 'waf_error:true', + 'waf_timeout:false', + 'block_failure:true', + 'rate_limited:true', + 'input_truncated:true', ] } ) 0 * _._