Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tracer/src/Datadog.Trace/AppSec/Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private Security(SecuritySettings settings = null, InstrumentationGateway instru
_settings.Enabled = _settings.Enabled && AreArchitectureAndOsSupported();
if (_settings.Enabled)
{
_powerWaf = powerWaf ?? Waf.Waf.Initialize();
_powerWaf = powerWaf ?? Waf.Waf.Initialize(_settings.Rules);
if (_powerWaf != null)
{
_agentWriter = agentWriter ?? new AppSecAgentWriter();
Expand Down
3 changes: 3 additions & 0 deletions tracer/src/Datadog.Trace/AppSec/SecuritySettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ public SecuritySettings(IConfigurationSource source)
// both should default to false
Enabled = source?.GetBool(ConfigurationKeys.AppSecEnabled) ?? false;
BlockingEnabled = source?.GetBool(ConfigurationKeys.AppSecBlockingEnabled) ?? false;
Rules = source?.GetString(ConfigurationKeys.AppSecRules);
}

public bool Enabled { get; set; }

public bool BlockingEnabled { get; }

public string Rules { get; }

public static SecuritySettings FromDefaultSources()
{
var source = GlobalSettings.CreateDefaultConfigurationSource();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ private static void ReadDlerror(string op)
if (errorPtr != IntPtr.Zero)
{
var error = Marshal.PtrToStringAnsi(errorPtr);
Log.Error($"Error during '{op}': {error}");
// warning, since in some cases dddlerror returns a message when an error didn't occur or was recoverable
Log.Warning("'{Op}' dddlerror returned: {Error}", op, error);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewlock took the opportunity to down grade to a warning, as discussed.

}
}

Expand Down
129 changes: 91 additions & 38 deletions tracer/src/Datadog.Trace/AppSec/Waf/Waf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Datadog.Trace.AppSec.Waf.NativeBindings;
using Datadog.Trace.Logging;
using Datadog.Trace.Vendors.Newtonsoft.Json;
using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;
using Datadog.Trace.Vendors.Serilog.Events;

namespace Datadog.Trace.AppSec.Waf
{
internal class Waf : IWaf
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(Waf));

private readonly WafHandle rule;
private readonly WafHandle wafHandle;
private bool disposed = false;

private Waf(WafHandle rule)
private Waf(WafHandle wafHandle)
{
this.rule = rule;
this.wafHandle = wafHandle;
}

~Waf()
Expand All @@ -39,15 +41,55 @@ public Version Version
}
}

public static Waf Initialize()
// null rulesFile means use rules embedded in the manifest
public static Waf Initialize(string rulesFile)
{
var rule = NewRule();
return rule == null ? null : new Waf(rule);
var argCache = new List<Obj>();
Obj configObj;
try
{
using var stream = GetRulesStream(rulesFile);

if (stream == null)
{
return null;
}

configObj = CreatObjFromRulesStream(argCache, stream);
}
catch (Exception ex)
{
if (rulesFile != null)
{
Log.Error(ex, "AppSec could not read the rule file \"{RulesFile}\" as it was invalid. AppSec will not run any protections in this application.", rulesFile);
}
else
{
Log.Error(ex, "AppSec could not read the rule file emmbeded in the manifest as it was invalid. AppSec will not run any protections in this application.");
}

return null;
}

try
{
DdwafConfigStruct args = default;
var ruleHandle = WafNative.Init(configObj.RawPtr, ref args);
return new Waf(new WafHandle(ruleHandle));
}
finally
{
configObj?.Dispose();
foreach (var arg in argCache)
{
arg.Dispose();
}
}
}

public IContext CreateContext()
{
var handle = WafNative.InitContext(rule.Handle, WafNative.ObjectFreeFuncPtr);
var handle = WafNative.InitContext(wafHandle.Handle, WafNative.ObjectFreeFuncPtr);
return new Context(handle);
}

Expand All @@ -66,53 +108,64 @@ public void Dispose(bool disposing)

disposed = true;

rule?.Dispose();
wafHandle?.Dispose();
}

private static Obj RuleSetFromManifest(List<Obj> argCache)
private static Obj CreatObjFromRulesStream(List<Obj> argCache, Stream stream)
{
var assembly = typeof(Waf).Assembly;
var resource = assembly.GetManifestResourceStream("Datadog.Trace.AppSec.Waf.rule-set.json");
using var reader = new JsonTextReader(new StreamReader(resource));
var root = JToken.ReadFrom(reader);
using var reader = new StreamReader(stream);
using var jsonReader = new JsonTextReader(reader);
var root = JToken.ReadFrom(jsonReader);

LogRuleDetailsIfDebugEnabled(root);

return Encoder.Encode(root, argCache);
}

private static WafHandle NewRule()
private static Stream GetRulesManifestStream()
{
try
{
DdwafConfigStruct args = default;

var argCache = new List<Obj>();
var rules = RuleSetFromManifest(argCache);
var assembly = typeof(Waf).Assembly;
return assembly.GetManifestResourceStream("Datadog.Trace.AppSec.Waf.rule-set.json");
}

var ruleHandle = WafNative.Init(rules.RawPtr, ref args);
private static Stream GetRulesFileStream(string rulesFile)
{
if (!File.Exists(rulesFile))
{
Log.Error("AppSec could not find the rules file in path \"{RulesFile}\". AppSec will not run any protections in this application.", rulesFile);
return null;
}

rules.Dispose();
foreach (var arg in argCache)
{
arg.Dispose();
}
return File.OpenRead(rulesFile);
}

if (ruleHandle == IntPtr.Zero)
private static void LogRuleDetailsIfDebugEnabled(JToken root)
{
if (Log.IsEnabled(LogEventLevel.Debug))
{
try
{
Log.Error("Failed to create rules.");
return null;
var eventsProp = root.Value<JArray>("events");
foreach (var ev in eventsProp)
{
var idProp = ev.Value<JValue>("id");
var nameProp = ev.Value<JValue>("name");
var addresses = ev.Value<JArray>("conditions").SelectMany(x => x.Value<JObject>("parameters").Value<JArray>("inputs"));
Log.Debug("Loaded rule: {id} - {name} on addresses: {addresses}", idProp.Value, nameProp.Value, string.Join(", ", addresses));
}
}
else
catch (Exception ex)
{
Log.Information("Rules successfully created.");
Log.Error(ex, "Error occured logging the ddwaf rules");
}

return new WafHandle(ruleHandle);
}
catch (Exception ex)
{
Log.Error(ex, "Error loading the power WAF rules");
return null;
}
}

private static Stream GetRulesStream(string rulesFile)
{
return string.IsNullOrWhiteSpace(rulesFile) ?
GetRulesManifestStream() :
GetRulesFileStream(rulesFile);
}
}
}
6 changes: 6 additions & 0 deletions tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public static class ConfigurationKeys
/// </summary>
public const string AppSecBlockingEnabled = "DD_APPSEC_BLOCKING_ENABLED";

/// <summary>
/// Override the default rules file provided. Must be a path to a valid JSON rules file.
/// Default is value is null (do not override).
/// </summary>
public const string AppSecRules = "DD_APPSEC_RULES";

/// <summary>
/// Configuration key for enabling or disabling the Tracer's debug mode.
/// Default is value is false (disabled).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Datadog.Trace.Configuration
public const string ApiKey = "DD_API_KEY";
public const string AppSecBlockingEnabled = "DD_APPSEC_BLOCKING_ENABLED";
public const string AppSecEnabled = "DD_APPSEC_ENABLED";
public const string AppSecRules = "DD_APPSEC_RULES";
public const string BufferSize = "DD_TRACE_BUFFER_SIZE";
public const string ConfigurationFileName = "DD_TRACE_CONFIG_FILE";
public const string CustomSamplingRules = "DD_TRACE_SAMPLING_RULES";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Datadog.Trace.Configuration
public const string ApiKey = "DD_API_KEY";
public const string AppSecBlockingEnabled = "DD_APPSEC_BLOCKING_ENABLED";
public const string AppSecEnabled = "DD_APPSEC_ENABLED";
public const string AppSecRules = "DD_APPSEC_RULES";
public const string BufferSize = "DD_TRACE_BUFFER_SIZE";
public const string ConfigurationFileName = "DD_TRACE_CONFIG_FILE";
public const string CustomSamplingRules = "DD_TRACE_SAMPLING_RULES";
Expand Down