Skip to content

Instantly share code, notes, and snippets.

@jezzsantos
Last active March 17, 2021 16:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jezzsantos/4b1f27e9a7c99784a6aa4fcab3ba1b55 to your computer and use it in GitHub Desktop.
Save jezzsantos/4b1f27e9a7c99784a6aa4fcab3ba1b55 to your computer and use it in GitHub Desktop.
A serilog.net Enricher (with unit tests) that removes any properties of any structured value that are named by a black-list (partially or wholly case-sensitive).
using System;
using System.Collections.Generic;
using System.Linq;
using Serilog.Events;
namespace Gene.Diagnostics
{
/// <summary>
/// Defines a black-list filter of <see cref="LogEventPropertyValue" />
/// </summary>
internal class BlacklistedPropertyFilter : ILogEventPropertyValueFilter
{
private Func<string, bool> isBlacklistedName;
public BlacklistedPropertyFilter(Func<string, bool> isBlacklistedName)
{
this.isBlacklistedName = isBlacklistedName;
}
public LogEventPropertyValue Apply(ScalarValue value)
{
//Do nothing with ScalarValue, as property has no name to filter
return null;
}
public LogEventPropertyValue Apply(SequenceValue value)
{
var childElements = value.Elements.ToList();
var clonedChildElements = new List<LogEventPropertyValue>(childElements);
var requireUpdate = false;
childElements.ForEach(childElement =>
{
var updatedChild = Apply(childElement);
if (updatedChild != null)
{
clonedChildElements[clonedChildElements.FindIndex(x => x == childElement)] = updatedChild;
requireUpdate = true;
}
});
if (requireUpdate)
{
return new SequenceValue(clonedChildElements);
}
return null;
}
public LogEventPropertyValue Apply(DictionaryValue value)
{
var childElements = value.Elements.ToList();
var clonedChildElements = value.Elements.ToDictionary(x => x.Key, x => x.Value);
var requireUpdate = false;
childElements.ForEach(childElement =>
{
var name = childElement.Key.Value.ToString();
if (this.isBlacklistedName(name))
{
clonedChildElements.Remove(childElement.Key);
requireUpdate = true;
}
else
{
var updatedChild = Apply(childElement.Value);
if (updatedChild != null)
{
clonedChildElements[childElement.Key] = updatedChild;
requireUpdate = true;
}
}
});
if (requireUpdate)
{
return new DictionaryValue(clonedChildElements);
}
return null;
}
public LogEventPropertyValue Apply(StructureValue value)
{
var childProperties = value.Properties.ToList();
var clonedChildProperties = new List<LogEventProperty>(childProperties);
var requireUpdate = false;
childProperties.ForEach(childProperty =>
{
var name = childProperty.Name;
if (this.isBlacklistedName(name))
{
clonedChildProperties.Remove(childProperty);
requireUpdate = true;
}
else
{
var updatedChild = Apply(childProperty.Value);
if (updatedChild != null)
{
clonedChildProperties[clonedChildProperties.FindIndex(x => x == childProperty)]
= new LogEventProperty(childProperty.Name, updatedChild);
requireUpdate = true;
}
}
});
if (requireUpdate)
{
return new StructureValue(clonedChildProperties);
}
return null;
}
public LogEventPropertyValue Apply(LogEventPropertyValue value)
{
var scalarValue = value as ScalarValue;
if (scalarValue != null)
{
return new FilterableScalar(scalarValue).Filter(this);
}
var sequenceValue = value as SequenceValue;
if (sequenceValue != null)
{
return new FilterableSequence(sequenceValue).Filter(this);
}
var dictionaryValue = value as DictionaryValue;
if (dictionaryValue != null)
{
return new FilterableDictionary(dictionaryValue).Filter(this);
}
var structureValue = value as StructureValue;
if (structureValue != null)
{
return new FilterableStructure(structureValue).Filter(this);
}
return null;
}
}
internal interface ILogEventPropertyValueFilter
{
LogEventPropertyValue Apply(ScalarValue value);
LogEventPropertyValue Apply(SequenceValue value);
LogEventPropertyValue Apply(DictionaryValue value);
LogEventPropertyValue Apply(StructureValue value);
}
internal interface IFilterableLogEventPropertyValue
{
LogEventPropertyValue Filter(ILogEventPropertyValueFilter filter);
}
internal class FilterableScalar : IFilterableLogEventPropertyValue
{
private ScalarValue value;
public FilterableScalar(ScalarValue value)
{
this.value = value;
}
public LogEventPropertyValue Filter(ILogEventPropertyValueFilter filter)
{
return filter.Apply(this.value);
}
}
internal class FilterableSequence : IFilterableLogEventPropertyValue
{
private SequenceValue value;
public FilterableSequence(SequenceValue value)
{
this.value = value;
}
public LogEventPropertyValue Filter(ILogEventPropertyValueFilter filter)
{
return filter.Apply(this.value);
}
}
internal class FilterableDictionary : IFilterableLogEventPropertyValue
{
private DictionaryValue value;
public FilterableDictionary(DictionaryValue value)
{
this.value = value;
}
public LogEventPropertyValue Filter(ILogEventPropertyValueFilter filter)
{
return filter.Apply(this.value);
}
}
internal class FilterableStructure : IFilterableLogEventPropertyValue
{
private StructureValue value;
public FilterableStructure(StructureValue value)
{
this.value = value;
}
public LogEventPropertyValue Filter(ILogEventPropertyValueFilter filter)
{
return filter.Apply(this.value);
}
}
}
using System.Collections.Generic;
using System.Linq;
using Serilog.Core;
using Serilog.Events;
namespace Gene.Diagnostics
{
/// <summary>
/// Defines a <see cref="ILogEventEnricher" /> that removes properties that match (wholey or partially case-sensitive) those in a black-list
/// </summary>
public class BlackListedPropertyRemover : ILogEventEnricher
{
internal static readonly List<string> DefaultBlacklistedPropertyNames = new List<string>
{
"Password",
"Secret",
"Token",
"Pin"
};
private IEnumerable<string> blacklistedNames;
public BlackListedPropertyRemover()
: this(DefaultBlacklistedPropertyNames)
{
}
public BlackListedPropertyRemover(IEnumerable<string> blacklistedNames)
{
this.blacklistedNames = blacklistedNames;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var filter = new BlacklistedPropertyFilter(n => IsBlacklistedName(n));
logEvent.Properties
.ToList()
.ForEach(propertyValue =>
{
var name = propertyValue.Key;
if (IsBlacklistedName(name))
{
logEvent.RemovePropertyIfPresent(name);
}
else
{
var updatedPropertyValue = filter.Apply(propertyValue.Value);
if (updatedPropertyValue != null)
{
logEvent.AddOrUpdateProperty(new LogEventProperty(name, updatedPropertyValue));
}
}
});
}
private bool IsBlacklistedName(string name)
{
return this.blacklistedNames.Any(bl => bl.EqualsOrdinal(name) || name.Contains(bl));
}
}
}
using System;
using System.Collections.Generic;
using Gene.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Serilog.Core;
using Serilog.Events;
using Serilog.Parsing;
namespace Gene.UnitTests.Diagnostics
{
public class BlackListedPropertyRemoverSpec
{
private static readonly IAssertion Assert = new Assertion();
[TestClass]
public class GivenABlacklistedName
{
private BlackListedPropertyRemover enricher;
private Mock<ILogEventPropertyFactory> propertyFactory;
[TestInitialize]
public void Initialize()
{
this.propertyFactory = new Mock<ILogEventPropertyFactory>();
this.enricher = new BlackListedPropertyRemover(new List<string> { "ablacklistedname" });
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithScalars_ThenNotRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("aname1", new ScalarValue("avalue")),
new LogEventProperty("aname2", new ScalarValue(42))
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(2, logEvent.Properties.Count);
Assert.Equal("\"avalue\"", logEvent.Properties["aname1"].ToString());
Assert.Equal("42", logEvent.Properties["aname2"].ToString());
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithStructureContainingNoSensitiveProperties_ThenNotRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("aname1", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("aname2", new ScalarValue("avalue2")),
new LogEventProperty("aname3", new ScalarValue("avalue3")),
})),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(1, logEvent.Properties.Count);
Assert.Equal("{ aname2: \"avalue2\", aname3: \"avalue3\" }", logEvent.Properties["aname1"].ToString());
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingAPartiallySensitiveStructuredProperty_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("ablacklistednameandprefix", new StructureValue(new List<LogEventProperty>())),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(0, logEvent.Properties.Count);
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingAWrongCasedSensitiveStructuredProperty_ThenNotRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("aBlacklistedName", new StructureValue(new List<LogEventProperty>())),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(1, logEvent.Properties.Count);
Assert.Equal("{ }", logEvent.Properties["aBlacklistedName"].ToString());
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingASingleSensitiveStructuredProperty_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new StructureValue(new List<LogEventProperty>())),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(0, logEvent.Properties.Count);
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingASingleSensitiveScalarProperty_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(0, logEvent.Properties.Count);
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingASingleSensitiveSequenceProperty_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("avalue1"),
new ScalarValue("avalue2"),
})),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(0, logEvent.Properties.Count);
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingASingleSensitiveDictionaryProperty_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("aname2"), new ScalarValue("avalue") }
})),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(0, logEvent.Properties.Count);
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingSensitiveProperties_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("aname1", new ScalarValue("avalue1")),
new LogEventProperty("aname2", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("aname3", new ScalarValue("avalue3")),
})),
new LogEventProperty("aname4", new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("aname5"), new ScalarValue("avalue5") }
})),
new LogEventProperty("aname6", new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("avalue6")
})),
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("ablacklistedname", new StructureValue(new List<LogEventProperty>())),
new LogEventProperty("ablacklistedname", new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>())),
new LogEventProperty("ablacklistedname", new SequenceValue(new List<LogEventPropertyValue>())),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(4, logEvent.Properties.Count);
Assert.Equal("\"avalue1\"", logEvent.Properties["aname1"].ToString());
Assert.Equal("{ aname3: \"avalue3\" }", logEvent.Properties["aname2"].ToString());
Assert.Equal("[(\"aname5\": \"avalue5\")]", logEvent.Properties["aname4"].ToString());
Assert.Equal("[\"avalue6\"]", logEvent.Properties["aname6"].ToString());
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingNestedSensitiveProperties_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("aname1", new ScalarValue("avalue1")),
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("aname2", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("aname3", new ScalarValue("avalue3")),
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("aname4", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("aname5", new ScalarValue("avalue5")),
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
})),
new LogEventProperty("aname6", new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("aname7"), new ScalarValue("avalue7") },
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
})),
new LogEventProperty("aname8", new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("avalue8"),
})),
})),
new LogEventProperty("aname9", new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("aname10"), new ScalarValue("avalue10") },
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
{
new ScalarValue("aname11"), new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("aname12", new ScalarValue("avalue12")),
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
})
},
{
new ScalarValue("aname13"), new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("aname14"), new ScalarValue("avalue15") },
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
})
},
{
new ScalarValue("aname16"), new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("avalue16"),
})
},
})),
new LogEventProperty("aname17", new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("avalue18"),
new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("aname19", new ScalarValue("avalue19")),
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
}),
new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("aname20"), new ScalarValue("avalue20") },
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
}),
new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("avalue21"),
}),
})),
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(4, logEvent.Properties.Count);
Assert.Equal("\"avalue1\"", logEvent.Properties["aname1"].ToString());
Assert.Equal("{ aname3: \"avalue3\", aname4: { aname5: \"avalue5\" }, aname6: [(\"aname7\": \"avalue7\")], aname8: [\"avalue8\"] }",
logEvent.Properties["aname2"].ToString());
Assert.Equal(
"[(\"aname10\": \"avalue10\"), (\"aname11\": { aname12: \"avalue12\" }), (\"aname13\": [(\"aname14\": \"avalue15\")]), (\"aname16\": [\"avalue16\"])]",
logEvent.Properties["aname9"].ToString());
Assert.Equal("[\"avalue18\", { aname19: \"avalue19\" }, [(\"aname20\": \"avalue20\")], [\"avalue21\"]]",
logEvent.Properties["aname17"].ToString());
}
[TestMethod, TestCategory("Unit")]
public void WhenEnrichWithComplexStructureContainingRecursiveSensitiveProperties_ThenRemoved()
{
var logEvent = new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null,
new MessageTemplate("atemplate", new List<MessageTemplateToken>()),
new List<LogEventProperty>
{
new LogEventProperty("aname1", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("aname2", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("aname3", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("aname4", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("aname5", new StructureValue(new List<LogEventProperty>
{
new LogEventProperty("ablacklistedname", new ScalarValue("avalue")),
new LogEventProperty("aname6", new ScalarValue("avalue6")),
})),
})),
})),
})),
})),
new LogEventProperty("aname7", new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
{
new ScalarValue("aname8"), new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
{
new ScalarValue("aname9"), new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
{
new ScalarValue("aname10"), new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
{
new ScalarValue("aname11"), new DictionaryValue(new Dictionary<ScalarValue, LogEventPropertyValue>
{
{ new ScalarValue("ablacklistedname"), new ScalarValue("avalue") },
{ new ScalarValue("aname12"), new ScalarValue("avalue12") },
})
},
})
},
})
},
})
},
})),
new LogEventProperty("aname13", new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("aname14"),
new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("aname15"),
new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("aname16"),
new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("aname17"),
new SequenceValue(new List<LogEventPropertyValue>
{
new ScalarValue("aname18"),
})
})
})
})
}))
});
this.enricher.Enrich(logEvent, this.propertyFactory.Object);
Assert.Equal(3, logEvent.Properties.Count);
Assert.Equal("{ aname2: { aname3: { aname4: { aname5: { aname6: \"avalue6\" } } } } }", logEvent.Properties["aname1"].ToString());
Assert.Equal("[(\"aname8\": [(\"aname9\": [(\"aname10\": [(\"aname11\": [(\"aname12\": \"avalue12\")])])])])]", logEvent.Properties["aname7"].ToString());
Assert.Equal("[\"aname14\", [\"aname15\", [\"aname16\", [\"aname17\", [\"aname18\"]]]]]", logEvent.Properties["aname13"].ToString());
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment