DEV Community

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

Posted on • Originally published at sangau.me

11

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

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (6)

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

Collapse
 
wit__1cc4d01d814f5944 profile image
Wit ΧαοΣ

Thanks for your example. I have a question. Suppose we have two classes, Person and User, both containing a property named Email. However, we only want to redact the Email property of the Person class. Would your example work in this scenario?

class Person
{
public string Email {get; set;}
}

class User
{
public string Email {get; set;}
}

Collapse
 
endyprekpalaj profile image
Endy Prekpalaj

This line:
loggerConfiguration.Destructure.With();

Is throwing error:
The type 'SensitiveDataDestructuringPolicy' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TDestructuringPolicy' in the generic type or method 'LoggerDestructionConfiguration.With()'

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay