Skip to content

Commit 660ae6e

Browse files
authored
[AppSec] Update waf and ruleset 1.3.0 (DataDog#2638)
* First update classes * Update of the waf * Waf tests and init fixed * change snapshots and obfuscate values * Adding some integration tests for initialization * update waf to 1.3.0 * Skip cookies tests because of new ruleset * Fix tests to not run parallel and snapshots Fixing tests to match 1.3.0 fix snapshots * fix snapshots * Response to comments * Response to 2nd round of comments Add default rule file * fix initialization * Return if ddwafObjectStruct from resultsetinfo is not a map, but it should be a map coming from the waf
1 parent 9e4722b commit 660ae6e

File tree

90 files changed

+23756
-233
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+23756
-233
lines changed

tracer/build/_build/Build.Steps.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ partial class Build
5151

5252
AbsolutePath ProfilerHomeDirectory => ProfilerHome ?? RootDirectory / ".." / "_build" / "DDProf-Deploy";
5353

54-
const string LibDdwafVersion = "1.0.17";
54+
const string LibDdwafVersion = "1.3.0";
5555
AbsolutePath LibDdwafDirectory => (NugetPackageDirectory ?? RootDirectory / "packages") / $"libddwaf.{LibDdwafVersion}";
5656

5757
AbsolutePath SourceDirectory => TracerDirectory / "src";

tracer/build/crank/Security.Samples.AspNetCoreSimpleController.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,4 @@ scenarios:
6767
warmup: 30
6868
duration: 240
6969
serverPort: 5000
70-
path: /hello?arg=[$slice]
70+
path: /hello?[$slice]=arg
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3-
<package id="libddwaf" version="1.0.17" targetFramework="native" />
3+
<package id="libddwaf" version="1.3.0" targetFramework="native" />
44
</packages>

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ private static IReadOnlyDictionary<string, object> ExtractProperties(object body
6060

6161
depth++;
6262

63-
var dictSize = Math.Min(WafConstants.MaxMapOrArrayLength, fields.Length);
63+
var dictSize = Math.Min(WafConstants.MaxContainerSize, fields.Length);
6464
var dict = new Dictionary<string, object>(dictSize);
6565

6666
for (var i = 0; i < fields.Length; i++)
6767
{
68-
if (dict.Count >= WafConstants.MaxMapOrArrayLength || depth >= WafConstants.MaxObjectDepth)
68+
if (dict.Count >= WafConstants.MaxContainerSize || depth >= WafConstants.MaxContainerDepth)
6969
{
7070
return dict;
7171
}
@@ -135,7 +135,7 @@ private static Dictionary<string, object> ExtractDictionary(object value, Type d
135135
var valueProp = tkvp.GetProperty("Value");
136136

137137
var sourceDict = (ICollection)value;
138-
var dictSize = Math.Min(WafConstants.MaxMapOrArrayLength, sourceDict.Count);
138+
var dictSize = Math.Min(WafConstants.MaxContainerSize, sourceDict.Count);
139139
var items = new Dictionary<string, object>(dictSize);
140140

141141
foreach (var item in sourceDict)
@@ -152,7 +152,7 @@ private static Dictionary<string, object> ExtractDictionary(object value, Type d
152152
items.Add(dictKey, nestedDict);
153153
}
154154

155-
if (items.Count >= WafConstants.MaxMapOrArrayLength)
155+
if (items.Count >= WafConstants.MaxContainerSize)
156156
{
157157
break;
158158
}
@@ -164,7 +164,7 @@ private static Dictionary<string, object> ExtractDictionary(object value, Type d
164164
private static List<object> ExtractListOrArray(object value, int depth, HashSet<object> visited)
165165
{
166166
var sourceList = (ICollection)value;
167-
var listSize = Math.Min(WafConstants.MaxMapOrArrayLength, sourceList.Count);
167+
var listSize = Math.Min(WafConstants.MaxContainerSize, sourceList.Count);
168168
var items = new List<object>(listSize);
169169

170170
foreach (var item in sourceList)
@@ -179,7 +179,7 @@ private static List<object> ExtractListOrArray(object value, int depth, HashSet<
179179
items.Add(nestedDict);
180180
}
181181

182-
if (items.Count >= WafConstants.MaxMapOrArrayLength)
182+
if (items.Count >= WafConstants.MaxContainerSize)
183183
{
184184
break;
185185
}

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private Security(SecuritySettings settings = null, InstrumentationGateway instru
9797
if (_settings.Enabled)
9898
{
9999
_waf = waf ?? Waf.Waf.Create(_settings.Rules);
100-
if (_waf != null)
100+
if (_waf.InitializedSuccessfully)
101101
{
102102
_instrumentationGateway.RequestEnd += InstrumentationGatewayInstrumentationGatewayEvent;
103103
_instrumentationGateway.BodyAvailable += InstrumentationGatewayInstrumentationGatewayEvent;
@@ -126,6 +126,7 @@ private Security(SecuritySettings settings = null, InstrumentationGateway instru
126126
_settings.Enabled = false;
127127
}
128128

129+
_instrumentationGateway.RequestEnd += ReportWafInitInfoOnce;
129130
LifetimeManager.Instance.AddShutdownTask(RunShutdown);
130131
_rateLimiter = new RateLimiterTimer(_settings.TraceRateLimit);
131132
}
@@ -222,7 +223,7 @@ private static void TryAddEndPoint(Span span)
222223
}
223224
}
224225

225-
private void AddAppsecSpecificInstrumentations()
226+
private static void AddAppsecSpecificInstrumentations()
226227
{
227228
try
228229
{
@@ -276,9 +277,10 @@ private void InstrumentationGateway_AddHeadersResponseTags(object sender, Instru
276277
}
277278
}
278279

279-
private void Report(ITransport transport, Span span, string resultData, bool blocked)
280+
private void Report(ITransport transport, Span span, IResult result, bool blocked)
280281
{
281282
span.SetTag(Tags.AppSecEvent, "true");
283+
var resultData = result.Data;
282284
var exceededTraces = _rateLimiter.UpdateTracesCounter();
283285
if (exceededTraces <= 0)
284286
{
@@ -307,7 +309,8 @@ private void Report(ITransport transport, Span span, string resultData, bool blo
307309

308310
var ipInfo = RequestHeadersHelper.ExtractIpAndPort(transport.GetHeader, _settings.CustomIpHeader, _settings.ExtraHeaders, transport.IsSecureConnection, reportedIpInfo);
309311
span.SetTag(Tags.ActorIp, ipInfo.IpAddress);
310-
312+
span.SetMetric(Metrics.AppSecWafDuration, result.AggregatedTotalRuntime);
313+
span.SetMetric(Metrics.AppSecWafAndBindingsDuration, result.AggregatedTotalRuntimeWithBindings);
311314
var headers = transport.GetRequestHeaders();
312315
AddHeaderTags(span, headers, RequestHeaders, SpanContextPropagator.HttpRequestHeadersTagPrefix);
313316
}
@@ -343,7 +346,7 @@ private void RunWafAndReact(IDictionary<string, object> args, ITransport transpo
343346
// blocking has been removed, waiting a better implementation
344347
}
345348

346-
Report(transport, span, wafResult.Data, block);
349+
Report(transport, span, wafResult, block);
347350
}
348351
}
349352

@@ -359,6 +362,22 @@ private void InstrumentationGatewayInstrumentationGatewayEvent(object sender, In
359362
}
360363
}
361364

365+
private void ReportWafInitInfoOnce(object sender, InstrumentationGatewaySecurityEventArgs e)
366+
{
367+
_instrumentationGateway.RequestEnd -= ReportWafInitInfoOnce;
368+
var span = e.RelatedSpan.Context.TraceContext.RootSpan ?? e.RelatedSpan;
369+
span.SetTraceSamplingPriority(_settings.KeepTraces ? SamplingPriorityValues.UserKeep : SamplingPriorityValues.AutoReject);
370+
span.SetMetric(Metrics.AppSecWafInitRulesLoaded, _waf.InitializationResult.LoadedRules);
371+
span.SetMetric(Metrics.AppSecWafInitRulesErrorCount, _waf.InitializationResult.FailedToLoadRules);
372+
span.SetTag(Tags.AppSecRuleFileVersion, _waf.InitializationResult.RuleFileVersion);
373+
if (_waf.InitializationResult.HasErrors)
374+
{
375+
span.SetTag(Tags.AppSecWafInitRuleErrors, _waf.InitializationResult.ErrorMessage);
376+
}
377+
378+
span.SetTag(Tags.AppSecWafVersion, _waf.Version.ToString());
379+
}
380+
362381
private void RunShutdown()
363382
{
364383
if (_instrumentationGateway != null)

tracer/src/Datadog.Trace/AppSec/Waf/Context.cs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System;
77
using System.Collections.Generic;
8+
using System.Diagnostics;
89
using Datadog.Trace.AppSec.Waf.NativeBindings;
910
using Datadog.Trace.Logging;
1011
using Datadog.Trace.Vendors.Serilog.Events;
@@ -18,22 +19,23 @@ internal class Context : IContext
1819
private readonly WafNative wafNative;
1920
private readonly Encoder encoder;
2021
private readonly List<Obj> argCache = new();
22+
private readonly Stopwatch _stopwatch;
2123
private bool disposed = false;
24+
private ulong _totalRuntimeOverRuns;
2225

2326
public Context(IntPtr contextHandle, WafNative wafNative, Encoder encoder)
2427
{
2528
this.contextHandle = contextHandle;
2629
this.wafNative = wafNative;
2730
this.encoder = encoder;
31+
_stopwatch = new Stopwatch();
2832
}
2933

30-
~Context()
31-
{
32-
Dispose(false);
33-
}
34+
~Context() => Dispose(false);
3435

3536
public IResult Run(IDictionary<string, object> args, ulong timeoutMicroSeconds)
3637
{
38+
_stopwatch.Start();
3739
var pwArgs = encoder.Encode(args, argCache, applySafetyLimits: true);
3840

3941
if (Log.IsEnabled(LogEventLevel.Debug))
@@ -42,23 +44,22 @@ public IResult Run(IDictionary<string, object> args, ulong timeoutMicroSeconds)
4244
Log.Debug("DDAS-0010-00: Executing AppSec In-App WAF with parameters: {Parameters}", parameters);
4345
}
4446

45-
var rawAgs = pwArgs.RawPtr;
47+
var rawArgs = pwArgs.RawPtr;
4648
DdwafResultStruct retNative = default;
47-
48-
var code = wafNative.Run(contextHandle, rawAgs, ref retNative, timeoutMicroSeconds);
49-
50-
var ret = new Result(retNative, code, wafNative);
49+
var code = wafNative.Run(contextHandle, rawArgs, ref retNative, timeoutMicroSeconds);
50+
_stopwatch.Stop();
51+
_totalRuntimeOverRuns += retNative.TotalRuntime / 1000;
52+
var result = new Result(retNative, code, wafNative, _totalRuntimeOverRuns, (ulong)_stopwatch.Elapsed.TotalMilliseconds * 1000);
5153

5254
if (Log.IsEnabled(LogEventLevel.Debug))
5355
{
54-
Log.Debug<ReturnCode, string, int>(
55-
"DDAS-0011-00: AppSec In-App WAF returned: {ReturnCode} {Data} Took {PerfTotalRuntime} ms",
56-
ret.ReturnCode,
57-
ret.Data,
58-
retNative.PerfTotalRuntime);
56+
Log.Debug(
57+
"DDAS-0011-00: AppSec In-App WAF returned: {ReturnCode} {Data}",
58+
result.ReturnCode,
59+
result.Data);
5960
}
6061

61-
return ret;
62+
return result;
6263
}
6364

6465
public void Dispose(bool disposing)

tracer/src/Datadog.Trace/AppSec/Waf/Encoder.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ private static string TruncateLongString(string s) =>
5757
s.Length > WafConstants.MaxStringLength ? s.Substring(0, WafConstants.MaxStringLength) : s;
5858

5959
public Obj Encode(object o, List<Obj> argCache, bool applySafetyLimits) =>
60-
EncodeInternal(o, argCache, WafConstants.MaxObjectDepth, applySafetyLimits);
60+
EncodeInternal(o, argCache, WafConstants.MaxContainerDepth, applySafetyLimits);
6161

6262
private Obj EncodeInternal(object o, List<Obj> argCache, int remainingDepth, bool applyLimits)
6363
{
@@ -98,10 +98,10 @@ private Obj EncodeList(IEnumerable<object> objEnumerator, List<Obj> argCache, in
9898
}
9999

100100
var count = objEnumerator is IList<object> objs ? objs.Count : objEnumerator.Count();
101-
if (applyLimits && count > WafConstants.MaxMapOrArrayLength)
101+
if (applyLimits && count > WafConstants.MaxContainerSize)
102102
{
103-
Log.Warning<int, int>("EncodeList: list too long, it will be truncated, count: {Count}, MaxMapOrArrayLength {MaxMapOrArrayLength}", count, WafConstants.MaxMapOrArrayLength);
104-
objEnumerator = objEnumerator.Take(WafConstants.MaxMapOrArrayLength);
103+
Log.Warning<int, int>("EncodeList: list too long, it will be truncated, count: {Count}, MaxMapOrArrayLength {MaxMapOrArrayLength}", count, WafConstants.MaxContainerSize);
104+
objEnumerator = objEnumerator.Take(WafConstants.MaxContainerSize);
105105
}
106106

107107
foreach (var o in objEnumerator)
@@ -125,10 +125,10 @@ private Obj EncodeDictionary(IEnumerable<KeyValuePair<string, object>> objDictEn
125125

126126
var count = objDictEnumerator is IDictionary<string, object> objDict ? objDict.Count : objDictEnumerator.Count();
127127

128-
if (applyLimits && count > WafConstants.MaxMapOrArrayLength)
128+
if (applyLimits && count > WafConstants.MaxContainerSize)
129129
{
130-
Log.Warning<int, int>("EncodeDictionary: list too long, it will be truncated, count: {Count}, MaxMapOrArrayLength {MaxMapOrArrayLength}", count, WafConstants.MaxMapOrArrayLength);
131-
objDictEnumerator = objDictEnumerator.Take(WafConstants.MaxMapOrArrayLength);
130+
Log.Warning<int, int>("EncodeDictionary: list too long, it will be truncated, count: {Count}, MaxMapOrArrayLength {MaxMapOrArrayLength}", count, WafConstants.MaxContainerSize);
131+
objDictEnumerator = objDictEnumerator.Take(WafConstants.MaxContainerSize);
132132
}
133133

134134
foreach (var o in objDictEnumerator)

tracer/src/Datadog.Trace/AppSec/Waf/IResult.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,9 @@ internal interface IResult : IDisposable
1212
ReturnCode ReturnCode { get; }
1313

1414
string Data { get; }
15+
16+
ulong AggregatedTotalRuntime { get; }
17+
18+
ulong AggregatedTotalRuntimeWithBindings { get; }
1519
}
1620
}

tracer/src/Datadog.Trace/AppSec/Waf/IWaf.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
// </copyright>
55

66
using System;
7+
using Datadog.Trace.AppSec.Waf.ReturnTypesManaged;
78

89
namespace Datadog.Trace.AppSec.Waf
910
{
1011
internal interface IWaf : IDisposable
1112
{
1213
public Version Version { get; }
1314

15+
public bool InitializedSuccessfully { get; }
16+
17+
public InitializationResult InitializationResult { get; }
18+
1419
public IContext CreateContext();
1520
}
1621
}

tracer/src/Datadog.Trace/AppSec/Waf/Initialization/WafConfigurator.cs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using System.IO;
88
using System.Linq;
99
using Datadog.Trace.AppSec.Waf.NativeBindings;
10+
using Datadog.Trace.AppSec.Waf.ReturnTypesManaged;
1011
using Datadog.Trace.Logging;
12+
using Datadog.Trace.Util;
1113
using Datadog.Trace.Vendors.Newtonsoft.Json;
1214
using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;
1315
using Datadog.Trace.Vendors.Serilog.Events;
@@ -18,30 +20,55 @@ internal static class WafConfigurator
1820
{
1921
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(WafConfigurator));
2022

21-
internal static IntPtr? Configure(string rulesFile, WafNative wafNative, Encoder encoder)
23+
internal static InitializationResult Configure(string rulesFile, WafNative wafNative, Encoder encoder)
2224
{
2325
var argCache = new List<Obj>();
2426
var configObj = GetConfigObj(rulesFile, argCache, encoder);
2527
if (configObj == null)
2628
{
27-
return null;
29+
return InitializationResult.FromUnusableRuleFile();
2830
}
2931

32+
DdwafRuleSetInfoStruct ruleSetInfo = default;
33+
3034
try
3135
{
3236
DdwafConfigStruct args = default;
33-
var ruleHandle = wafNative.Init(configObj.RawPtr, ref args);
37+
var ruleHandle = wafNative.Init(configObj.RawPtr, ref args, ref ruleSetInfo);
3438
if (ruleHandle == IntPtr.Zero)
3539
{
3640
Log.Warning("DDAS-0005-00: WAF initialization failed.");
3741
}
3842

39-
return ruleHandle;
43+
var initResult = InitializationResult.From(ruleSetInfo, ruleHandle);
44+
if (initResult.LoadedRules == 0)
45+
{
46+
Log.Error("DDAS-0003-03: AppSec could not read the rule file {rulesFile}. Reason: All rules are invalid. AppSec will not run any protections in this application.", rulesFile);
47+
}
48+
else
49+
{
50+
Log.Information("DDAS-0015-00: AppSec loaded {loadedRules} from file {rulesFile}.", initResult.LoadedRules, rulesFile);
51+
}
52+
53+
if (initResult.HasErrors)
54+
{
55+
var sb = StringBuilderCache.Acquire(0);
56+
sb.Append($"WAF initialization failed. Some rules are invalid in rule file {rulesFile}:");
57+
foreach (var item in initResult.Errors)
58+
{
59+
sb.Append($"{item.Key}: [{string.Join(", ", item.Value)}] ");
60+
}
61+
62+
var errorMess = StringBuilderCache.GetStringAndRelease(sb);
63+
Log.Warning(errorMess);
64+
}
65+
66+
return initResult;
4067
}
4168
finally
4269
{
43-
wafNative.ObjectFree(configObj.RawPtr);
44-
70+
wafNative.RuleSetInfoFree(ref ruleSetInfo);
71+
wafNative.ObjectFreePtr(configObj.RawPtr);
4572
configObj.Dispose();
4673
foreach (var arg in argCache)
4774
{
@@ -97,10 +124,11 @@ private static void LogRuleDetailsIfDebugEnabled(JToken root)
97124
Log.Debug($"eventspropo {eventsProp.Count}");
98125
foreach (var ev in eventsProp)
99126
{
100-
var idProp = ev.Value<JValue>("id");
101-
var nameProp = ev.Value<JValue>("name");
102-
var addresses = ev.Value<JArray>("conditions").SelectMany(x => x.Value<JObject>("parameters").Value<JArray>("inputs"));
103-
Log.Debug("DDAS-0007-00: Loaded rule: {id} - {name} on addresses: {addresses}", idProp.Value, nameProp.Value, string.Join(", ", addresses));
127+
var emptyJValue = JValue.CreateString(string.Empty);
128+
var idProp = ev.Value<JValue>("id") ?? emptyJValue;
129+
var nameProp = ev.Value<JValue>("name") ?? emptyJValue;
130+
var addresses = ev.Value<JArray>("conditions")?.SelectMany(x => x.Value<JObject>("parameters")?.Value<JArray>("inputs"));
131+
Log.Debug("DDAS-0007-00: Loaded rule: {id} - {name} on addresses: {addresses}", idProp.Value, nameProp.Value, string.Join(", ", addresses ?? Enumerable.Empty<JToken>()));
104132
}
105133
}
106134
catch (Exception ex)

0 commit comments

Comments
 (0)