Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature / Pass logPropertyName into MaskingOperators #37

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public EmailAddressMaskingOperator() : base(EmailPattern, RegexOptions.IgnoreCas
{
}

protected override string PreprocessInput(string input)
protected override string PreprocessInput(string input, string? propertyName = null)
{
if (input.Contains("%40"))
{
Expand All @@ -20,7 +20,7 @@ protected override string PreprocessInput(string input)
return input;
}

protected override bool ShouldMaskInput(string input)
protected override bool ShouldMaskInput(string input, string? propertyName = null)
{
return input.Contains("@");
}
Expand Down
3 changes: 2 additions & 1 deletion src/Serilog.Enrichers.Sensitive/IMaskingOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

public interface IMaskingOperator
{
MaskingResult Mask(string input, string mask);
MaskingResult MaskProperty(string propertyName, string input, string mask);
MaskingResult MaskMessage(string input, string mask);
}
}
83 changes: 56 additions & 27 deletions src/Serilog.Enrichers.Sensitive/RegexMaskingOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public abstract class RegexMaskingOperator : IMaskingOperator
{
private readonly Regex _regex;

protected RegexMaskingOperator(string regexString)
: this(regexString, RegexOptions.Compiled)
protected RegexMaskingOperator(string regexString)
: this(regexString, RegexOptions.Compiled)
{
}

Expand All @@ -25,7 +25,36 @@ protected RegexMaskingOperator(string regexString, RegexOptions options)
}
}

public MaskingResult Mask(string input, string mask)
public MaskingResult MaskProperty(string propertyName, string input, string mask)
{
var preprocessedInput = PreprocessInput(input, propertyName);

if (!ShouldMaskInput(preprocessedInput, propertyName))
{
return MaskingResult.NoMatch;
}

var maskedResult = _regex.Replace(preprocessedInput, match =>
{
if (ShouldMaskMatch(match))
{
return match.Result(PreprocessMask(PreprocessMask(mask), match));
}

return match.Value;
});

var result = new MaskingResult
{
Result = maskedResult,
Match = maskedResult != input
};

return result;
}

public MaskingResult MaskMessage(string input, string mask)

{
var preprocessedInput = PreprocessInput(input);

Expand All @@ -34,16 +63,16 @@ public MaskingResult Mask(string input, string mask)
return MaskingResult.NoMatch;
}

var maskedResult = _regex.Replace(preprocessedInput, match =>
{
if (ShouldMaskMatch(match))
{
return match.Result(PreprocessMask(PreprocessMask(mask), match));
}
var maskedResult = _regex.Replace(preprocessedInput, match =>
{
if (ShouldMaskMatch(match))
{
return match.Result(PreprocessMask(PreprocessMask(mask), match));
}

return match.Value;
});

return match.Value;
});

var result = new MaskingResult
{
Result = maskedResult,
Expand All @@ -59,37 +88,37 @@ public MaskingResult Mask(string input, string mask)
/// <param name="input">The message template or the value of a property on the log event</param>
/// <returns><c>true</c> when the input should be masked, otherwise <c>false</c>. Defaults to <c>true</c></returns>
/// <remarks>This method provides an extension point to short-circuit the masking operation before the regular expression matching is performed</remarks>
protected virtual bool ShouldMaskInput(string input) => true;
protected virtual bool ShouldMaskInput(string input, string? propertyName = null) => true;

/// <summary>
/// Perform any operations on the input value before masking the input
/// </summary>
/// <param name="input">The message template or the value of a property on the log event</param>
/// <param name="input">The message template or the value of a property on the log event</param>
/// <returns>The processed input, defaults to no pre-processing and returns the input</returns>
/// <remarks>Use this method if the input is encoded using URL encoding for example</remarks>
protected virtual string PreprocessInput(string input) => input;
protected virtual string PreprocessInput(string input, string? propertyName = null) => input;

/// <summary>
/// Perform any operations on the mask before masking the matched value
/// </summary>
/// <param name="mask">The mask value as specified on the <see cref="SensitiveDataEnricherOptions"/></param>
/// <returns>The processed mask, defaults to no pre-processing and returns the input</returns>
/// <returns>The processed mask, defaults to no pre-processing and returns the input</returns>
protected virtual string PreprocessMask(string mask) => mask;

/// <summary>
/// Perform any operations on the mask before masking the matched value
/// </summary>
/// <param name="mask">The mask value as specified on the <see cref="SensitiveDataEnricherOptions"/></param>
/// <param name="match">The regex match</param>
/// <returns>The processed mask, defaults to no pre-processing and returns the input</returns>
protected virtual string PreprocessMask(string mask, Match match) => mask;
/// <summary>
/// Perform any operations on the mask before masking the matched value
/// </summary>
/// <param name="mask">The mask value as specified on the <see cref="SensitiveDataEnricherOptions"/></param>
/// <param name="match">The regex match</param>
/// <returns>The processed mask, defaults to no pre-processing and returns the input</returns>
protected virtual string PreprocessMask(string mask, Match match) => mask;

/// <summary>
/// Indicate whether the operator should continue with masking the matched value from the input
/// </summary>
/// <param name="match">The match found by the regular expression of this operator</param>
/// <returns><c>true</c> when the match should be masked, otherwise <c>false</c>. Defaults to <c>true</c></returns>
/// <remarks>This method provides an extension point to short-circuit the masking operation if the value matches the regular expression but does not satisfy some additional criteria</remarks>
protected virtual bool ShouldMaskMatch(Match match) => true;
}
/// <returns><c>true</c> when the match should be masked, otherwise <c>false</c>. Defaults to <c>true</c></returns>
/// <remarks>This method provides an extension point to short-circuit the masking operation if the value matches the regular expression but does not satisfy some additional criteria</remarks>
protected virtual bool ShouldMaskMatch(Match match) => true;
}
}
71 changes: 37 additions & 34 deletions src/Serilog.Enrichers.Sensitive/SensitiveDataEnricher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal class SensitiveDataEnricher : ILogEventEnricher
private readonly MaskPropertyCollection _maskProperties;
private readonly List<string> _excludeProperties;

public SensitiveDataEnricher(SensitiveDataEnricherOptions options)
public SensitiveDataEnricher(SensitiveDataEnricherOptions options)
: this(options.Apply)
{
}
Expand All @@ -30,8 +30,8 @@ public SensitiveDataEnricher(
Action<SensitiveDataEnricherOptions>? options)
{
var enricherOptions = new SensitiveDataEnricherOptions(
MaskingMode.Globally,
DefaultMaskValue,
MaskingMode.Globally,
DefaultMaskValue,
DefaultOperators.Select(o => o.GetType().AssemblyQualifiedName),
new List<string>(),
new List<string>());
Expand All @@ -55,7 +55,7 @@ public SensitiveDataEnricher(
var fields = typeof(LogEvent).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);

var backingField = fields.SingleOrDefault(f => f.Name.Contains("<MessageTemplate>"));

if (backingField == null)
{
throw new InvalidOperationException(
Expand Down Expand Up @@ -114,7 +114,7 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
return (false, null);
}

if(_maskProperties.TryGetProperty(property.Key, out var options))
if (_maskProperties.TryGetProperty(property.Key, out var options))
{
if (options == MaskOptions.Default)
{
Expand All @@ -136,7 +136,7 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
case ScalarValue { Value: string stringValue }:
{
var (wasMasked, maskedValue) = ReplaceSensitiveDataFromString(stringValue);
var (wasMasked, maskedValue) = ReplaceSensitiveDataFromString(stringValue, property.Key);

if (wasMasked)
{
Expand All @@ -150,23 +150,23 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
// which is why this needs special handling and isn't
// caught by the string value above.
case ScalarValue { Value: Uri uriValue }:
{
var (wasMasked, maskedValue) = ReplaceSensitiveDataFromString(uriValue.ToString());

if (wasMasked)
{
return (true, new ScalarValue(new Uri(maskedValue)));
}
var (wasMasked, maskedValue) = ReplaceSensitiveDataFromString(uriValue.ToString(), property.Key);

return (false, null);
}
if (wasMasked)
{
return (true, new ScalarValue(new Uri(maskedValue)));
}

return (false, null);
}
case SequenceValue sequenceValue:
var resultElements = new List<LogEventPropertyValue>();
var anyElementMasked = false;
foreach (var element in sequenceValue.Elements)
{
var (wasElementMasked, elementResult) = MaskProperty(new KeyValuePair<string, LogEventPropertyValue>(property.Key, element));

if (wasElementMasked)
{
resultElements.Add(elementResult!);
Expand Down Expand Up @@ -201,27 +201,27 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
return (anyMasked, new StructureValue(propList));
}
case DictionaryValue dictionaryValue:
{
var resultDictionary = new List<KeyValuePair<ScalarValue, LogEventPropertyValue>>();
var anyKeyMasked = false;

foreach (var pair in dictionaryValue.Elements)
{
var (wasPairMasked, pairResult) = MaskProperty(new KeyValuePair<string, LogEventPropertyValue>(pair.Key.Value as string, pair.Value));
var resultDictionary = new List<KeyValuePair<ScalarValue, LogEventPropertyValue>>();
var anyKeyMasked = false;

if (wasPairMasked)
{
resultDictionary.Add(new KeyValuePair<ScalarValue, LogEventPropertyValue>(pair.Key, pairResult));
anyKeyMasked = true;
}
else
foreach (var pair in dictionaryValue.Elements)
{
resultDictionary.Add(new KeyValuePair<ScalarValue, LogEventPropertyValue>(pair.Key, pair.Value));
var (wasPairMasked, pairResult) = MaskProperty(new KeyValuePair<string, LogEventPropertyValue>(pair.Key.Value as string, pair.Value));

if (wasPairMasked)
{
resultDictionary.Add(new KeyValuePair<ScalarValue, LogEventPropertyValue>(pair.Key, pairResult));
anyKeyMasked = true;
}
else
{
resultDictionary.Add(new KeyValuePair<ScalarValue, LogEventPropertyValue>(pair.Key, pair.Value));
}
}
}

return (anyKeyMasked, new DictionaryValue(resultDictionary));
}
return (anyKeyMasked, new DictionaryValue(resultDictionary));
}
default:
return (false, null);
}
Expand Down Expand Up @@ -315,20 +315,23 @@ private string MaskWithOptions(string maskValue, MaskOptions options, string inp
{
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)
private (bool, string) ReplaceSensitiveDataFromString(string input, string? propertyName = null)
{
var isMasked = false;

foreach (var maskingOperator in _maskingOperators)
{
var maskResult = maskingOperator.Mask(input, _maskValue);

var maskResult = string.IsNullOrWhiteSpace(propertyName)
? maskingOperator.MaskMessage(input, _maskValue)
: maskingOperator.MaskProperty(propertyName!, input, _maskValue);

if (maskResult.Match)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@

<ItemGroup>
<None Update="enricher-operator-config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="enricher-config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,14 @@ public MaskingResult Mask(string input, string mask)
Result = mask
};
}

public MaskingResult MaskProperty(string propertyName, string input, string mask)
{
return Mask(input, mask);
}

public MaskingResult MaskMessage(string input, string mask)
{
return Mask(input, mask);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,46 @@ namespace Serilog.Enrichers.Sensitive.Tests.Unit
{
public class WhenMaskingCreditCards
{
private const string Mask = "***MASK***";
private const string Mask = "***MASK***";

[Theory]
[Theory]
[InlineData("4111 1111 1111 1111", "***MASK***", true, false)]
[InlineData("4111-1111-1111-1111", "***MASK***", true, false)]
[InlineData("4111111111111111", "***MASK***", true, true)]
[InlineData("4111 1111 1111 1111", "4111 ***MASK***11 1111", false, false)]
[InlineData("4111-1111-1111-1111", "4111-***MASK***11-1111", false, false)]
[InlineData("4111111111111111", "4111***MASK***111111", false, false)]
[InlineData("4111 1111 1111 1111", "***MASK***", false, true)]
[InlineData("4111-1111-1111-1111", "***MASK***", false, true)]
[InlineData("4111111111111111", "***MASK***", false, true)]
[InlineData("4111 1111 1111 1111", "4111 ***MASK***11 1111", false, false)]
[InlineData("4111-1111-1111-1111", "4111-***MASK***11-1111", false, false)]
[InlineData("4111111111111111", "4111***MASK***111111", false, false)]
[InlineData("4111 1111 1111 1111", "***MASK***", false, true)]
[InlineData("4111-1111-1111-1111", "***MASK***", false, true)]
[InlineData("4111111111111111", "***MASK***", false, true)]
public void GivenCreditCard_ValuesAreMaskedFully(string cc, string result, bool fullMask, bool useDefaultConstructor)
{
TheMaskedResultOf(cc, fullMask, useDefaultConstructor)
ThePropertyMaskedResultOf("anyPropertyName", cc, fullMask, useDefaultConstructor)
.Should()
.Be(result);

TheMessageMaskedResultOf(cc, fullMask, useDefaultConstructor)
.Should()
.Be(result);
}

private static string ThePropertyMaskedResultOf(string propertyName, string input, bool fullMask, bool useDefaultConstructor)
{
var maskingResult = (useDefaultConstructor ? new CreditCardMaskingOperator() : new CreditCardMaskingOperator(fullMask)).MaskProperty(propertyName, input, Mask);

return maskingResult.Match
? maskingResult.Result
: input;
}

private static string TheMaskedResultOf(string input, bool fullMask, bool useDefaultConstructor)


private static string TheMessageMaskedResultOf(string input, bool fullMask, bool useDefaultConstructor)
{
var maskingResult = (useDefaultConstructor ? new CreditCardMaskingOperator() : new CreditCardMaskingOperator(fullMask)).Mask(input, Mask);
var maskingResult = (useDefaultConstructor ? new CreditCardMaskingOperator() : new CreditCardMaskingOperator(fullMask)).MaskMessage(input, Mask);

return maskingResult.Match
? maskingResult.Result
return maskingResult.Match
? maskingResult.Result
: input;
}
}
Expand Down
Loading