2015-05-08 14 views
7

Sto cercando di utilizzare log4net come framework di scelta dei log per un nuovo progetto che inizierà a breve. Un problema che ho incontrato durante la prototipazione per il quale non riesco a trovare una risposta definitiva è come si può pulire o mascherare il contenuto dei messaggi in modo configurabile e ordinato.Masking dei dati sensibili configurabili tramite log4net

Ipoteticamente diciamo che voglio mettere in azione più detergenti ma voglio anche seguire il principio della responsabilità unica. Alcuni esempi più puliti:

  • CardNumber/PAN pulitore
  • pulito password
  • pulitore di dati privati ​​

So che non si dovrebbe mai essere accede questo tipo di informazioni in formato testo e il codice in esecuzione i log non lo faranno mai consapevolmente. Voglio avere un ultimo livello di protezione nel caso in cui i dati diventino malformati e i dati sensibili in qualche modo scivolino in qualche posto che non dovrebbe; log è lo scenario peggiore.

Opzione 1:

ho trovato questo articolo StackOverflow che dettaglia una possibile soluzione tuttavia comporta l'uso di riflessione. Questo non è auspicabile per le prestazioni, ma sembra anche hacky per manipolare i meccanismi di archiviazione interni. Editing-log4net-messages-before-they-reach-the-appenders

Opzione 2:

La risposta suggerita sulla stessa domanda suggerisce l'uso di un PatternLayoutConverter. Questo va bene per una singola operazione più pulite, ma non si riesce a utilizzare più operazioni come il qui sotto:

public class CardNumberCleanerLayoutConverter : PatternLayoutConverter 
{ 
    protected override void Convert(TextWriter writer, LoggingEvent loggingEvent) 
    { 
     string message = loggingEvent.RenderedMessage; 

     // TODO: Replace with real card number detection and masking. 
     writer.Write(message.Replace("9", "*")); 
    } 
} 
<layout type="log4net.Layout.PatternLayout"> 
    <converter> 
     <name value="cleanedMessage" /> 
     <type value="Log4NetPrototype.CardNumberCleanerLayoutConverter, Log4NetPrototype" /> 
    </converter> 
    <converter> 
     <name value="cleanedMessage" /> 
     <type value="Log4NetPrototype.PasswordCleanerLayoutConverter, Log4NetPrototype" /> 
    </converter> 
    <conversionPattern value="%cleanedMessage" /> 
</layout> 

Nel caso di una collisione nomi come dimostrato sopra, il convertitore caricata ultima sarà quello che è azionato. Utilizzando l'esempio precedente, questo significa che le password verranno pulite ma non i numeri delle carte.

Opzione 3:

Una terza opzione che ho provato è l'uso di istanze ForwarderAppender incatenati, ma questo complica rapidamente la configurazione e non vorrei prendere in considerazione una soluzione ideale. Poiché la classe LoggingEvent ha una proprietà RenderedMessage immutabile siamo in grado di cambiare senza creare una nuova istanza della classe LoggingEvent e passando attraverso come illustrato di seguito:

public class CardNumberCleanerForwarder : ForwardingAppender 
{ 
    protected override void Append(LoggingEvent loggingEvent) 
    { 
     // TODO: Replace this with real card number detection and masking. 
     string newMessage = loggingEvent.RenderedMessage.Replace("9", "*"); 

     // What context data are we losing by doing this? 
     LoggingEventData eventData = new LoggingEventData() 
     { 
     Domain = loggingEvent.Domain, 
     Identity = loggingEvent.Identity, 
     Level = loggingEvent.Level, 
     LocationInfo = loggingEvent.LocationInformation, 
     LoggerName = loggingEvent.LoggerName, 
     ExceptionString = loggingEvent.GetExceptionString(), 
     TimeStamp = loggingEvent.TimeStamp, 
     Message = newMessage, 
     Properties = loggingEvent.Properties, 
     ThreadName = loggingEvent.ThreadName, 
     UserName = loggingEvent.UserName 
     }; 

     base.Append(new LoggingEvent(eventData)); 
    } 
} 

public class PasswordCleanerForwarder : ForwardingAppender 
{ 
    protected override void Append(LoggingEvent loggingEvent) 
    { 
     // TODO: Replace this with real password detection and masking. 
     string newMessage = loggingEvent.RenderedMessage.Replace("4", "*"); 

     // What context data are we losing by doing this? 
     LoggingEventData eventData = new LoggingEventData() 
     { 
     Domain = loggingEvent.Domain, 
     Identity = loggingEvent.Identity, 
     Level = loggingEvent.Level, 
     LocationInfo = loggingEvent.LocationInformation, 
     LoggerName = loggingEvent.LoggerName, 
     ExceptionString = loggingEvent.GetExceptionString(), 
     TimeStamp = loggingEvent.TimeStamp, 
     Message = newMessage, 
     Properties = loggingEvent.Properties, 
     ThreadName = loggingEvent.ThreadName, 
     UserName = loggingEvent.UserName 
     }; 

     base.Append(new LoggingEvent(eventData)); 
    } 
} 

configurazione coordinati (molto difficile da seguire):

<log4net> 
    <appender name="LocatedAsyncForwardingAppender" type="Log4NetPrototype.LocatedAsyncForwardingAppender, Log4NetPrototype"> 
     <appender-ref ref="CardNumberCleanerForwarder" /> 
    </appender> 
    <appender name="CardNumberCleanerForwarder" type="Log4NetPrototype.CardNumberCleanerForwarder, Log4NetPrototype"> 
     <appender-ref ref="PasswordCleanerForwarder" /> 
    </appender> 
    <appender name="PasswordCleanerForwarder" type="Log4NetPrototype.PasswordCleanerForwarder, Log4NetPrototype"> 
     <appender-ref ref="LogFileAppender" /> 
    </appender> 
    <appender name="LogFileAppender" type="Log4NetPrototype.LogFileAppender, Log4NetPrototype"> 
     <layout type="log4net.Layout.PatternLayout"> 
     <conversionPattern value="%m" /> 
     </layout> 
    </appender> 
    <root> 
     <level value="DEBUG" /> 
     <appender-ref ref="LocatedAsyncForwardingAppender" /> 
    </root> 
</log4net> 

Qualcuno ha un altro suggerimento su come questo potrebbe essere implementato dove teoricamente n numero di pulitori potrebbe essere configurato al costo della prestazione?

risposta

-1

Nella tua domanda stai già dicendo che dovresti andare alla causa e non registrare alcun dato sensibile. Questo può essere applicato da una quarta opzione di utilizzo delle revisioni del codice e dai dati registrati. Le tue dichiarazioni di registrazione non dovrebbero mai registrare dati sensibili, perché la causa è un rischio per la sicurezza.Fidandosi di qualsiasi codice con filtri, i dati sensibili probabilmente falliranno se apporti modifiche al tuo progetto. Il tuo processo di controllo qualità deve essere veramente buono per cogliere questo tipo di errori (non ho mai visto un tester che abbia esaminato tutti i registri). Quindi vorrei andare per l'opzione 4 che si sta accertando di non registrare questo tipo di informazioni in primo luogo.

+0

Sono completamente d'accordo con voi sul fatto che tutte le precauzioni dovrebbero essere prese per garantire che questo tipo di dati non sia stato registrato in primo luogo. Ci occupiamo di grandi quantità di dati dai nostri clienti che poi introduciamo in motori di regole abbastanza complessi. Se un registro di debug indica ad esempio che il numero di conto xyz non è riuscito a causa della regola abc ma per qualche motivo ci sono stati inviati dati sensibili nel campo del numero di conto, vogliamo essere in grado di rilevarlo. Avere una logica di rilevamento in ogni punto in cui è possibile creare un registro diagnostico causerebbe un'enorme duplicazione del codice. Preferirei un punto singolare, da qui la mia domanda. –

+0

Anche un ottimo punto sulle recensioni del codice. Abbiamo una rigorosa politica di revisione del codice in cui ogni pezzo di codice che finirà nella produzione deve essere esaminato da almeno altri due sviluppatori. Sfortunatamente anche con tre serie di occhi su un blocco di codice, le cose possono essere perse e voglio essere certo di avere tutte le difese che possiamo raccogliere. –