Skip to content

Commit

Permalink
[ASM] Add event rules version in telemetry (#6208)
Browse files Browse the repository at this point in the history
## Summary of changes

In the WAF related telemetry metrics, we are not currently sending the
rules version, only the WAF version. All the other libraries already
send that and we have been requested to send this metric as well.

For now, the tag is only sent in non-RASP ASM metrics. RASP metrics will
include this tag in the near future but the it is still not defined.

## Reason for change

## 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. -->
  • Loading branch information
NachoEchevarria authored Oct 30, 2024
1 parent 15fba9c commit 1d065bd
Show file tree
Hide file tree
Showing 15 changed files with 119 additions and 66 deletions.
4 changes: 3 additions & 1 deletion tracer/src/Datadog.Trace/AppSec/Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ private ApplyDetails[] UpdateFromRcm(Dictionary<string, List<RemoteConfiguration
if (_wafInitResult?.RuleFileVersion is not null)
{
WafRuleFileVersion = _wafInitResult.RuleFileVersion;
TelemetryFactory.Metrics.SetWafAndRulesVersion(_waf!.Version, WafRuleFileVersion);
}

RefreshRcmSubscriptions();
Expand All @@ -222,6 +223,7 @@ private ApplyDetails[] UpdateFromRcm(Dictionary<string, List<RemoteConfiguration
if (!string.IsNullOrEmpty(updateResult.RuleFileVersion))
{
WafRuleFileVersion = updateResult.RuleFileVersion;
TelemetryFactory.Metrics.SetWafAndRulesVersion(_waf!.Version, WafRuleFileVersion);
}

UpdateActiveAddresses();
Expand Down Expand Up @@ -550,7 +552,7 @@ private void InitWafAndInstrumentations()
if (oldWaf is null)
{
// occurs the first time we initialize the WAF
TelemetryFactory.Metrics.SetWafVersion(_waf!.Version);
TelemetryFactory.Metrics.SetWafAndRulesVersion(_waf!.Version, WafRuleFileVersion);
TelemetryFactory.Metrics.RecordCountWafInit();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,11 @@ private static AggregatedMetric[] GetCountBuffer()
// waf.updates, index = 499
new(null),
// waf.requests, index = 500
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
// waf.input_truncated, index = 505
new(new[] { "truncation_reason:string_too_long" }),
new(new[] { "truncation_reason:list_or_map_too_large" }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,11 @@ private static AggregatedMetric[] GetCountBuffer()
// waf.updates, index = 499
new(null),
// waf.requests, index = 500
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
// waf.input_truncated, index = 505
new(new[] { "truncation_reason:string_too_long" }),
new(new[] { "truncation_reason:list_or_map_too_large" }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,11 @@ private static AggregatedMetric[] GetCountBuffer()
// waf.updates, index = 499
new(null),
// waf.requests, index = 500
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
// waf.input_truncated, index = 505
new(new[] { "truncation_reason:string_too_long" }),
new(new[] { "truncation_reason:list_or_map_too_large" }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,11 @@ private static AggregatedMetric[] GetCountBuffer()
// waf.updates, index = 499
new(null),
// waf.requests, index = 500
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:false", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:true", "request_blocked:true", "waf_timeout:false", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:true", "request_excluded:false" }),
new(new[] { "waf_version", "event_rules_version", "rule_triggered:false", "request_blocked:false", "waf_timeout:false", "request_excluded:true" }),
// waf.input_truncated, index = 505
new(new[] { "truncation_reason:string_too_long" }),
new(new[] { "truncation_reason:list_or_map_too_large" }),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="IMetricsTelemetryCollector.cs" company="Datadog">
// <copyright file="IMetricsTelemetryCollector.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
Expand All @@ -22,7 +22,7 @@ internal partial interface IMetricsTelemetryCollector
/// <summary>
/// Sets the version of the WAF used for future metrics
/// </summary>
public void SetWafVersion(string wafVersion);
public void SetWafAndRulesVersion(string wafVersion, string? eventRulesVersion);

public Task DisposeAsync();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="MetricsTelemetryCollectorBase.cs" company="Datadog">
// <copyright file="MetricsTelemetryCollectorBase.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
Expand All @@ -14,12 +14,12 @@ namespace Datadog.Trace.Telemetry;

internal abstract partial class MetricsTelemetryCollectorBase
{
private static readonly string[] _unknownWafAndRulesVersionTags = { "waf_version:unknown", "event_rules_version:unknown" };
private readonly TimeSpan _aggregationInterval;
private readonly Action? _aggregationNotification;
private readonly string[] _unknownWafVersionTags = { "waf_version:unknown" };
private readonly Task _aggregateTask;
private readonly TaskCompletionSource<bool> _processExit = new();
private string[]? _wafVersionTags;
private string[]? _wafAndRulesVersionTags;

protected MetricsTelemetryCollectorBase()
: this(TimeSpan.FromSeconds(10))
Expand Down Expand Up @@ -53,10 +53,10 @@ public Task DisposeAsync()
return _aggregateTask;
}

public void SetWafVersion(string wafVersion)
public void SetWafAndRulesVersion(string wafVersion, string? eventRulesVersion)
{
// Setting this an array so we can reuse it for multiple metrics
_wafVersionTags = new[] { $"waf_version:{wafVersion}" };
_wafAndRulesVersionTags = new[] { $"waf_version:{wafVersion}", $"event_rules_version:{eventRulesVersion ?? "unknown"}" };
}

protected static AggregatedMetric[] GetPublicApiCountBuffer()
Expand Down Expand Up @@ -220,12 +220,19 @@ private async Task AggregateMetricsLoopAsync()

if (metricKeyTags is null)
{
return _wafVersionTags ?? _unknownWafVersionTags;
return _wafAndRulesVersionTags ?? _unknownWafAndRulesVersionTags;
}

var wafVersionTag = (_wafVersionTags ?? _unknownWafVersionTags)[0];
if (string.Equals(metricKeyTags[0], "waf_version"))
{
metricKeyTags[0] = (_wafAndRulesVersionTags ?? _unknownWafAndRulesVersionTags)[0];
}

if (metricKeyTags.Length > 1 && string.Equals(metricKeyTags[1], "event_rules_version"))
{
metricKeyTags[1] = (_wafAndRulesVersionTags ?? _unknownWafAndRulesVersionTags)[1];
}

metricKeyTags[0] = wafVersionTag;
return metricKeyTags;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="NullMetricsTelemetryCollector.cs" company="Datadog">
// <copyright file="NullMetricsTelemetryCollector.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
Expand All @@ -21,7 +21,7 @@ public void Record(PublicApiUsage api)

public MetricResults GetMetrics() => new(null, null);

public void SetWafVersion(string wafVersion)
public void SetWafAndRulesVersion(string wafVersion, string? eventRulesVersion)
{
}
}
10 changes: 5 additions & 5 deletions tracer/src/Datadog.Trace/Telemetry/Metrics/MetricTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,11 @@ public enum WafAnalysis
// Note the initial 'waf_version'. This is an optimisation to avoid multiple array allocations
// It is replaced with the "real" waf_version at runtime
// CAUTION: waf_version should aways be placed in first position
[Description("waf_version;rule_triggered:false;request_blocked:false;waf_timeout:false;request_excluded:false")]Normal,
[Description("waf_version;rule_triggered:true;request_blocked:false;waf_timeout:false;request_excluded:false")]RuleTriggered,
[Description("waf_version;rule_triggered:true;request_blocked:true;waf_timeout:false;request_excluded:false")]RuleTriggeredAndBlocked,
[Description("waf_version;rule_triggered:false;request_blocked:false;waf_timeout:true;request_excluded:false")]WafTimeout,
[Description("waf_version;rule_triggered:false;request_blocked:false;waf_timeout:false;request_excluded:true")]RequestExcludedViaFilter,
[Description("waf_version;event_rules_version;rule_triggered:false;request_blocked:false;waf_timeout:false;request_excluded:false")]Normal,
[Description("waf_version;event_rules_version;rule_triggered:true;request_blocked:false;waf_timeout:false;request_excluded:false")]RuleTriggered,
[Description("waf_version;event_rules_version;rule_triggered:true;request_blocked:true;waf_timeout:false;request_excluded:false")]RuleTriggeredAndBlocked,
[Description("waf_version;event_rules_version;rule_triggered:false;request_blocked:false;waf_timeout:true;request_excluded:false")]WafTimeout,
[Description("waf_version;event_rules_version;rule_triggered:false;request_blocked:false;waf_timeout:false;request_excluded:true")]RequestExcludedViaFilter,
}

[EnumExtensions]
Expand Down
2 changes: 1 addition & 1 deletion tracer/src/Datadog.Trace/TracerManagerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ internal TracerManager CreateTracerManager(
gitMetadataTagsProvider);

telemetry.RecordTracerSettings(settings, defaultServiceName);
TelemetryFactory.Metrics.SetWafVersion(Security.Instance.DdlibWafVersion);
TelemetryFactory.Metrics.SetWafAndRulesVersion(Security.Instance.DdlibWafVersion, Security.Instance.WafRuleFileVersion);
ErrorData? initError = !string.IsNullOrEmpty(Security.Instance.InitializationError)
? new ErrorData(TelemetryErrorCode.AppsecConfigurationError, Security.Instance.InitializationError)
: null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public void ScrubFingerprintHeaders(VerifySettings settings)
settings.AddRegexScrubber(AppSecFingerPrintNetwork, "_dd.appsec.fp.http.network: <NetworkPrint>");
}

public async Task VerifySpans(IImmutableList<MockSpan> spans, VerifySettings settings, bool testInit = false, string methodNameOverride = null, string testName = null, bool forceMetaStruct = false, string fileNameOverride = null)
public async Task VerifySpans(IImmutableList<MockSpan> spans, VerifySettings settings, bool testInit = false, string methodNameOverride = null, string testName = null, bool forceMetaStruct = false, string fileNameOverride = null, bool showRulesVersion = false)
{
settings.ModifySerialization(
serializationSettings =>
Expand Down Expand Up @@ -209,11 +209,15 @@ public async Task VerifySpans(IImmutableList<MockSpan> spans, VerifySettings set
if (!testInit)
{
settings.AddRegexScrubber(AppSecWafVersion, string.Empty);
settings.AddRegexScrubber(AppSecWafRulesVersion, string.Empty);
settings.AddRegexScrubber(AppSecErrorCount, string.Empty);
settings.AddRegexScrubber(AppSecEventRulesLoaded, string.Empty);
}

if (!showRulesVersion && !testInit)
{
settings.AddRegexScrubber(AppSecWafRulesVersion, string.Empty);
}

var appsecSpans = spans.Where(s => s.Tags.ContainsKey("_dd.appsec.json") || (s.MetaStruct != null && s.MetaStruct.ContainsKey("appsec")));
if (appsecSpans.Any())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public async Task TestRemoteRules()
spans.AddRange(spans5);
spans.AddRange(spans6);

await VerifySpans(spans.ToImmutableList(), settings);
// We want to test that the remote rules version tag gets updated through RC
await VerifySpans(spans.ToImmutableList(), settings, showRulesVersion: true);
}

protected override string GetTestName() => Prefix + nameof(AspNetCore5AsmRemoteRules);
Expand Down
Loading

0 comments on commit 1d065bd

Please sign in to comment.