DEV Community

Cover image for Prevent sensitive data exposure in log with Serilog
Sang Au
Sang Au

Posted on • Originally published at sangau.me

Prevent sensitive data exposure in log with Serilog

1. Problem

Writing log when developing the application will help the developer to easy debugging and tracing. But writing a good log is not enough, because some sensitive data maybe exposed in log such as: password, account number or something like that.

2. Idea

To prevent this issue, we need to replace all sensitive words to any mask character.
The purpose of this article is help you implement the simple Sensitive data destructing policy with Serilog - one of common logger extension in .NET Core. By applying this policy you can prevent the sensitive data exposure in you log. It's enough, let's go through the code...

3. Implementation

3.1. Tech stack

  • .NET Core (on this article, I use .NET 6.0)
  • Serilog

3.2 Show the code

  • To implement the Destructing Policy we need to implement the interface IDestructuringPolicy from namespace Serilog.Core
public class SensitiveDataDestructuringPolicy : IDestructuringPolicy
{
    public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
    {
        throw new NotImplementedException();
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Now, we need to define a mask value (such as * character) and list of sensitive keywords
var mask = "******";
var sensitiveKeywords = new List<string> {
    "Password", "NewPassword",
};
Enter fullscreen mode Exit fullscreen mode

Actually, we should define the list of sensitive keywords some where such as Configuration file.

  • We will lookup all Log Event Properties, if we found any sensitive keyword in the log object, we will create a new Log Event Property with the mask value and add the new this property to the new list of Log Event Properties, for non sensitive keyword, we also create a new Log Event Property, but keep the original value. After that, just return the result.

Bellow is complete code.

using Microsoft.Extensions.Configuration;
using Serilog.Core;
using Serilog.Events;
using System.Reflection;

namespace Microsoft.Extensions.Logging;

public class SensitiveDataDestructuringPolicy : IDestructuringPolicy
{
    private const string DEFAULT_MASK_VALUE = "******";
    private const string SENSITIVE_KEYWORDS_SECTION = "Logging:SensitiveData:Keywords";
    private const string MASK_VALUE = "Logging:SensitiveData:Mask";

    private readonly IConfiguration _configuration;

    public SensitiveDataDestructuringPolicy(IConfiguration configuration)
    {
        _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
    }

    public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
    {
        var sensitiveKeywords = _configuration.GetValue<string[]>(SENSITIVE_KEYWORDS_SECTION) ?? Array.Empty<string>();
        var maskValue = _configuration.GetValue<string>(MASK_VALUE) ?? DEFAULT_MASK_VALUE;

        if (!sensitiveKeywords.Any())
        {
            result = new StructureValue(new LogEventProperty[] { });

            return false;
        }

        var props = value.GetType().GetTypeInfo().DeclaredProperties;
        var logEventProperties = new List<LogEventProperty>();

        foreach (var propertyInfo in props)
        {
            if (sensitiveKeywords.Any(x => x.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
            {
                logEventProperties.Add(new LogEventProperty(propertyInfo.Name, propertyValueFactory.CreatePropertyValue(maskValue)));
            }
            else
            {
                logEventProperties.Add(new LogEventProperty(propertyInfo.Name, propertyValueFactory.CreatePropertyValue(propertyInfo.GetValue(value))));
            }
        }

        result = new StructureValue(logEventProperties);

        return true;
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Finally, add the SensitiveDataDestructuringPolicy to the loggerConfiguration as below:
# Rest of code
.UseSerilog((context, services, configuration) =>
{
    var loggerConfiguration = configuration
        .ReadFrom.Configuration(context.Configuration)
        .Enrich.FromLogContext()
        .Enrich.WithProperty("Application", context.HostingEnvironment.ApplicationName)
        .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
        .MinimumLevel.Information();

    loggerConfiguration.Destructure.With<SensitiveDataDestructuringPolicy>();
});
Enter fullscreen mode Exit fullscreen mode

4. Concluding

By applying the SensitiveDataDestructuringPolicy, you will make the log safer, that help to prevent the sensitive data exposing while tracing log.

Notes: The sample above just a very simple example, and you can modify, optimize to fit with your project.

This article was originally published at: https://sangau.me/prevent-sensitive-data-exposure-in-log-with-serilog

Top comments (4)

Collapse
 
stphnwlsh profile image
Stephen Walsh

Nice work, but would be nice to see a screenshot of the masked output. This is important stuff great to see engineers sharing their knowledge

Collapse
 
auvansangit profile image
Sang Au • Edited

Fisrt of all, thank you for your comment, the screenshot is the cover image of this article. Actually, I already applyed this to the company project before posting to here!

Collapse
 
stphnwlsh profile image
Stephen Walsh

Totally missed that on mobile!!! Still worthwhile to repeat it. The best examples also exist in real life projects too!!!

Thread Thread
 
auvansangit profile image
Sang Au

Noted, next time I will add the demo on each articles