Prevent sensitive data exposure in log with Serilog

Sensitive data destructing policy with Serilog

·

3 min read

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();
    }
}
  • 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",
};

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;
    }
}
  • 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>();
});

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.