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 pathmask #27

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>1.7.1.0</Version>
<Authors>Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman</Authors>
<Version>1.8.0.0</Version>
<Authors>Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman, sunnamed434</Authors>
<Company>Codenizer BV</Company>
<Copyright>2023 Sander van Vliet</Copyright>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ By default the enricher uses the following masking operators:
- EmailAddressMaskingOperator
- IbanMaskingOperator
- CreditCardMaskingOperator
- PathMaskingOperator
Copy link
Contributor

Choose a reason for hiding this comment

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

Adding the default operators when I built this library was a bit of a mistake.
Could you remove it from the docs here? (I see you didn't actually add it to the list of default operators)

Copy link
Author

Choose a reason for hiding this comment

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

Added to the default list of operators


It's good practice to only configure the masking operators that are applicable for your application. For example:

Expand Down
14 changes: 12 additions & 2 deletions src/Serilog.Enrichers.Sensitive.Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Serilog.Core;

Expand All @@ -13,7 +14,8 @@ static async Task Main(string[] args)
{
new EmailAddressMaskingOperator(),
new IbanMaskingOperator(),
new CreditCardMaskingOperator(false)
new CreditCardMaskingOperator(false),
new PathMaskingOperator()
})
.WriteTo.Console()
.CreateLogger();
Expand Down Expand Up @@ -43,16 +45,24 @@ static async Task Main(string[] args)
Key = 12345, XmlValue = "<MyElement><CreditCard>4111111111111111</CreditCard></MyElement>"
};
logger.Information("Object dump with embedded credit card: {x}", x);

// Path to the file is also masked
logger.Information("This is path to the file: {0}",
@"E:\SuperSecretPath\test.txt");

// Path to the directory is also masked
logger.Information("This is path to the directory: {0}", @"C:\Admin\");

}

// But outside the sensitive area nothing is masked
logger.Information("Felix can be reached at [email protected]");

logger.Information("This is your path to the file: {file}", new FileInfo("test.txt").FullName);

// Now, show that this works for async contexts too
logger.Information("Now, show the Async works");

var t1 = LogAsSensitiveAsync(logger);
var t2 = LogAsUnsensitiveAsync(logger);

Expand Down
8 changes: 8 additions & 0 deletions src/Serilog.Enrichers.Sensitive/IPathWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Serilog.Enrichers.Sensitive;

public interface IPathWrapper
{
bool IsDirectory(string path);
string GetFileName(string path);
string GetDirectoryName(string path);
}
52 changes: 52 additions & 0 deletions src/Serilog.Enrichers.Sensitive/PathMaskingOperator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

namespace Serilog.Enrichers.Sensitive;

/// <summary>
/// Represents a masking operator for path names.
/// <remarks>Supports path names for Windows, Linux, and macOS.</remarks>
/// </summary>
public class PathMaskingOperator : RegexMaskingOperator
{
private readonly IPathWrapper _pathWrapper;
private readonly bool _combineMaskWithPath;
Copy link
Contributor

Choose a reason for hiding this comment

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

It took me a while to figure out exactly what this meant. Perhaps changing it to _keepLastPartOfPath or _doNotMaskLastPartOfPath would be more descriptive and easier to understand for consumers of this.

Copy link
Author

Choose a reason for hiding this comment

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

Renamed to the _keepLastPartOfPath

private const string PathPattern =
@"^(?:[a-zA-Z]\:|\\\\[\w-]+\\[\w-]+\$?|[\/][^\/\0]+)+(\\[^\\/:*?""<>|]*)*(\\?)?$";

/// <summary>
/// Initializes a new instance of the <see cref="PathMaskingOperator"/> class.
/// </summary>
/// <param name="pathWrapper">The path wrapper.</param>
/// <param name="combineMaskWithPath">This means if set to <see langword="true"/> then the mask will combine with the file name or its directory name.</param>
public PathMaskingOperator(IPathWrapper pathWrapper, bool combineMaskWithPath = true) : base(PathPattern)
Copy link
Contributor

Choose a reason for hiding this comment

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

As the IPathWrapper abstraction isn't necessary (see my other comment below) you can simplify this constructor after inlining the Path.* methods.

Copy link
Author

Choose a reason for hiding this comment

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

Now path wrapper removed

{
_pathWrapper = pathWrapper;
_combineMaskWithPath = combineMaskWithPath;
}
/// <summary>
/// Initializes a new instance of the <see cref="PathMaskingOperator"/> class, with default <see cref="PathWrapper"/>.
/// </summary>
/// <param name="combineMaskWithPath">This means if set to <see langword="true"/> then the mask will combine with the file name or its directory name.</param>
public PathMaskingOperator(bool combineMaskWithPath = true) : this(new PathWrapper(), combineMaskWithPath)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PathMaskingOperator"/> class.
/// </summary>
public PathMaskingOperator() : this(combineMaskWithPath: true)
Copy link
Contributor

Choose a reason for hiding this comment

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

This constructor isn't necessary as the one you are calling has a default value for the parameter.

Copy link
Author

Choose a reason for hiding this comment

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

Empty ctor removed

{
}
[SuppressMessage("ReSharper", "InvertIf")]
protected override string PreprocessMask(string mask, Match match)
{
if (_combineMaskWithPath)
{
var value = match.Value;
return _pathWrapper.IsDirectory(value)
? mask + _pathWrapper.GetDirectoryName(value)
: mask + _pathWrapper.GetFileName(value);
}
return mask;
}
}
19 changes: 19 additions & 0 deletions src/Serilog.Enrichers.Sensitive/PathWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.IO;

namespace Serilog.Enrichers.Sensitive;

public class PathWrapper : IPathWrapper
Copy link
Contributor

Choose a reason for hiding this comment

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

I would strongly recommend to remove this abstraction, it's not adding any value right now as there is no other implementation of IPathWrapper and the methods you are using do not actually depend on the filesystem being available.

So I'd prefer that where you're calling _pathWrapper.GetDirectoryName(), just call Path.GetDIrectoryName instead.

Copy link
Author

@sunnamed434 sunnamed434 Mar 4, 2023

Choose a reason for hiding this comment

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

The only reason I made such abstraction, cuz I thought if there's a specific when some of the OS unable to get the path properly, then developer might make it own implementation. In case if this feature with abstraction would be removed and If I were such developer, I'd just delete the package and go cry. Let me know if this is still should be removed

Copy link
Author

Choose a reason for hiding this comment

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

On the other hand, Path.GetDIrectoryName works weird, sometimes It's unable to get the directory name correct

Copy link
Contributor

Choose a reason for hiding this comment

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

I understand where you're coming from, however you may expect from the runtime to handle this for you.

Copy link
Author

Choose a reason for hiding this comment

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

Now path wrapper removed

{
public bool IsDirectory(string path)
{
return Path.GetExtension(path) == string.Empty;
}
public string GetFileName(string path)
{
return Path.GetFileName(path);
}
public string GetDirectoryName(string path)
{
return new DirectoryInfo(path).Name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<Title>Serilog enricher to mask sensitive data</Title>
<Description>An enricher to be used for masking sensitive (PII) data using Serilog</Description>
<Copyright Condition="'$(Copyright)' == ''">2021 Sander van Vliet</Copyright>
<Authors Condition="'$(Authors)' == ''">Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman</Authors>
<Authors Condition="'$(Authors)' == ''">Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman, sunnamed434</Authors>
<PackageProjectUrl>https://github.com/serilog-contrib/Serilog.Enrichers.Sensitive/README.md</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/serilog-contrib/Serilog.Enrichers.Sensitive/</RepositoryUrl>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text.RegularExpressions;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;

namespace Serilog.Enrichers.Sensitive.Tests.Benchmark;

[SimpleJob(RunStrategy.Throughput, warmupCount: 1)]
Copy link
Contributor

Choose a reason for hiding this comment

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

The existing benchmark for regexes already proves a compiled regex is faster so adding another benchmark to prove it again isn't necessary.
You can remove this class altogether.

Copy link
Author

Choose a reason for hiding this comment

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

Class removed

public class PathMaskingOperatorBenchmarks
{
private const string PathInput = "/home/me";
private const string MaskValue = "***MASKED***";

private readonly Regex PathRegex =
new(@"^(?:[a-zA-Z]\:|\\\\[\w-]+\\[\w-]+\$?|[\/][^\/\0]+)+(\\[^\\/:*?""<>|]*)*(\\?)?$");

private readonly Regex PathCompiledRegex =
new(@"^(?:[a-zA-Z]\:|\\\\[\w-]+\\[\w-]+\$?|[\/][^\/\0]+)+(\\[^\\/:*?""<>|]*)*(\\?)?$",
RegexOptions.Compiled);

[Benchmark]
public string PathRegexReplace()
{
string result = null;
for (var i = 0; i < 10000; i++)
result = PathRegex.Replace(PathInput, MaskValue);
return result;
}

[Benchmark]
public string PathRegexCompiledReplace()
{
string result = null;
for (var i = 0; i < 10000; i++)
result = PathCompiledRegex.Replace(PathInput, MaskValue);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ private static void Main(string[] args)
BenchmarkRunner.Run<BenchmarkCompiledEmailRegex>();
BenchmarkRunner.Run<BenchmarkCompiledIbanRegex>();
BenchmarkRunner.Run<CreditCardMarkingBenchmarks>();
BenchmarkRunner.Run<PathMaskingOperatorBenchmarks>();
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be removed too

Copy link
Author

Choose a reason for hiding this comment

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

Removed also

}
}
}
39 changes: 39 additions & 0 deletions test/Serilog.Enrichers.Sensitive.Tests.Unit/WhenMaskingPaths.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using FluentAssertions;
using Xunit;

namespace Serilog.Enrichers.Sensitive.Tests.Unit;

public class WhenMaskingPaths
{
private const string Mask = @"***\";

[Theory]
[InlineData(@"C:\Users\Admin\Secret\File.dll", @"***\", false)]
Copy link
Contributor

Choose a reason for hiding this comment

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

I would expect this to be only the mask in the expected output. Right now it looks like you're keeping the directory separator which seems odd.

Copy link
Author

Choose a reason for hiding this comment

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

Don't really understand what do you mean

[InlineData(@"C:\Users\Admin\Secret\File.dll", @"***\File.dll", true)]
[InlineData(@"C:\Users\Admin\Secret\Hidden\File.dll", @"***\File.dll", true)]
[InlineData(@"C:\Users\Admin\Secret\Hidden", @"***\", false)]
[InlineData(@"C:\Users\Admin\Secret\Hidden", @"***\Hidden", true)]
[InlineData(@"C:\Users\Admin\Secret", @"***\Secret", true)]
[InlineData(@"C:\Users\", @"***\Users", true)]
[InlineData(@"/home/i_use_arch_linux_btw", @"***\i_use_arch_linux_btw", true)]
Copy link
Contributor

Choose a reason for hiding this comment

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

This made me laugh 😄

[InlineData(@"/home/i_use_arch_linux_btw", @"***\", false)]
[InlineData(@"C:\", @"***\C:\", true)]
[InlineData(@"C:\", @"***\", false)]
[InlineData("File.txt", "File.txt", false)]
[InlineData(@"This is not a path", "This is not a path", false)]
public void GivenPaths_ReturnsExpectedResult(string path, string result, bool combineMaskWithPath)
{
TheMaskedResultOf(path, combineMaskWithPath)
.Should()
.Be(result);
}

private static string TheMaskedResultOf(string input, bool combineMaskWithPath)
{
var maskingResult = new PathMaskingOperator(combineMaskWithPath)
.Mask(input, Mask);
return maskingResult.Match
? maskingResult.Result
: input;
}
}