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 f4473aff6f3..290780a5d4f 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 @@ -130,6 +130,9 @@ public class AppSecRequestContext implements DataBundle, Closeable { private volatile int raspInternalErrors; private volatile int raspInvalidObjectErrors; private volatile int raspInvalidArgumentErrors; + private volatile int wafInternalErrors; + private volatile int wafInvalidObjectErrors; + private volatile int wafInvalidArgumentErrors; // keep a reference to the last published usr.id private volatile String userId; @@ -157,6 +160,17 @@ public class AppSecRequestContext implements DataBundle, Closeable { AtomicIntegerFieldUpdater.newUpdater( AppSecRequestContext.class, "raspInvalidArgumentErrors"); + private static final AtomicIntegerFieldUpdater WAF_INTERNAL_ERRORS_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(AppSecRequestContext.class, "wafInternalErrors"); + private static final AtomicIntegerFieldUpdater + WAF_INVALID_OBJECT_ERRORS_UPDATER = + AtomicIntegerFieldUpdater.newUpdater( + AppSecRequestContext.class, "wafInvalidObjectErrors"); + private static final AtomicIntegerFieldUpdater + WAF_INVALID_ARGUMENT_ERRORS_UPDATER = + AtomicIntegerFieldUpdater.newUpdater( + AppSecRequestContext.class, "wafInvalidArgumentErrors"); + // to be called by the Event Dispatcher public void addAll(DataBundle newData) { for (Map.Entry, Object> entry : newData) { @@ -222,6 +236,22 @@ public void increaseRaspErrorCode(int code) { } } + public void increaseWafErrorCode(int code) { + switch (code) { + case DD_WAF_RUN_INTERNAL_ERROR: + WAF_INTERNAL_ERRORS_UPDATER.incrementAndGet(this); + break; + case DD_WAF_RUN_INVALID_OBJECT_ERROR: + WAF_INVALID_OBJECT_ERRORS_UPDATER.incrementAndGet(this); + break; + case DD_WAF_RUN_INVALID_ARGUMENT_ERROR: + WAF_INVALID_ARGUMENT_ERRORS_UPDATER.incrementAndGet(this); + break; + default: + break; + } + } + public int getWafTimeouts() { return wafTimeouts; } @@ -243,6 +273,19 @@ public int getRaspError(int code) { } } + public int getWafError(int code) { + switch (code) { + case DD_WAF_RUN_INTERNAL_ERROR: + return wafInternalErrors; + case DD_WAF_RUN_INVALID_OBJECT_ERROR: + return wafInvalidObjectErrors; + case DD_WAF_RUN_INVALID_ARGUMENT_ERROR: + return wafInvalidArgumentErrors; + default: + return 0; + } + } + public Additive getOrCreateAdditive(PowerwafContext ctx, boolean createMetrics, boolean isRasp) { if (createMetrics) { 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 233ba236756..23a05c3aa22 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 @@ -451,9 +451,8 @@ public void onDataAvailable( reqCtx.increaseRaspErrorCode(e.code); WafMetricCollector.get().raspErrorCode(gwCtx.raspRuleType, e.code); } else { - // TODO APPSEC-56703 - // reqCtx.increaseWafErrorCode(e.code); - // WafMetricCollector.get().wafErrorCode(e.code); + reqCtx.increaseWafErrorCode(e.code); + WafMetricCollector.get().wafErrorCode(gwCtx.raspRuleType, e.code); } return; } finally { 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 bec280f1f20..163445320c5 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 @@ -303,4 +303,17 @@ class AppSecRequestContextSpecification extends DDSpecification { ctx.getRaspError(AppSecRequestContext.DD_WAF_RUN_INVALID_ARGUMENT_ERROR) == 0 ctx.getRaspError(0) == 0 } + + def "test increase and get WafErrors"() { + when: + ctx.increaseWafErrorCode(AppSecRequestContext.DD_WAF_RUN_INTERNAL_ERROR) + ctx.increaseWafErrorCode(AppSecRequestContext.DD_WAF_RUN_INTERNAL_ERROR) + ctx.increaseWafErrorCode(AppSecRequestContext.DD_WAF_RUN_INVALID_OBJECT_ERROR) + + then: + ctx.getWafError(AppSecRequestContext.DD_WAF_RUN_INTERNAL_ERROR) == 2 + ctx.getWafError(AppSecRequestContext.DD_WAF_RUN_INVALID_OBJECT_ERROR) == 1 + ctx.getWafError(AppSecRequestContext.DD_WAF_RUN_INVALID_ARGUMENT_ERROR) == 0 + ctx.getWafError(0) == 0 + } } 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 b0e19dc9264..637cfddbee5 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 @@ -46,10 +46,13 @@ private WafMetricCollector() { new AtomicLongArray(RuleType.getNumValues()); private static final ConcurrentMap raspErrorCodeCounter = new ConcurrentSkipListMap<>(); + private static final ConcurrentMap wafErrorCodeCounter = + new ConcurrentSkipListMap<>(); static { for (int i = -1 * ABSTRACT_POWERWAF_EXCEPTION_NUMBER; i < 0; i++) { raspErrorCodeCounter.put(i, new AtomicLongArray(RuleType.getNumValues())); + wafErrorCodeCounter.put(i, new AtomicLongArray(RuleType.getNumValues())); } } @@ -121,6 +124,10 @@ public void raspErrorCode(final RuleType ruleType, final int ddwafRunErrorCode) raspErrorCodeCounter.get(ddwafRunErrorCode).incrementAndGet(ruleType.ordinal()); } + public void wafErrorCode(final RuleType ruleType, final int ddwafRunErrorCode) { + wafErrorCodeCounter.get(ddwafRunErrorCode).incrementAndGet(ruleType.ordinal()); + } + public void missingUserLogin(final LoginFramework framework, final LoginEvent eventType) { missingUserLoginQueue.incrementAndGet( framework.ordinal() * LoginEvent.getNumValues() + eventType.ordinal()); @@ -242,6 +249,10 @@ public void prepareMetrics() { new RaspError(counter, ruleType, WafMetricCollector.wafVersion, i))) { return; } + if (!rawMetricsQueue.offer( + new WafError(counter, ruleType, WafMetricCollector.wafVersion, i))) { + return; + } } } } @@ -422,6 +433,31 @@ public RaspError( } } + public static class WafError extends WafMetric { + public WafError( + final long counter, + final RuleType ruleType, + final String wafVersion, + final Integer ddwafRunError) { + super( + "waf.error", + counter, + ruleType.variant != null + ? new String[] { + "rule_type:" + ruleType.type, + "rule_variant:" + ruleType.variant, + "waf_version:" + wafVersion, + "event_rules_version:" + rulesVersion, + "waf_error:" + ddwafRunError + } + : new String[] { + "rule_type:" + ruleType.type, + "waf_version:" + wafVersion, + "waf_error:" + ddwafRunError + }); + } + } + public static class AtomicRequestCounter { private final AtomicLong atomicLong = new AtomicLong(); 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 bb83fd9387b..c3d792968fb 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 @@ -36,7 +36,9 @@ class WafMetricCollectorTest extends DDSpecification { WafMetricCollector.get().raspRuleEval(RuleType.SQL_INJECTION) WafMetricCollector.get().raspTimeout(RuleType.SQL_INJECTION) WafMetricCollector.get().raspErrorCode(RuleType.SHELL_INJECTION, DD_WAF_RUN_INTERNAL_ERROR) + 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().prepareMetrics() @@ -150,7 +152,20 @@ class WafMetricCollectorTest extends DDSpecification { 'waf_error:' + DD_WAF_RUN_INTERNAL_ERROR ].toSet() - def raspInvalidObjectCode = (WafMetricCollector.RaspError)metrics[11] + def wafInvalidCode = (WafMetricCollector.WafError)metrics[11] + wafInvalidCode.type == 'count' + wafInvalidCode.value == 1 + wafInvalidCode.namespace == 'appsec' + wafInvalidCode.metricName == 'waf.error' + wafInvalidCode.tags.toSet() == [ + 'waf_version:waf_ver1', + 'rule_type:command_injection', + 'rule_variant:shell', + 'event_rules_version:rules.3', + 'waf_error:' +DD_WAF_RUN_INTERNAL_ERROR + ].toSet() + + def raspInvalidObjectCode = (WafMetricCollector.RaspError)metrics[12] raspInvalidObjectCode.type == 'count' raspInvalidObjectCode.value == 1 raspInvalidObjectCode.namespace == 'appsec' @@ -161,6 +176,17 @@ class WafMetricCollectorTest extends DDSpecification { 'waf_error:' + DD_WAF_RUN_INVALID_OBJECT_ERROR ] .toSet() + + def wafInvalidObjectCode = (WafMetricCollector.WafError)metrics[13] + wafInvalidObjectCode.type == 'count' + wafInvalidObjectCode.value == 1 + wafInvalidObjectCode.namespace == 'appsec' + wafInvalidObjectCode.metricName == 'waf.error' + wafInvalidObjectCode.tags.toSet() == [ + 'rule_type:sql_injection', + 'waf_version:waf_ver1', + 'waf_error:'+DD_WAF_RUN_INVALID_OBJECT_ERROR + ].toSet() } def "overflowing WafMetricCollector does not crash"() { @@ -335,6 +361,7 @@ class WafMetricCollectorTest extends DDSpecification { WafMetricCollector.get().raspRuleEval(ruleType) WafMetricCollector.get().raspTimeout(ruleType) WafMetricCollector.get().raspErrorCode(ruleType, DD_WAF_RUN_INTERNAL_ERROR) + WafMetricCollector.get().wafErrorCode(ruleType, DD_WAF_RUN_INTERNAL_ERROR) WafMetricCollector.get().prepareMetrics() then: @@ -389,6 +416,19 @@ class WafMetricCollectorTest extends DDSpecification { 'waf_error:' + DD_WAF_RUN_INTERNAL_ERROR ].toSet() + def wafInvalidCode = (WafMetricCollector.WafError)metrics[5] + wafInvalidCode.type == 'count' + wafInvalidCode.value == 1 + wafInvalidCode.namespace == 'appsec' + wafInvalidCode.metricName == 'waf.error' + wafInvalidCode.tags.toSet() == [ + 'waf_version:waf_ver1', + 'rule_type:command_injection', + 'rule_variant:' + ruleType.variant, + 'event_rules_version:rules.1', + 'waf_error:' + DD_WAF_RUN_INTERNAL_ERROR + ].toSet() + where: ruleType << [RuleType.COMMAND_INJECTION, RuleType.SHELL_INJECTION] }