通过 log4net 可配置的敏感数据屏蔽

Posted

技术标签:

【中文标题】通过 log4net 可配置的敏感数据屏蔽【英文标题】:Configurable sensitive data masking via log4net 【发布时间】:2015-07-18 18:42:21 【问题描述】:

我正在考虑使用 log4net 作为即将开始的新项目的日志记录框架。我在原型设计过程中遇到的一个我无法找到明确答案的问题是如何以可配置和整洁的方式清理或屏蔽消息内容。

假设我想让几个清洁工投入工作,但我也想遵循单一责任原则。一些更简洁的例子:

卡号/PAN 清洁器 密码清理器 私有数据清理器

我知道您永远不应该以纯文本形式记录此类信息,并且执行日志的代码永远不会故意这样做。我希望有最后一层保护,但是以防数据格式不正确并且敏感数据不知何故滑入不应该的地方;日志是最坏的情况。

选项 1:

我发现这篇 *** 文章详细介绍了一种可能的解决方案,但它涉及使用反射。这对于性能来说是不可取的,但操纵内部存储机制似乎也很棘手。 Editing-log4net-messages-before-they-reach-the-appenders

选项 2:

对同一问题的建议答案建议使用 PatternLayoutConverter。这对于单个清理器操作很好,但您无法使用多个操作,例如以下:

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>

在上面演示的命名冲突的情况下,最后加载的转换器将是被操作的转换器。使用上面的示例,这意味着将清除密码但不会清除卡号。

选项 3:

我尝试过的第三个选项是使用链式 ForwarderAppender 实例,但这很快会使配置复杂化,我认为它不是一个理想的解决方案。因为 LoggingEvent 类有一个不可变的 RenderedMessage 属性,我们无法在不创建 LoggingEvent 类的新实例并通过它的情况下更改它,如下所示:

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));
   

匹配的配置(很难理解):

<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>

对于如何在理论上以性能为代价配置 n 个清洁器的情况下如何实现这一点,是否有人有其他建议?

【问题讨论】:

【参考方案1】:

在您的问题中,您已经说过您应该找到原因,而不是记录任何敏感数据。这可以通过使用代码审查的第四个选项来强制执行,并查看正在记录的数据。你的日志语句不应该记录任何敏感数据,因为这会导致安全风险。如果您对项目进行更改,则信任任何带有过滤器的代码可能会失败。您的 QA 流程必须非常出色才能发现此类错误(我从未见过测试人员检查所有日志)。所以我会选择选项 4,它确保您首先不记录此类信息。

【讨论】:

我完全同意您的观点,即应采取所有预防措施以确保不会首先记录此类数据。我们处理来自客户的大量数据,然后通过相当复杂的规则引擎处理这些数据。例如,如果调试日志指出帐号 xyz 由于规则 abc 而失败,但由于某种原因,我们在帐号字段中收到了敏感数据,我们希望能够检测到这一点。在可能创建诊断日志的每个点都有检测逻辑会导致大量代码重复。我更喜欢一个奇点,因此我的问题。 代码审查也很重要。我们有严格的代码审查政策,每段最终投入生产的代码都必须由至少两名其他开发人员审查。不幸的是,即使对一段代码有三对眼睛,也可能会遗漏一些事情,我想确保我们拥有所有可以采取的防御措施。 不,据我所知,这从未解决过。我记得我们最终选择了 Nlog 而不是 log4net。

以上是关于通过 log4net 可配置的敏感数据屏蔽的主要内容,如果未能解决你的问题,请参考以下文章

markdown Symfony:屏蔽日志文件中的敏感数据

如何检测人员在客户端的非屏蔽字段中输入 SSN 数据?

屏蔽功能

如何根据用户权限屏蔽或显示SAP的订单中的成本显示

python 实现敏感词屏蔽小程序

萌新笔记——用KMP算法与词典实现屏蔽敏感词(UTF-8编码)