Skip to content

Commit 9fb7812

Browse files
authored
[ASM] Support removing whole files from RC (#3862)
* support removing whole files from RC
1 parent 80f961f commit 9fb7812

File tree

7 files changed

+205
-56
lines changed

7 files changed

+205
-56
lines changed

tracer/src/Datadog.Trace/AppSec/RcmModels/RemoteConfigurationStatus.cs

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

66
using System.Collections.Generic;
77
using System.Collections.ObjectModel;
8+
using System.Linq;
89
using Datadog.Trace.AppSec.RcmModels.Asm;
910
using Datadog.Trace.AppSec.RcmModels.AsmData;
1011
using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;
@@ -13,11 +14,11 @@ namespace Datadog.Trace.AppSec.RcmModels;
1314

1415
internal class RemoteConfigurationStatus
1516
{
16-
internal List<RuleOverride> RulesOverrides { get; } = new();
17+
internal Dictionary<string, RuleOverride[]> RulesOverridesByFile { get; } = new();
1718

18-
internal List<RuleData> RulesData { get; } = new();
19+
internal Dictionary<string, RuleData[]> RulesDataByFile { get; } = new();
1920

20-
internal List<JToken> Exclusions { get; } = new();
21+
internal Dictionary<string, JArray> ExclusionsByFile { get; } = new();
2122

2223
internal IDictionary<string, Action> Actions { get; set; } = new Dictionary<string, Action>();
2324

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

Lines changed: 122 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Linq;
1010
using System.Threading;
1111
using Datadog.Trace.AppSec.RcmModels;
12+
using Datadog.Trace.AppSec.RcmModels.AsmData;
1213
using Datadog.Trace.AppSec.Waf;
1314
using Datadog.Trace.AppSec.Waf.Initialization;
1415
using Datadog.Trace.AppSec.Waf.NativeBindings;
@@ -283,27 +284,28 @@ private void SetRemoteConfigCapabilites()
283284

284285
private void AsmDDProductConfigChanged(object sender, ProductConfigChangedEventArgs e)
285286
{
286-
if (!_enabled) { return; }
287-
288287
var asmDd = e.GetConfigurationAsString().FirstOrDefault();
289288
if (!string.IsNullOrEmpty(asmDd.TypedFile))
290289
{
291290
_remoteConfigurationStatus.RemoteRulesJson = asmDd.TypedFile;
292-
var result = _waf?.UpdateRules(_remoteConfigurationStatus.RemoteRulesJson);
293-
WafRuleFileVersion = result?.RuleFileVersion;
294-
if (_wafInitResult?.Success ?? false)
291+
if (_enabled)
295292
{
296-
e.Acknowledge(asmDd.Name);
297-
}
298-
else
299-
{
300-
e.Error(asmDd.Name, "An error happened updating waf rules");
293+
var result = _waf?.UpdateRules(_remoteConfigurationStatus.RemoteRulesJson);
294+
WafRuleFileVersion = result?.RuleFileVersion;
295+
if (_wafInitResult?.Success ?? false)
296+
{
297+
e.Acknowledge(asmDd.Name);
298+
}
299+
else
300+
{
301+
e.Error(asmDd.Name, "An error happened updating waf rules");
302+
}
303+
304+
return;
301305
}
302306
}
303-
else
304-
{
305-
e.Acknowledge(asmDd.Name);
306-
}
307+
308+
e.Acknowledge(asmDd.Name);
307309
}
308310

309311
private void FeaturesProductConfigChanged(object sender, ProductConfigChangedEventArgs e)
@@ -325,30 +327,60 @@ private void FeaturesProductConfigChanged(object sender, ProductConfigChangedEve
325327
e.Acknowledge(features.Name);
326328
}
327329

328-
private void AsmDataProductConfigChanged(object sender, ProductConfigChangedEventArgs e)
330+
private void AsmDataProductConfigRemoved(object sender, ProductConfigChangedEventArgs e)
329331
{
330-
if (!_enabled)
332+
var asmDataConfigs = e.GetDeserializedConfigurations<RcmModels.AsmData.Payload>();
333+
foreach (var asmDataConfig in asmDataConfigs)
331334
{
332-
return;
335+
if (_remoteConfigurationStatus.RulesDataByFile.ContainsKey(asmDataConfig.Name))
336+
{
337+
_remoteConfigurationStatus.RulesDataByFile.Remove(asmDataConfig.Name);
338+
}
339+
}
340+
341+
var updated = true;
342+
if (_enabled)
343+
{
344+
var ruleData = _remoteConfigurationStatus.RulesDataByFile.SelectMany(x => x.Value).ToList();
345+
updated = UpdateWafWithRulesData(ruleData);
333346
}
334347

348+
foreach (var asmDataConfig in asmDataConfigs)
349+
{
350+
if (!updated)
351+
{
352+
e.Error(asmDataConfig.Name, "Waf could not remove the rules data");
353+
}
354+
else
355+
{
356+
e.Acknowledge(asmDataConfig.Name);
357+
}
358+
}
359+
}
360+
361+
private void AsmDataProductConfigChanged(object sender, ProductConfigChangedEventArgs e)
362+
{
335363
var asmDataConfigs = e.GetDeserializedConfigurations<RcmModels.AsmData.Payload>();
336364
foreach (var asmDataConfig in asmDataConfigs)
337365
{
338366
if (asmDataConfig.TypedFile?.RulesData?.Length > 0)
339367
{
340-
_remoteConfigurationStatus.RulesData.AddRange(asmDataConfig.TypedFile.RulesData);
368+
_remoteConfigurationStatus.RulesDataByFile[asmDataConfig.Name] = asmDataConfig.TypedFile.RulesData;
341369
}
370+
}
342371

343-
e.Acknowledge(asmDataConfig.Name);
372+
var updated = true;
373+
if (_enabled)
374+
{
375+
var ruleData = _remoteConfigurationStatus.RulesDataByFile.SelectMany(x => x.Value).ToList();
376+
updated = UpdateWafWithRulesData(ruleData);
344377
}
345378

346-
var updated = UpdateWafWithRulesData();
347379
foreach (var asmDataConfig in asmDataConfigs)
348380
{
349381
if (!updated)
350382
{
351-
e.Error(asmDataConfig.Name, "Waf could not update the rules");
383+
e.Error(asmDataConfig.Name, "Waf could not update the rules data");
352384
}
353385
else
354386
{
@@ -357,25 +389,64 @@ private void AsmDataProductConfigChanged(object sender, ProductConfigChangedEven
357389
}
358390
}
359391

360-
private void AsmProductConfigChanged(object sender, ProductConfigChangedEventArgs e)
392+
private void AsmProductConfigRemoved(object sender, ProductConfigChangedEventArgs e)
361393
{
362-
if (!_enabled) { return; }
363-
364394
var asmConfigs = e.GetDeserializedConfigurations<RcmModels.Asm.Payload>();
365395

366-
_remoteConfigurationStatus.RulesOverrides.Clear();
367-
_remoteConfigurationStatus.Exclusions.Clear();
396+
foreach (var asmConfig in asmConfigs)
397+
{
398+
if (_remoteConfigurationStatus.RulesOverridesByFile.ContainsKey(asmConfig.Name))
399+
{
400+
_remoteConfigurationStatus.RulesOverridesByFile.Remove(asmConfig.Name);
401+
}
402+
403+
if (_remoteConfigurationStatus.ExclusionsByFile.ContainsKey(asmConfig.Name))
404+
{
405+
_remoteConfigurationStatus.ExclusionsByFile.Remove(asmConfig.Name);
406+
}
407+
}
408+
409+
var result = true;
410+
if (_enabled)
411+
{
412+
var overrides = _remoteConfigurationStatus.RulesOverridesByFile.SelectMany(x => x.Value).ToList();
413+
var exclusions = _remoteConfigurationStatus.ExclusionsByFile.SelectMany(x => x.Value).ToList();
414+
415+
result = _waf.UpdateRulesStatus(overrides, exclusions);
416+
Log.Debug<bool, int, int>(
417+
"_waf.Update was updated for removal: {Success}, ({RulesOverridesCount} rule status entries), ({ExclusionsCount} exclusion filters)",
418+
result,
419+
overrides.Count,
420+
exclusions.Count);
421+
}
422+
423+
foreach (var asmConfig in asmConfigs)
424+
{
425+
if (result)
426+
{
427+
e.Acknowledge(asmConfig.Name);
428+
}
429+
else
430+
{
431+
e.Error(asmConfig.Name, "waf couldn't be remove with rule asm product");
432+
}
433+
}
434+
}
435+
436+
private void AsmProductConfigChanged(object sender, ProductConfigChangedEventArgs e)
437+
{
438+
var asmConfigs = e.GetDeserializedConfigurations<RcmModels.Asm.Payload>();
368439

369440
foreach (var asmConfig in asmConfigs)
370441
{
371442
if (asmConfig.TypedFile.RuleOverrides?.Length > 0)
372443
{
373-
_remoteConfigurationStatus.RulesOverrides.AddRange(asmConfig.TypedFile.RuleOverrides);
444+
_remoteConfigurationStatus.RulesOverridesByFile[asmConfig.Name] = asmConfig.TypedFile.RuleOverrides;
374445
}
375446

376447
if (asmConfig.TypedFile.Exclusions?.Count > 0)
377448
{
378-
_remoteConfigurationStatus.Exclusions.AddRange(asmConfig.TypedFile.Exclusions);
449+
_remoteConfigurationStatus.ExclusionsByFile[asmConfig.Name] = asmConfig.TypedFile.Exclusions;
379450
}
380451

381452
if (asmConfig.TypedFile.Actions != null)
@@ -395,12 +466,19 @@ private void AsmProductConfigChanged(object sender, ProductConfigChangedEventArg
395466
}
396467
}
397468

398-
var result = _waf.UpdateRulesStatus(_remoteConfigurationStatus.RulesOverrides, _remoteConfigurationStatus.Exclusions);
399-
Log.Debug<bool, int, int>(
400-
"_waf.Update was updated: {Success}, ({RulesOverridesCount} rule status entries), ({ExclusionsCount} exclusion filters)",
401-
result,
402-
_remoteConfigurationStatus.RulesOverrides.Count,
403-
_remoteConfigurationStatus.Exclusions.Count);
469+
var result = true;
470+
if (_enabled)
471+
{
472+
var overrides = _remoteConfigurationStatus.RulesOverridesByFile.SelectMany(x => x.Value).ToList();
473+
var exclusions = _remoteConfigurationStatus.ExclusionsByFile.SelectMany(x => x.Value).ToList();
474+
475+
result = _waf.UpdateRulesStatus(overrides, exclusions);
476+
Log.Debug<bool, int, int>(
477+
"_waf.Update was updated for change: {Success}, ({RulesOverridesCount} rule status entries), ({ExclusionsCount} exclusion filters)",
478+
result,
479+
overrides.Count,
480+
exclusions.Count);
481+
}
404482

405483
foreach (var asmConfig in asmConfigs)
406484
{
@@ -410,12 +488,12 @@ private void AsmProductConfigChanged(object sender, ProductConfigChangedEventArg
410488
}
411489
else
412490
{
413-
e.Error(asmConfig.Name, "waf couldn't be updated with rule overrides");
491+
e.Error(asmConfig.Name, "waf couldn't be updated with asm product");
414492
}
415493
}
416494
}
417495

418-
private bool UpdateWafWithRulesData() => _waf?.UpdateRulesData(_remoteConfigurationStatus.RulesData) ?? false;
496+
private bool UpdateWafWithRulesData(List<RuleData> ruleData) => _waf?.UpdateRulesData(ruleData) ?? false;
419497

420498
private void InitWafAndInstrumentations(bool fromRemoteConfig = false)
421499
{
@@ -441,7 +519,8 @@ private void InitWafAndInstrumentations(bool fromRemoteConfig = false)
441519
_waf = _wafInitResult.Waf;
442520
oldWaf?.Dispose();
443521
Log.Debug("Disposed old waf and affected new waf");
444-
UpdateWafWithRulesData();
522+
var ruleData = _remoteConfigurationStatus.RulesDataByFile.SelectMany(x => x.Value).ToList();
523+
UpdateWafWithRulesData(ruleData);
445524
AddInstrumentationsAndProducts(fromRemoteConfig);
446525
}
447526
else
@@ -464,6 +543,8 @@ private void AddInstrumentationsAndProducts(bool fromRemoteConfig)
464543
{
465544
AsmRemoteConfigurationProducts.AsmDataProduct.ConfigChanged += AsmDataProductConfigChanged;
466545
AsmRemoteConfigurationProducts.AsmProduct.ConfigChanged += AsmProductConfigChanged;
546+
AsmRemoteConfigurationProducts.AsmDataProduct.ConfigRemoved += AsmDataProductConfigRemoved;
547+
AsmRemoteConfigurationProducts.AsmProduct.ConfigRemoved += AsmProductConfigRemoved;
467548
AddAppsecSpecificInstrumentations();
468549

469550
_rateLimiter ??= new AppSecRateLimiter(_settings.TraceRateLimit);
@@ -480,6 +561,8 @@ private void RemoveInstrumentationsAndProducts(bool fromRemoteConfig)
480561
{
481562
AsmRemoteConfigurationProducts.AsmDataProduct.ConfigChanged -= AsmDataProductConfigChanged;
482563
AsmRemoteConfigurationProducts.AsmProduct.ConfigChanged -= AsmProductConfigChanged;
564+
AsmRemoteConfigurationProducts.AsmDataProduct.ConfigRemoved -= AsmDataProductConfigRemoved;
565+
AsmRemoteConfigurationProducts.AsmProduct.ConfigRemoved -= AsmProductConfigRemoved;
483566
RemoveAppsecSpecificInstrumentations();
484567

485568
_enabled = false;
@@ -508,6 +591,8 @@ private void RunShutdown()
508591
{
509592
AsmRemoteConfigurationProducts.AsmDataProduct.ConfigChanged -= AsmDataProductConfigChanged;
510593
AsmRemoteConfigurationProducts.AsmProduct.ConfigChanged -= AsmProductConfigChanged;
594+
AsmRemoteConfigurationProducts.AsmDataProduct.ConfigRemoved -= AsmDataProductConfigRemoved;
595+
AsmRemoteConfigurationProducts.AsmProduct.ConfigRemoved -= AsmProductConfigRemoved;
511596
AsmRemoteConfigurationProducts.AsmFeaturesProduct.ConfigChanged -= FeaturesProductConfigChanged;
512597
AsmRemoteConfigurationProducts.AsmDDProduct.ConfigChanged -= AsmDDProductConfigChanged;
513598
Dispose();

tracer/src/Datadog.Trace/RemoteConfigurationManagement/RemoteConfigurationManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ private void ProcessResponse(GetRcmResponse response, IDictionary<string, Produc
322322
try
323323
{
324324
var removedConfigurations = GetRemovedConfigurations(product);
325+
325326
if (removedConfigurations is null)
326327
{
327328
continue;

tracer/test/Datadog.Trace.Security.IntegrationTests/Rcm/AspNetCore5AsmRulesToggle.cs

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

66
#if NETCOREAPP3_0_OR_GREATER
77

8+
using System;
89
using System.Collections.Generic;
910
using System.Collections.Immutable;
1011
using System.Threading.Tasks;
@@ -38,28 +39,38 @@ public async Task TestRulesToggling()
3839
var ruleId = "crs-942-290";
3940

4041
var spans0 = await SendRequestsAsync(agent, url);
41-
var acknowledgedId = nameof(TestRulesToggling);
42+
var acknowledgedId = nameof(TestRulesToggling) + Guid.NewGuid();
4243

4344
var request1 = await agent.SetupRcmAndWait(Output, new[] { ((object)new Payload { RuleOverrides = new[] { new RuleOverride { Id = ruleId, Enabled = false } } }, acknowledgedId) }, ASMProduct, appliedServiceNames: new[] { acknowledgedId });
44-
CheckAckState(request1, ASMProduct, ApplyStates.ACKNOWLEDGED, null, "First RCM call");
45+
CheckAckState(request1, ASMProduct, 1, ApplyStates.ACKNOWLEDGED, null, "First RCM call");
4546

4647
var spans1 = await SendRequestsAsync(agent, url);
4748

48-
var acknowledgedId2 = nameof(TestRulesToggling) + 2;
49+
var acknowledgedId2 = nameof(TestRulesToggling) + Guid.NewGuid();
4950
var request2 = await agent.SetupRcmAndWait(Output, new[] { ((object)new Payload { RuleOverrides = new[] { new RuleOverride { Id = ruleId, Enabled = true } } }, acknowledgedId2) }, ASMProduct, appliedServiceNames: new[] { acknowledgedId2 });
50-
CheckAckState(request2, ASMProduct, ApplyStates.ACKNOWLEDGED, null, "Second RCM call");
51+
CheckAckState(request2, ASMProduct, 1, ApplyStates.ACKNOWLEDGED, null, "Second RCM call");
5152
var spans2 = await SendRequestsAsync(agent, url);
5253

53-
var acknowledgedId3 = nameof(TestRulesToggling) + 3;
54-
var request3 = await agent.SetupRcmAndWait(Output, new[] { ((object)new Payload { RuleOverrides = new[] { new RuleOverride { Id = ruleId, Enabled = true, OnMatch = new[] { "block" } } } }, acknowledgedId3) }, ASMProduct, appliedServiceNames: new[] { acknowledgedId3 });
55-
CheckAckState(request3, ASMProduct, ApplyStates.ACKNOWLEDGED, null, "Third RCM call");
54+
var acknowledgedId3 = nameof(TestRulesToggling) + Guid.NewGuid();
55+
var acknowledgedId4 = nameof(TestRulesToggling) + Guid.NewGuid();
56+
var payload1 = ((object)new Payload { RuleOverrides = new[] { new RuleOverride { Id = ruleId, Enabled = true } } }, acknowledgedId3);
57+
var payload2 = ((object)new Payload { RuleOverrides = new[] { new RuleOverride { Id = ruleId, OnMatch = new[] { "block" } } } }, acknowledgedId4);
58+
var request3 = await agent.SetupRcmAndWait(Output, new[] { payload1, payload2 }, ASMProduct, appliedServiceNames: new[] { acknowledgedId3, acknowledgedId4 });
59+
CheckAckState(request3, ASMProduct, 2, ApplyStates.ACKNOWLEDGED, null, "Third RCM call");
5660
var spans3 = await SendRequestsAsync(agent, url);
5761

62+
var acknowledgedId5 = nameof(TestRulesToggling) + Guid.NewGuid();
63+
var payload3 = ((object)new Payload { RuleOverrides = new[] { new RuleOverride { Id = ruleId, Enabled = true } } }, acknowledgedId5);
64+
var request4 = await agent.SetupRcmAndWait(Output, new[] { payload3 }, ASMProduct, appliedServiceNames: new[] { acknowledgedId5 });
65+
CheckAckState(request4, ASMProduct, 1, ApplyStates.ACKNOWLEDGED, null, "Forth RCM call");
66+
var spans4 = await SendRequestsAsync(agent, url);
67+
5868
var spans = new List<MockSpan>();
5969
spans.AddRange(spans0);
6070
spans.AddRange(spans1);
6171
spans.AddRange(spans2);
6272
spans.AddRange(spans3);
73+
spans.AddRange(spans4);
6374

6475
await VerifySpans(spans.ToImmutableList(), settings);
6576
}

tracer/test/Datadog.Trace.Security.IntegrationTests/Rcm/AspNetCore5AsmToggle.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,17 @@ public async Task TestSecurityToggling()
6969

7070
var request1 = await agent.SetupRcmAndWait(Output, new[] { ((object)new AsmFeatures { Asm = new AsmFeature { Enabled = false } }, acknowledgedId) }, "ASM_FEATURES", "first", new[] { acknowledgedId });
7171

72-
RcmBase.CheckAckState(request1, "ASM_FEATURES", expectedState, null, "First RCM call");
72+
RcmBase.CheckAckState(request1, "ASM_FEATURES", 1, expectedState, null, "First RCM call");
7373
request1.Client.State.BackendClientState.Should().Be("first");
7474

75-
RcmBase.CheckAckState(request1, "ASM_FEATURES", expectedState, null, "First RCM call");
75+
RcmBase.CheckAckState(request1, "ASM_FEATURES", 1, expectedState, null, "First RCM call");
7676

7777
var spans2 = await SendRequestsAsync(agent, url);
7878
var acknowledgedId2 = nameof(TestSecurityToggling) + Guid.NewGuid();
7979

8080
var request2 = await agent.SetupRcmAndWait(Output, new[] { ((object)new AsmFeatures { Asm = new AsmFeature { Enabled = true } }, acknowledgedId2) }, "ASM_FEATURES", "second", new[] { acknowledgedId2 });
8181

82-
RcmBase.CheckAckState(request2, "ASM_FEATURES", expectedState, null, "Second RCM call");
82+
RcmBase.CheckAckState(request2, "ASM_FEATURES", 1, expectedState, null, "Second RCM call");
8383

8484
var request3 = await agent.WaitRcmRequestAndReturnLast(appliedServiceNames: new[] { acknowledgedId2 });
8585
request3.Client.State.BackendClientState.Should().Be("second");
@@ -116,7 +116,7 @@ public async Task TestRemoteConfigError()
116116

117117
var request = await agent.SetupRcmAndWait(Output, new[] { ((object)"haha, you weren't expect this!", acknowledgedId) }, "ASM_FEATURES", appliedServiceNames: new[] { acknowledgedId });
118118

119-
RcmBase.CheckAckState(request, "ASM_FEATURES", ApplyStates.ERROR, "Error converting value \"haha, you weren't expect this!\" to type 'Datadog.Trace.AppSec.AsmFeatures'. Path '', line 1, position 32.", "First RCM call");
119+
RcmBase.CheckAckState(request, "ASM_FEATURES", 1, ApplyStates.ERROR, "Error converting value \"haha, you weren't expect this!\" to type 'Datadog.Trace.AppSec.AsmFeatures'. Path '', line 1, position 32.", "First RCM call");
120120

121121
await VerifySpans(spans1.ToImmutableList(), settings);
122122
}

0 commit comments

Comments
 (0)