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 e0f75569274..965c6c5c00b 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 @@ -417,6 +417,9 @@ public void onDataAvailable( if (reqCtx.isAdditiveClosed()) { log.debug("Skipped; the WAF context is closed"); + if (gwCtx.isRasp) { + WafMetricCollector.get().raspRuleSkipped(gwCtx.raspRuleType); + } return; } 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 d6f98339511..939f4194d5f 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 @@ -1731,6 +1731,30 @@ class PowerWAFModuleSpecification extends DDSpecification { 0 * _ } + void 'raspRuleSkipped if rasp available and WAF context is closed'() { + setup: + ChangeableFlow flow = Mock() + GatewayContext gwCtxMock = new GatewayContext(false, RuleType.SQL_INJECTION) + + when: + setupWithStubConfigService('rules_with_data_config.json') + dataListener = pwafModule.dataSubscriptions.first() + + def bundle = MapDataBundle.of( + KnownAddresses.USER_ID, + 'legit-user' + ) + ctx.closeAdditive() + dataListener.onDataAvailable(flow, ctx, bundle, gwCtxMock) + + then: + 1 * ctx.closeAdditive() + 1 * ctx.isAdditiveClosed() >> true + 1 * wafMetricCollector.wafInit(Powerwaf.LIB_VERSION, _, true) + 1 * wafMetricCollector.raspRuleSkipped(RuleType.SQL_INJECTION) + 0 * _ + } + private Map getDefaultConfig() { def service = new StubAppSecConfigService() service.init() 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 cfabd3d49e5..91505599200 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 @@ -47,6 +47,8 @@ private WafMetricCollector() { new AtomicLongArray(WafTruncatedType.values().length); private static final AtomicLongArray raspRuleEvalCounter = new AtomicLongArray(RuleType.getNumValues()); + private static final AtomicLongArray raspRuleSkippedCounter = + new AtomicLongArray(RuleType.getNumValues()); private static final AtomicLongArray raspRuleMatchCounter = new AtomicLongArray(RuleType.getNumValues()); private static final AtomicLongArray raspTimeoutCounter = @@ -135,6 +137,10 @@ public void raspRuleEval(final RuleType ruleType) { raspRuleEvalCounter.incrementAndGet(ruleType.ordinal()); } + public void raspRuleSkipped(final RuleType ruleType) { + raspRuleSkippedCounter.incrementAndGet(ruleType.ordinal()); + } + public void raspRuleMatch(final RuleType ruleType) { raspRuleMatchCounter.incrementAndGet(ruleType.ordinal()); } @@ -347,6 +353,16 @@ public void prepareMetrics() { } } } + + // RASP rule skipped per rule type for after-request reason + for (RuleType ruleType : RuleType.values()) { + long counter = raspRuleSkippedCounter.getAndSet(ruleType.ordinal(), 0); + if (counter > 0) { + if (!rawMetricsQueue.offer(new AfterRequestRaspRuleSkipped(counter, ruleType))) { + return; + } + } + } } public abstract static class WafMetric extends MetricCollector.Metric { @@ -451,6 +467,22 @@ public RaspRuleEval(final long counter, final RuleType ruleType, final String wa } } + // Although rasp.rule.skipped reason could be before-request, there is no real case scenario + public static class AfterRequestRaspRuleSkipped extends WafMetric { + public AfterRequestRaspRuleSkipped(final long counter, final RuleType ruleType) { + super( + "rasp.rule.skipped", + counter, + ruleType.variant != null + ? new String[] { + "rule_type:" + ruleType.type, + "rule_variant:" + ruleType.variant, + "reason:" + "after-request" + } + : new String[] {"rule_type:" + ruleType.type, "reason:" + "after-request"}); + } + } + public static class RaspRuleMatch extends WafMetric { public RaspRuleMatch(final long counter, final RuleType ruleType, final String wafVersion) { super( 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 4eb8e7e6f03..cba2f0c63ea 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 @@ -43,6 +43,7 @@ class WafMetricCollectorTest extends DDSpecification { WafMetricCollector.get().wafErrorCode(RuleType.SHELL_INJECTION, DD_WAF_RUN_INTERNAL_ERROR) WafMetricCollector.get().raspErrorCode(RuleType.SQL_INJECTION, DD_WAF_RUN_INVALID_OBJECT_ERROR) WafMetricCollector.get().wafErrorCode(RuleType.SQL_INJECTION, DD_WAF_RUN_INVALID_OBJECT_ERROR) + WafMetricCollector.get().raspRuleSkipped(RuleType.SQL_INJECTION) WafMetricCollector.get().prepareMetrics() @@ -224,6 +225,13 @@ class WafMetricCollectorTest extends DDSpecification { 'waf_version:waf_ver1', 'waf_error:'+DD_WAF_RUN_INVALID_OBJECT_ERROR ].toSet() + + def raspRuleSkipped = (WafMetricCollector.AfterRequestRaspRuleSkipped)metrics[15] + raspRuleSkipped.type == 'count' + raspRuleSkipped.value == 1 + raspRuleSkipped.namespace == 'appsec' + raspRuleSkipped.metricName == 'rasp.rule.skipped' + raspRuleSkipped.tags.toSet() == ['rule_type:sql_injection', 'reason:after-request',].toSet() } def "overflowing WafMetricCollector does not crash"() { @@ -399,6 +407,7 @@ class WafMetricCollectorTest extends DDSpecification { WafMetricCollector.get().raspTimeout(ruleType) WafMetricCollector.get().raspErrorCode(ruleType, DD_WAF_RUN_INTERNAL_ERROR) WafMetricCollector.get().wafErrorCode(ruleType, DD_WAF_RUN_INTERNAL_ERROR) + WafMetricCollector.get().raspRuleSkipped(ruleType) WafMetricCollector.get().prepareMetrics() then: @@ -466,6 +475,17 @@ class WafMetricCollectorTest extends DDSpecification { 'waf_error:' + DD_WAF_RUN_INTERNAL_ERROR ].toSet() + def raspRuleSkipped = (WafMetricCollector.AfterRequestRaspRuleSkipped)metrics[6] + raspRuleSkipped.type == 'count' + raspRuleSkipped.value == 1 + raspRuleSkipped.namespace == 'appsec' + raspRuleSkipped.metricName == 'rasp.rule.skipped' + raspRuleSkipped.tags.toSet() == [ + 'rule_type:command_injection', + 'rule_variant:'+ruleType.variant, + 'reason:after-request', + ].toSet() + where: ruleType << [RuleType.COMMAND_INJECTION, RuleType.SHELL_INJECTION] }