Skip to content

Commit c92d71a

Browse files
[AAP] Added WAF metrics. Reorganized other metrics to match RFC (#7356)
## Summary of changes Implemented some WAF missing metrics, as `WafTimeouts` Reorganized existing metrics which were defined as Tags instead of Metrics, matching the definition in the RFC ## Reason for change WAF metrics [RFC](https://docs.google.com/document/d/1D4hkC0jwwUyeo0hEQgyKP54kM1LZU98GL8MaP60tQrA/edit?tab=t.0) ## Implementation details ## Test coverage ## Other details <!-- Fixes #{issue} --> <!-- ⚠️ Note: Where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. MergeQueue is NOT enabled in this repository. If you have write access to the repo, the PR has 1-2 approvals (see above), and all of the required checks have passed, you can use the Squash and Merge button to merge the PR. If you don't have write access, or you need help, reach out in the #apm-dotnet channel in Slack. -->
1 parent 6a07aa6 commit c92d71a

File tree

8 files changed

+102
-36
lines changed

8 files changed

+102
-36
lines changed

tracer/src/Datadog.Trace/AppSec/AppSecRequestContext.cs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#nullable enable
77

88
using System.Collections.Generic;
9+
using System.Threading;
910
using Datadog.Trace.AppSec.Rasp;
1011
using Datadog.Trace.AppSec.Waf;
1112
using Datadog.Trace.Logging;
@@ -22,13 +23,14 @@ internal partial class AppSecRequestContext
2223
private const string AppsecKey = "appsec";
2324
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor<AppSecRequestContext>();
2425
private readonly object _sync = new();
25-
private readonly RaspTelemetryHelper? _raspTelemetryHelper = Security.Instance.RaspEnabled ? new RaspTelemetryHelper() : null;
26+
private readonly RaspMetricsHelper? _raspMetricsHelper = Security.Instance.RaspEnabled ? new RaspMetricsHelper() : null;
2627
private readonly List<object> _wafSecurityEvents = new();
28+
private int _wafTimeout = 0;
2729
private int? _wafError = null;
2830
private int? _wafRaspError = null;
2931
private Dictionary<string, List<Dictionary<string, object>>>? _raspStackTraces;
3032

31-
internal void CloseWebSpan(TraceTagCollection tags, Span span)
33+
internal void CloseWebSpan(Span span)
3234
{
3335
lock (_sync)
3436
{
@@ -43,31 +45,42 @@ internal void CloseWebSpan(TraceTagCollection tags, Span span)
4345
else
4446
{
4547
var triggers = JsonConvert.SerializeObject(_wafSecurityEvents);
46-
tags.SetTag(Tags.AppSecJson, "{\"triggers\":" + triggers + "}");
48+
span.Tags.SetTag(Tags.AppSecJson, "{\"triggers\":" + triggers + "}");
4749
}
4850
}
4951

50-
if (_raspStackTraces?.Count > 0)
52+
if (_wafTimeout > 0)
5153
{
52-
span.SetMetaStruct(StackKey, MetaStructHelper.ObjectToByteArray(_raspStackTraces));
54+
span.Tags.SetMetric(Metrics.WafTimeouts, _wafTimeout);
5355
}
5456

5557
if (_wafError != null)
5658
{
57-
tags.SetTag(Tags.WafError, _wafError.ToString());
59+
span.Tags.SetMetric(Metrics.WafError, _wafError);
5860
}
5961

6062
if (_wafRaspError != null)
6163
{
62-
tags.SetTag(Tags.RaspWafError, _wafRaspError.ToString());
64+
span.Tags.SetMetric(Metrics.RaspWafError, _wafRaspError);
65+
}
66+
67+
if (_raspStackTraces?.Count > 0)
68+
{
69+
span.SetMetaStruct(StackKey, MetaStructHelper.ObjectToByteArray(_raspStackTraces));
6370
}
6471

65-
_raspTelemetryHelper?.GenerateRaspSpanMetricTags(span.Tags);
72+
_raspMetricsHelper?.GenerateRaspSpanMetricTags(span.Tags);
6673
}
6774
}
6875

69-
internal void CheckWAFError(int code, bool isRasp)
76+
internal void CheckWAFError(IResult result, bool isRasp)
7077
{
78+
if (result.Timeout)
79+
{
80+
_wafTimeout++;
81+
}
82+
83+
var code = (int)result.ReturnCode;
7184
int? existingValue = isRasp ? _wafRaspError : _wafError;
7285
if (code < 0 && (existingValue == null || existingValue < code))
7386
{
@@ -86,7 +99,7 @@ internal void AddRaspSpanMetrics(ulong duration, ulong durationWithBindings, boo
8699
{
87100
lock (_sync)
88101
{
89-
_raspTelemetryHelper?.AddRaspSpanMetrics(duration, durationWithBindings, timeout);
102+
_raspMetricsHelper?.AddRaspSpanMetrics(duration, durationWithBindings, timeout);
90103
}
91104
}
92105

tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private void SetErrorInformation(bool isRasp, IResult? result)
9999
{
100100
if (result is not null)
101101
{
102-
_localRootSpan.Context.TraceContext.AppSecRequestContext.CheckWAFError((int)result.ReturnCode, isRasp);
102+
_localRootSpan.Context.TraceContext.AppSecRequestContext.CheckWAFError(result, isRasp);
103103
}
104104
}
105105

tracer/src/Datadog.Trace/AppSec/Rasp/RaspTelemetryHelper.cs renamed to tracer/src/Datadog.Trace/AppSec/Rasp/RaspMetricsHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright file="RaspTelemetryHelper.cs" company="Datadog">
1+
// <copyright file="RaspMetricsHelper.cs" company="Datadog">
22
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
44
// </copyright>
@@ -8,7 +8,7 @@
88
#nullable enable
99
namespace Datadog.Trace.AppSec.Rasp;
1010

11-
internal class RaspTelemetryHelper
11+
internal class RaspMetricsHelper
1212
{
1313
private ulong _raspWafDuration = 0;
1414
private ulong _raspWafAndBindingsDuration = 0;

tracer/src/Datadog.Trace/Metrics.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ internal static class Metrics
8787
/// </summary>
8888
public const string AppSecWafAndBindingsDuration = "_dd.appsec.waf.duration_ext";
8989

90+
/// <summary>
91+
/// The number of WAF timeouts during a request
92+
/// </summary>
93+
public const string WafTimeouts = "_dd.appsec.waf.timeouts";
94+
95+
/// <summary>
96+
/// The most relevant WAF error code during a request
97+
/// </summary>
98+
public const string WafError = "_dd.appsec.waf.error";
99+
100+
/// <summary>
101+
/// The most relevant WAF error code during a request when using RASP
102+
/// </summary>
103+
public const string RaspWafError = "_dd.appsec.rasp.error";
104+
90105
/// <summary>
91106
/// Total cumulative waf duration for RASP calls across spans for one request
92107
/// </summary>

tracer/src/Datadog.Trace/Tags.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -614,16 +614,6 @@ public static partial class Tags
614614
/// </summary>
615615
internal const string AppSecWafVersion = "_dd.appsec.waf.version";
616616

617-
/// <summary>
618-
/// The most relevant WAF error code during a request
619-
/// </summary>
620-
public const string WafError = "_dd.appsec.waf.error";
621-
622-
/// <summary>
623-
/// The most relevant WAF error code during a request when using RASP
624-
/// </summary>
625-
public const string RaspWafError = "_dd.appsec.rasp.error";
626-
627617
/// <summary>
628618
/// String-serialized JSON array, each item being a map containing:
629619
/// Error(e) - the error string.

tracer/src/Datadog.Trace/TraceContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public void CloseSpan(Span span)
180180

181181
if (_appSecRequestContext is not null)
182182
{
183-
_appSecRequestContext.CloseWebSpan(Tags, span);
183+
_appSecRequestContext.CloseWebSpan(span);
184184
_appSecRequestContext.DisposeAdditiveContext();
185185
}
186186
}

tracer/test/Datadog.Trace.Security.Unit.Tests/AppSecContextTests.cs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// </copyright>
55

66
using System.Collections.Generic;
7+
using Datadog.Trace.AppSec.Waf;
78
using Datadog.Trace.Configuration;
89
using Datadog.Trace.Tagging;
910
using FluentAssertions;
@@ -13,23 +14,70 @@ namespace Datadog.Trace.Security.Unit.Tests;
1314

1415
public class AppSecContextTests
1516
{
16-
[InlineData(-2, -2, -1, -2, -1)]
17-
[InlineData(2, -2, -1, null, -1)]
18-
[InlineData(2, 2, 1, null, null)]
17+
[InlineData(-2, -2, -1, 0, -2, -1)]
18+
[InlineData(2, -2, -1, 1, null, -1)]
19+
[InlineData(2, 2, 1, 2, null, null)]
1920
[Theory]
20-
public void GivenAQuery_WhenWAFError_ThenSpanHasErrorTags(int raspErrorCode, int wafErrorCode, int wafErrorCode2, int? expectedRaspErrorCode, int? expectedWafErrorCode)
21+
public void GivenAQuery_WhenWAFError_ThenSpanHasErrorTags(int raspErrorCode, int wafErrorCode, int wafErrorCode2, int wafTimeouts, int? expectedRaspErrorCode, int? expectedWafErrorCode)
2122
{
2223
var settings = TracerSettings.Create(new Dictionary<string, object>());
2324
var tracer = new Tracer(settings, null, null, null, null);
2425
var rootTestScope = (Scope)tracer.StartActive("test.trace");
2526

2627
var appSecContext = rootTestScope.Span.Context.TraceContext.AppSecRequestContext;
27-
appSecContext.CheckWAFError(raspErrorCode, true);
28-
appSecContext.CheckWAFError(wafErrorCode, false);
29-
appSecContext.CheckWAFError(wafErrorCode2, false);
30-
TraceTagCollection tags = new();
31-
appSecContext.CloseWebSpan(tags, rootTestScope.Span);
32-
tags.GetTag(Tags.WafError).Should().Be(expectedWafErrorCode?.ToString());
33-
tags.GetTag(Tags.RaspWafError).Should().Be(expectedRaspErrorCode?.ToString());
28+
int timeouts = wafTimeouts;
29+
30+
appSecContext.CheckWAFError(new MockResult(raspErrorCode, timeouts-- > 0), true);
31+
appSecContext.CheckWAFError(new MockResult(wafErrorCode, timeouts-- > 0), false);
32+
appSecContext.CheckWAFError(new MockResult(wafErrorCode2, timeouts-- > 0), false);
33+
appSecContext.CloseWebSpan(rootTestScope.Span);
34+
rootTestScope.Span.GetMetric(Metrics.WafError).Should().Be(expectedWafErrorCode);
35+
rootTestScope.Span.GetMetric(Metrics.RaspWafError).Should().Be(expectedRaspErrorCode);
36+
37+
if (wafTimeouts > 0)
38+
{
39+
rootTestScope.Span.GetMetric(Metrics.WafTimeouts).Should().Be(wafTimeouts);
40+
}
41+
}
42+
43+
private class MockResult : IResult
44+
{
45+
public MockResult(int returnCode, bool timeout)
46+
{
47+
ReturnCode = (WafReturnCode)returnCode;
48+
Timeout = timeout;
49+
}
50+
51+
public WafReturnCode ReturnCode { get; }
52+
53+
public bool Timeout { get; }
54+
55+
public bool ShouldBlock => throw new System.NotImplementedException();
56+
57+
public Dictionary<string, object> BlockInfo => throw new System.NotImplementedException();
58+
59+
public Dictionary<string, object> RedirectInfo => throw new System.NotImplementedException();
60+
61+
public Dictionary<string, object> SendStackInfo => throw new System.NotImplementedException();
62+
63+
public IReadOnlyCollection<object> Data => throw new System.NotImplementedException();
64+
65+
public Dictionary<string, object> Actions => throw new System.NotImplementedException();
66+
67+
public ulong AggregatedTotalRuntime => throw new System.NotImplementedException();
68+
69+
public ulong AggregatedTotalRuntimeWithBindings => throw new System.NotImplementedException();
70+
71+
public ulong AggregatedTotalRuntimeRasp => throw new System.NotImplementedException();
72+
73+
public ulong AggregatedTotalRuntimeWithBindingsRasp => throw new System.NotImplementedException();
74+
75+
public uint RaspRuleEvaluations => throw new System.NotImplementedException();
76+
77+
public Dictionary<string, object> ExtractSchemaDerivatives => throw new System.NotImplementedException();
78+
79+
public Dictionary<string, object> FingerprintDerivatives => throw new System.NotImplementedException();
80+
81+
public bool ShouldReportSecurityResult => throw new System.NotImplementedException();
3482
}
3583
}

tracer/test/Datadog.Trace.Security.Unit.Tests/RASP/RaspTelemetryHelperTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class RaspTelemetryHelperTests
1717
[Fact]
1818
public void GivenARaspTelemetryHelper_WhenGenerateRaspSpanMetricTagsIsCalledWithTimeout_ThenTagIsSet()
1919
{
20-
var raspTelemetryHelper = new RaspTelemetryHelper();
20+
var raspTelemetryHelper = new RaspMetricsHelper();
2121
var tags = new TagsList();
2222
raspTelemetryHelper.AddRaspSpanMetrics(1000, 2000, true);
2323
raspTelemetryHelper.GenerateRaspSpanMetricTags(tags);

0 commit comments

Comments
 (0)