Skip to content

Commit a47c3ce

Browse files
Create metric: appsec.rasp.error (#8364)
* Create metric: appsec.rasp.error
1 parent d8dc1f2 commit a47c3ce

File tree

5 files changed

+171
-1
lines changed

5 files changed

+171
-1
lines changed

dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java

+47
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ public class AppSecRequestContext implements DataBundle, Closeable {
7777
public static final Set<String> RESPONSE_HEADERS_ALLOW_LIST =
7878
new TreeSet<>(
7979
Arrays.asList("content-length", "content-type", "content-encoding", "content-language"));
80+
public static final int DD_WAF_RUN_INTERNAL_ERROR = -3;
81+
public static final int DD_WAF_RUN_INVALID_OBJECT_ERROR = -2;
82+
public static final int DD_WAF_RUN_INVALID_ARGUMENT_ERROR = -1;
8083

8184
static {
8285
REQUEST_HEADERS_ALLOW_LIST.addAll(DEFAULT_REQUEST_HEADERS_ALLOW_LIST);
@@ -124,6 +127,9 @@ public class AppSecRequestContext implements DataBundle, Closeable {
124127
private volatile boolean blocked;
125128
private volatile int wafTimeouts;
126129
private volatile int raspTimeouts;
130+
private volatile int raspInternalErrors;
131+
private volatile int raspInvalidObjectErrors;
132+
private volatile int raspInvalidArgumentErrors;
127133

128134
// keep a reference to the last published usr.id
129135
private volatile String userId;
@@ -139,6 +145,18 @@ public class AppSecRequestContext implements DataBundle, Closeable {
139145
private static final AtomicIntegerFieldUpdater<AppSecRequestContext> RASP_TIMEOUTS_UPDATER =
140146
AtomicIntegerFieldUpdater.newUpdater(AppSecRequestContext.class, "raspTimeouts");
141147

148+
private static final AtomicIntegerFieldUpdater<AppSecRequestContext>
149+
RASP_INTERNAL_ERRORS_UPDATER =
150+
AtomicIntegerFieldUpdater.newUpdater(AppSecRequestContext.class, "raspInternalErrors");
151+
private static final AtomicIntegerFieldUpdater<AppSecRequestContext>
152+
RASP_INVALID_OBJECT_ERRORS_UPDATER =
153+
AtomicIntegerFieldUpdater.newUpdater(
154+
AppSecRequestContext.class, "raspInvalidObjectErrors");
155+
private static final AtomicIntegerFieldUpdater<AppSecRequestContext>
156+
RASP_INVALID_ARGUMENT_ERRORS_UPDATER =
157+
AtomicIntegerFieldUpdater.newUpdater(
158+
AppSecRequestContext.class, "raspInvalidArgumentErrors");
159+
142160
// to be called by the Event Dispatcher
143161
public void addAll(DataBundle newData) {
144162
for (Map.Entry<Address<?>, Object> entry : newData) {
@@ -188,6 +206,22 @@ public void increaseRaspTimeouts() {
188206
RASP_TIMEOUTS_UPDATER.incrementAndGet(this);
189207
}
190208

209+
public void increaseRaspErrorCode(int code) {
210+
switch (code) {
211+
case DD_WAF_RUN_INTERNAL_ERROR:
212+
RASP_INTERNAL_ERRORS_UPDATER.incrementAndGet(this);
213+
break;
214+
case DD_WAF_RUN_INVALID_OBJECT_ERROR:
215+
RASP_INVALID_OBJECT_ERRORS_UPDATER.incrementAndGet(this);
216+
break;
217+
case DD_WAF_RUN_INVALID_ARGUMENT_ERROR:
218+
RASP_INVALID_ARGUMENT_ERRORS_UPDATER.incrementAndGet(this);
219+
break;
220+
default:
221+
break;
222+
}
223+
}
224+
191225
public int getWafTimeouts() {
192226
return wafTimeouts;
193227
}
@@ -196,6 +230,19 @@ public int getRaspTimeouts() {
196230
return raspTimeouts;
197231
}
198232

233+
public int getRaspError(int code) {
234+
switch (code) {
235+
case DD_WAF_RUN_INTERNAL_ERROR:
236+
return raspInternalErrors;
237+
case DD_WAF_RUN_INVALID_OBJECT_ERROR:
238+
return raspInvalidObjectErrors;
239+
case DD_WAF_RUN_INVALID_ARGUMENT_ERROR:
240+
return raspInvalidArgumentErrors;
241+
default:
242+
return 0;
243+
}
244+
}
245+
199246
public Additive getOrCreateAdditive(PowerwafContext ctx, boolean createMetrics, boolean isRasp) {
200247

201248
if (createMetrics) {

dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFModule.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import io.sqreen.powerwaf.exception.AbstractPowerwafException;
4848
import io.sqreen.powerwaf.exception.InvalidRuleSetException;
4949
import io.sqreen.powerwaf.exception.TimeoutPowerwafException;
50+
import io.sqreen.powerwaf.exception.UnclassifiedPowerwafException;
5051
import java.io.IOException;
5152
import java.lang.reflect.Constructor;
5253
import java.lang.reflect.InvocationHandler;
@@ -440,11 +441,21 @@ public void onDataAvailable(
440441
log.debug(LogCollector.EXCLUDE_TELEMETRY, "Timeout calling the WAF", tpe);
441442
}
442443
return;
443-
} catch (AbstractPowerwafException e) {
444+
} catch (UnclassifiedPowerwafException e) {
444445
if (!reqCtx.isAdditiveClosed()) {
445446
log.error("Error calling WAF", e);
446447
}
447448
return;
449+
} catch (AbstractPowerwafException e) {
450+
if (gwCtx.isRasp) {
451+
reqCtx.increaseRaspErrorCode(e.code);
452+
WafMetricCollector.get().raspErrorCode(gwCtx.raspRuleType, e.code);
453+
} else {
454+
// TODO APPSEC-56703
455+
// reqCtx.increaseWafErrorCode(e.code);
456+
// WafMetricCollector.get().wafErrorCode(e.code);
457+
}
458+
return;
448459
} finally {
449460
if (log.isDebugEnabled()) {
450461
long elapsed = System.currentTimeMillis() - start;

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/AppSecRequestContextSpecification.groovy

+13
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,17 @@ class AppSecRequestContextSpecification extends DDSpecification {
290290
then:
291291
ctx.getRaspTimeouts() == 2
292292
}
293+
294+
def "test increase and get RaspErrors"() {
295+
when:
296+
ctx.increaseRaspErrorCode(AppSecRequestContext.DD_WAF_RUN_INTERNAL_ERROR)
297+
ctx.increaseRaspErrorCode(AppSecRequestContext.DD_WAF_RUN_INTERNAL_ERROR)
298+
ctx.increaseRaspErrorCode(AppSecRequestContext.DD_WAF_RUN_INVALID_OBJECT_ERROR)
299+
300+
then:
301+
ctx.getRaspError(AppSecRequestContext.DD_WAF_RUN_INTERNAL_ERROR) == 2
302+
ctx.getRaspError(AppSecRequestContext.DD_WAF_RUN_INVALID_OBJECT_ERROR) == 1
303+
ctx.getRaspError(AppSecRequestContext.DD_WAF_RUN_INVALID_ARGUMENT_ERROR) == 0
304+
ctx.getRaspError(0) == 0
305+
}
293306
}

internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java

+55
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
import java.util.List;
77
import java.util.concurrent.ArrayBlockingQueue;
88
import java.util.concurrent.BlockingQueue;
9+
import java.util.concurrent.ConcurrentMap;
10+
import java.util.concurrent.ConcurrentSkipListMap;
911
import java.util.concurrent.atomic.AtomicInteger;
1012
import java.util.concurrent.atomic.AtomicLong;
1113
import java.util.concurrent.atomic.AtomicLongArray;
1214

1315
public class WafMetricCollector implements MetricCollector<WafMetricCollector.WafMetric> {
1416

1517
public static WafMetricCollector INSTANCE = new WafMetricCollector();
18+
private static final int ABSTRACT_POWERWAF_EXCEPTION_NUMBER =
19+
3; // only 3 error codes are possible for now in AbstractPowerwafException
1620

1721
public static WafMetricCollector get() {
1822
return WafMetricCollector.INSTANCE;
@@ -40,6 +44,15 @@ private WafMetricCollector() {
4044
new AtomicLongArray(RuleType.getNumValues());
4145
private static final AtomicLongArray raspTimeoutCounter =
4246
new AtomicLongArray(RuleType.getNumValues());
47+
private static final ConcurrentMap<Integer, AtomicLongArray> raspErrorCodeCounter =
48+
new ConcurrentSkipListMap<>();
49+
50+
static {
51+
for (int i = -1 * ABSTRACT_POWERWAF_EXCEPTION_NUMBER; i < 0; i++) {
52+
raspErrorCodeCounter.put(i, new AtomicLongArray(RuleType.getNumValues()));
53+
}
54+
}
55+
4356
private static final AtomicLongArray missingUserLoginQueue =
4457
new AtomicLongArray(LoginFramework.getNumValues() * LoginEvent.getNumValues());
4558
private static final AtomicLongArray missingUserIdQueue =
@@ -104,6 +117,10 @@ public void raspTimeout(final RuleType ruleType) {
104117
raspTimeoutCounter.incrementAndGet(ruleType.ordinal());
105118
}
106119

120+
public void raspErrorCode(final RuleType ruleType, final int ddwafRunErrorCode) {
121+
raspErrorCodeCounter.get(ddwafRunErrorCode).incrementAndGet(ruleType.ordinal());
122+
}
123+
107124
public void missingUserLogin(final LoginFramework framework, final LoginEvent eventType) {
108125
missingUserLoginQueue.incrementAndGet(
109126
framework.ordinal() * LoginEvent.getNumValues() + eventType.ordinal());
@@ -216,6 +233,19 @@ public void prepareMetrics() {
216233
}
217234
}
218235

236+
// RASP rule type for each possible error code
237+
for (int i = -1 * ABSTRACT_POWERWAF_EXCEPTION_NUMBER; i < 0; i++) {
238+
for (RuleType ruleType : RuleType.values()) {
239+
long counter = raspErrorCodeCounter.get(i).getAndSet(ruleType.ordinal(), 0);
240+
if (counter > 0) {
241+
if (!rawMetricsQueue.offer(
242+
new RaspError(counter, ruleType, WafMetricCollector.wafVersion, i))) {
243+
return;
244+
}
245+
}
246+
}
247+
}
248+
219249
// Missing user login
220250
for (LoginFramework framework : LoginFramework.values()) {
221251
for (LoginEvent event : LoginEvent.values()) {
@@ -367,6 +397,31 @@ public RaspTimeout(final long counter, final RuleType ruleType, final String waf
367397
}
368398
}
369399

400+
public static class RaspError extends WafMetric {
401+
public RaspError(
402+
final long counter,
403+
final RuleType ruleType,
404+
final String wafVersion,
405+
final Integer ddwafRunError) {
406+
super(
407+
"rasp.error",
408+
counter,
409+
ruleType.variant != null
410+
? new String[] {
411+
"rule_type:" + ruleType.type,
412+
"rule_variant:" + ruleType.variant,
413+
"waf_version:" + wafVersion,
414+
"event_rules_version:" + rulesVersion,
415+
"waf_error:" + ddwafRunError
416+
}
417+
: new String[] {
418+
"rule_type:" + ruleType.type,
419+
"waf_version:" + wafVersion,
420+
"waf_error:" + ddwafRunError
421+
});
422+
}
423+
}
424+
370425
public static class AtomicRequestCounter {
371426

372427
private final AtomicLong atomicLong = new AtomicLong();

internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy

+44
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import java.util.concurrent.TimeUnit
88

99
class WafMetricCollectorTest extends DDSpecification {
1010

11+
public static final int DD_WAF_RUN_INTERNAL_ERROR = -3
12+
public static final int DD_WAF_RUN_INVALID_OBJECT_ERROR = -2
13+
1114
def "no metrics - drain empty list"() {
1215
when:
1316
WafMetricCollector.get().prepareMetrics()
@@ -32,6 +35,8 @@ class WafMetricCollectorTest extends DDSpecification {
3235
WafMetricCollector.get().raspRuleMatch(RuleType.SQL_INJECTION)
3336
WafMetricCollector.get().raspRuleEval(RuleType.SQL_INJECTION)
3437
WafMetricCollector.get().raspTimeout(RuleType.SQL_INJECTION)
38+
WafMetricCollector.get().raspErrorCode(RuleType.SHELL_INJECTION, DD_WAF_RUN_INTERNAL_ERROR)
39+
WafMetricCollector.get().raspErrorCode(RuleType.SQL_INJECTION, DD_WAF_RUN_INVALID_OBJECT_ERROR)
3540

3641
WafMetricCollector.get().prepareMetrics()
3742

@@ -131,6 +136,31 @@ class WafMetricCollectorTest extends DDSpecification {
131136
raspTimeout.namespace == 'appsec'
132137
raspTimeout.metricName == 'rasp.timeout'
133138
raspTimeout.tags.toSet() == ['rule_type:sql_injection', 'waf_version:waf_ver1'].toSet()
139+
140+
def raspInvalidCode = (WafMetricCollector.RaspError)metrics[10]
141+
raspInvalidCode.type == 'count'
142+
raspInvalidCode.value == 1
143+
raspInvalidCode.namespace == 'appsec'
144+
raspInvalidCode.metricName == 'rasp.error'
145+
raspInvalidCode.tags.toSet() == [
146+
'waf_version:waf_ver1',
147+
'rule_type:command_injection',
148+
'rule_variant:shell',
149+
'event_rules_version:rules.3',
150+
'waf_error:' + DD_WAF_RUN_INTERNAL_ERROR
151+
].toSet()
152+
153+
def raspInvalidObjectCode = (WafMetricCollector.RaspError)metrics[11]
154+
raspInvalidObjectCode.type == 'count'
155+
raspInvalidObjectCode.value == 1
156+
raspInvalidObjectCode.namespace == 'appsec'
157+
raspInvalidObjectCode.metricName == 'rasp.error'
158+
raspInvalidObjectCode.tags.toSet() == [
159+
'rule_type:sql_injection',
160+
'waf_version:waf_ver1',
161+
'waf_error:' + DD_WAF_RUN_INVALID_OBJECT_ERROR
162+
]
163+
.toSet()
134164
}
135165

136166
def "overflowing WafMetricCollector does not crash"() {
@@ -304,6 +334,7 @@ class WafMetricCollectorTest extends DDSpecification {
304334
WafMetricCollector.get().raspRuleMatch(ruleType)
305335
WafMetricCollector.get().raspRuleEval(ruleType)
306336
WafMetricCollector.get().raspTimeout(ruleType)
337+
WafMetricCollector.get().raspErrorCode(ruleType, DD_WAF_RUN_INTERNAL_ERROR)
307338
WafMetricCollector.get().prepareMetrics()
308339

309340
then:
@@ -345,6 +376,19 @@ class WafMetricCollectorTest extends DDSpecification {
345376
'event_rules_version:rules.1'
346377
].toSet()
347378

379+
def raspInvalidCode = (WafMetricCollector.RaspError)metrics[4]
380+
raspInvalidCode.type == 'count'
381+
raspInvalidCode.value == 1
382+
raspInvalidCode.namespace == 'appsec'
383+
raspInvalidCode.metricName == 'rasp.error'
384+
raspInvalidCode.tags.toSet() == [
385+
'waf_version:waf_ver1',
386+
'rule_type:command_injection',
387+
'rule_variant:' + ruleType.variant,
388+
'event_rules_version:rules.1',
389+
'waf_error:' + DD_WAF_RUN_INTERNAL_ERROR
390+
].toSet()
391+
348392
where:
349393
ruleType << [RuleType.COMMAND_INJECTION, RuleType.SHELL_INJECTION]
350394
}

0 commit comments

Comments
 (0)