Skip to content

Commit

Permalink
[#29] Add mask options
Browse files Browse the repository at this point in the history
  • Loading branch information
sandermvanvliet committed Jan 7, 2024
1 parent 0dc043f commit dfcfa19
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 6 deletions.
11 changes: 11 additions & 0 deletions src/Serilog.Enrichers.Sensitive/MaskOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Serilog.Enrichers.Sensitive;

public class MaskOptions
{
public static readonly MaskOptions Default= new();
public const int NotSet = -1;
public int ShowFirst { get; set; } = NotSet;
public int ShowLast { get; set; } = NotSet;
public bool PreserveLength { get; set; } = true;

}
35 changes: 35 additions & 0 deletions src/Serilog.Enrichers.Sensitive/MaskPropertyCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Generic;

namespace Serilog.Enrichers.Sensitive;

public class MaskPropertyCollection : List<MaskProperty>
{
private readonly Dictionary<string, MaskOptions> _properties = new();

public void Add(string propertyName)
{
_properties.Add(propertyName.ToLower(), MaskOptions.Default);
}

public void Add(string propertyName, MaskOptions maskOptions)
{
_properties.Add(propertyName.ToLower(), maskOptions);
}

public bool TryGetProperty(string propertyName, out MaskOptions options)
{
return _properties.TryGetValue(propertyName.ToLower(), out options);
}

public static MaskPropertyCollection From(IEnumerable<string> enricherOptionsMaskProperties)
{
var collection = new MaskPropertyCollection();

foreach (var x in enricherOptionsMaskProperties)
{
collection.Add(x, MaskOptions.Default);
}

return collection;
}
}
115 changes: 112 additions & 3 deletions src/Serilog.Enrichers.Sensitive/SensitiveDataEnricher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ internal class SensitiveDataEnricher : ILogEventEnricher
{
private readonly MaskingMode _maskingMode;
public const string DefaultMaskValue = "***MASKED***";
private const string DefaultMaskPad = "***";

private static readonly MessageTemplateParser Parser = new();
private readonly FieldInfo _messageTemplateBackingField;
private readonly List<IMaskingOperator> _maskingOperators;
private readonly string _maskValue;
private readonly List<string> _maskProperties;
private readonly MaskPropertyCollection _maskProperties;
private readonly List<string> _excludeProperties;

public SensitiveDataEnricher(SensitiveDataEnricherOptions options)
Expand Down Expand Up @@ -113,9 +114,22 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
return (false, null);
}

if (_maskProperties.Contains(property.Key, StringComparer.InvariantCultureIgnoreCase))
if(_maskProperties.TryGetProperty(property.Key, out var options))
{
return (true, new ScalarValue(_maskValue));
if (options == MaskOptions.Default)
{
return (true, new ScalarValue(_maskValue));
}

switch (property.Value)
{
case ScalarValue { Value: string stringValue }:
return (true, new ScalarValue(MaskWithOptions(_maskValue, options, stringValue)));
case ScalarValue { Value: Uri uriValue } when options is UriMaskOptions uriMaskOptions:
return (true, new ScalarValue(MaskWithUriOptions(_maskValue, uriMaskOptions, uriValue)));
case ScalarValue { Value: Uri uriValue }:
return (true, new ScalarValue(MaskWithOptions(_maskValue, options, uriValue.ToString())));
}
}

switch (property.Value)
Expand Down Expand Up @@ -213,6 +227,101 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
}
}

private string MaskWithUriOptions(string maskValue, UriMaskOptions options, Uri uri)
{
var scheme = options.ShowScheme
? uri.Scheme
: DefaultMaskPad;

var host = options.ShowHost
? uri.Host
: DefaultMaskPad;

var path = options.ShowPath
? uri.AbsolutePath
: "/" + DefaultMaskPad;

var queryString = !string.IsNullOrEmpty(uri.Query)
? options.ShowQueryString
? uri.Query
: "?" + DefaultMaskPad
: "";

return $"{scheme}://{host}{path}{queryString}";
}

private string MaskWithOptions(string maskValue, MaskOptions options, string input)
{
if (options is { ShowFirst: >= 0, ShowLast: < 0 })
{
var start = options.ShowFirst;
if (start >= input.Length)
{
start = 1;
}

var first = input.Substring(0, start);
if (options.PreserveLength)
{
return first.PadRight(input.Length, '*');
}

return first + DefaultMaskPad;
}

if (options is { ShowFirst: < 0, ShowLast: >= 0 })
{
var end = input.Length - options.ShowLast;
if (end <= 0)
{
end = input.Length - 1;
}

var last = input.Substring(end);

if (options.PreserveLength)
{
return last.PadLeft(input.Length, '*');
}

return DefaultMaskPad + last;
}

if (options is { ShowFirst: >= 0, ShowLast: >= 0 })
{
if (options.ShowFirst + options.ShowLast >= input.Length)
{
if (input.Length > 3)
{
return input[0] + DefaultMaskPad + input.Last();
}

if (input.Length == 3)
{
return input[0] + DefaultMaskPad;
}

if (input.Length <= 2)
{
return maskValue;
}
}

var start = options.ShowFirst;
var end = input.Length - options.ShowLast;
var pad = input.Length - (input.Length - end);

if (options.PreserveLength)
{
return input.Substring(0, start).PadRight(pad, '*') + input.Substring(end);
}

return input.Substring(0, start) + DefaultMaskPad + input.Substring(end);
}

return maskValue;
}

private (bool, string) ReplaceSensitiveDataFromString(string input)
{
var isMasked = false;
Expand Down
21 changes: 18 additions & 3 deletions src/Serilog.Enrichers.Sensitive/SensitiveDataEnricherOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public SensitiveDataEnricherOptions(
Mode = mode;
MaskValue = maskValue;
MaskingOperators = maskingOperators == null ? new List<IMaskingOperator>() : ResolveMaskingOperators(maskingOperators);
MaskProperties = maskProperties?.ToList() ?? new List<string>();
MaskProperties = maskProperties == null ? new MaskPropertyCollection() : MaskPropertyCollection.From(maskProperties);
ExcludeProperties = excludeProperties?.ToList() ?? new List<string>();
}

Expand Down Expand Up @@ -90,15 +90,15 @@ private static List<IMaskingOperator> ResolveMaskingOperators(IEnumerable<string
/// The list of properties that should always be masked regardless of whether they match the pattern of any of the masking operators
/// </summary>
/// <remarks>The property name is case-insensitive, when the property is present on the log message it will always be masked even if it is empty</remarks>
public List<string> MaskProperties { get; set; } = new List<string>();
public MaskPropertyCollection MaskProperties { get; set; } = new();
/// <summary>
/// The list of properties that should never be masked
/// </summary>
/// <remarks>
/// <para>The property name is case-insensitive, when the property is present on the log message it will always be masked even if it is empty.</para>
/// <para>This property takes precedence over <see cref="MaskProperties"/> and the masking operators.</para>
/// </remarks>
public List<string> ExcludeProperties { get; set; } = new List<string>();
public List<string> ExcludeProperties { get; set; } = new();

/// <remarks>
/// This property only exists to support JSON configuration of the enricher. If you are configuring the enricher from code you'll want <see cref="MaskingOperators"/> instead.
Expand Down Expand Up @@ -130,4 +130,19 @@ public void Apply(SensitiveDataEnricherOptions other)
other.Operators = Operators;
}
}

public class MaskProperty
{
public MaskProperty()
{
}

public MaskProperty(string propertyName)
{
Name = propertyName;
}

public string Name { get; set; }
public MaskOptions Options { get; set; } = MaskOptions.Default;
}
}
9 changes: 9 additions & 0 deletions src/Serilog.Enrichers.Sensitive/UriMaskOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Serilog.Enrichers.Sensitive;

public class UriMaskOptions : MaskOptions
{
public bool ShowScheme { get; set; } = true;
public bool ShowHost { get; set; } = true;
public bool ShowPath { get; set; } = false;
public bool ShowQueryString { get; set; } = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using Serilog.Sinks.InMemory;
using Serilog.Sinks.InMemory.Assertions;
using Xunit;

namespace Serilog.Enrichers.Sensitive.Tests.Unit
{
public class WhenMaskingWithOptions
{
[Theory]
[InlineData("1234567890", 2, 2, true, "12******90")]
[InlineData("1234567890", 2, 2, false, "12***90")]
[InlineData("1234567890", 2, -5, false, "12***")]
[InlineData("1234567890", 2, -5, true, "12********")]
[InlineData("1234567890", -5, 2, false, "***90")]
[InlineData("1234567890", -5, 2, true, "********90")]
[InlineData("1234", 2, 2, true, "1***4")]
[InlineData("1234", 2, 3, true, "1***4")]
[InlineData("124", 2, 2, true, "1***")]
[InlineData("12", 2, 2, true, SensitiveDataEnricher.DefaultMaskValue)]
[InlineData("1234", 5, MaskOptions.NotSet, true, "1***")]
[InlineData("1234", MaskOptions.NotSet, 5, true, "***4")]
public void GivenMaskOptionsWithInputShorterThanNumberOfCharactersThatShouldBeShown(string input, int showFirst, int showLast, bool preserveLength, string expectedValue)
{
var inMemorySink = new InMemorySink();
var logger = new LoggerConfiguration()
.Enrich.WithSensitiveDataMasking(
options => options.MaskProperties.Add("Prop", new MaskOptions{ ShowFirst = showFirst, ShowLast = showLast, PreserveLength = preserveLength}))
.WriteTo.Sink(inMemorySink)
.CreateLogger();

logger.Information("{Prop}", input);

inMemorySink
.Should()
.HaveMessage("{Prop}")
.Appearing().Once()
.WithProperty("Prop")
.WithValue(expectedValue);
}

[Theory]
[InlineData(true, false ,false, false, "https://***/***?***")]
[InlineData(true, true ,false, false, "https://example.com/***?***")]
[InlineData(true, true,true, false, "https://example.com/some/sensitive/path?***")]
[InlineData(true, false,true, true, "https://***/some/sensitive/path?foo=bar")]
[InlineData(false, false,true, true, "***://***/some/sensitive/path?foo=bar")]
[InlineData(false, false,true, false, "***://***/some/sensitive/path?***")]
public void GivenUriMaskOptions(bool showScheme, bool showHost, bool showPath, bool showQuery,
string expectedValue)
{
var inMemorySink = new InMemorySink();
var logger = new LoggerConfiguration()
.Enrich.WithSensitiveDataMasking(
options => options.MaskProperties.Add("Prop", new UriMaskOptions
{
ShowScheme = showScheme,
ShowHost = showHost,
ShowPath = showPath,
ShowQueryString = showQuery
}))
.WriteTo.Sink(inMemorySink)
.CreateLogger();

logger.Information("{Prop}", new Uri("https://example.com/some/sensitive/path?foo=bar"));

inMemorySink
.Should()
.HaveMessage("{Prop}")
.Appearing().Once()
.WithProperty("Prop")
.WithValue(expectedValue);
}
}
}

0 comments on commit dfcfa19

Please sign in to comment.