sidebar_position | sidebar_label |
---|---|
4 |
Add log extended properties "safely" |
In general, it is an extremely bad idea to log sensitive information. Unfortunately, it can be all too easy to inadvertently do this, such as by adding a request object containing PII as an extended property to a log. RockLib.Logging has several methods and extension methods to help protect against this. Specifically, we're protecting against properties of complex types that contain sensitive information. Applications are expected to know not to add a top-level extended property containing sensitive information.
To indicate that a property is safe to log, decorate it with the [SafeToLog]
attribute:
public class Client
{
[SafeToLog]
public string FirstName { get; set; }
[SafeToLog]
public string LastName { get; set; }
public string SSN { get; set; }
}
Alternatively, decorate the class with [SafeToLog]
and exclude the unsafe properties by decorating them with [NotSafeToLog]
:
[SafeToLog]
public class Client
{
public string FirstName { get; set; }
public string LastName { get; set; }
[NotSafeToLog]
public string SSN { get; set; }
}
If you do not own the type that needs to be marked as safe to log, you can add SafeToLogAttribute
and NotSafeToLogAttribute
attributes at runtime.
SafeToLogAttribute.Decorate<Client>(client => client.FirstName);
SafeToLogAttribute.Decorate<Client>(client => client.LastName);
Alternatively, decorate the class with SafeToLogAttribute
and exclude the unsafe properties by decorating them with NotSafeToLogAttribute
:
SafeToLogAttribute.Decorate<Client>();
NotSafeToLogAttribute.Decorate<Client>(client => client.SSN);
To add a sanitized extended property directly to a LogEntry
object, use the SetSanitizedExtendedProperty
method:
var client = new Client { FirstName = "Joe", LastName = "Public", SSN = "123-45-6789" };
LogEntry logEntry = new LogEntry("Example log", LogLevel.Info);
logEntry.SetSanitizedExtendedProperty("Client", client);
To add multiple sanitized extended properties to a LogEntry
object, use the SetSanitizedExtendedProperties
method:
var client1 = new Client { FirstName = "Joe", LastName = "Public", SSN = "123-45-6789" };
var client2 = new Client { FirstName = "Joan", LastName = "Public", SSN = "987-65-4321" };
LogEntry logEntry = new LogEntry("Example log", LogLevel.Info);
logEntry.SetSanitizedExtendedProperties("Client", new { Client = client1, Coclient = client2 });
To add sanitized extended properties to a log without directly creating a LogEntry
object, call the DebugSanitized
, InfoSanitized
, etc. extension methods.
var client1 = new Client { FirstName = "Joe", LastName = "Public", SSN = "123-45-6789" };
var client2 = new Client { FirstName = "Joan", LastName = "Public", SSN = "987-65-4321" };
var extendedProperties = new Dictionary<string, object>();
extendedProperties["Client"] = client1;
extendedProperties["Coclient"] = client2;
logger.InfoSanitized("ExampleMessage", extendedProperties);
It is important to understand exactly what it means to sanitize an extended property:
- If the object is a "complex" type, then only properties decorated with the
[SafeToLog]
attribute are included.- The actual sanitization process involves creating a
Dictionary<string, object>
and adding an item for each of the safe-to-log properties. The key is the property name, and value is the sanitized property value.
- The actual sanitization process involves creating a
- If the object is "value" type, such as a number, enum, or string, it is not modified.
- We could attempt to sanitize a string value with regular expressions (looking for ssn or cc), but false positives are extremely likely.
- If the object is a collection, then we return a new collection, where each item is sanitized.
- If the object is a dictionary, then we return a new dictionary, where the value (but not the key) of each item is sanitized.
- If the object type is "clean" according to the
SanitizeEngine.IsCleanTypeFunction
property (see below), then it is not modified.
Note that sanitation is a recursive process. Any circular references will result in a stack overflow exception.
This static property is meant to allow applications to mark a type as safe-to-log, when the application does not own the type in question. For example, an application could be using System.Numerics.BigInteger
to store a very large value in an object:
public class Apple
{
[SafeToLog]
public BigInteger NumberOfMolecules { get; set; }
}
Even though we're opting in to the NumberOfMolecules
property, the BigInteger
struct isn't known to the SanitizeEngine
, so we would be left with an empty dictionary after sanitizing the number. This is one way to mark the BigInteger
struct as safe-to-log:
SanitizeEngine.IsTypeSafeToLog = runtimeType => runtimeType == typeof(BigInteger);