问题 使用logback屏蔽日志中的敏感数据


我需要能够在事件中搜索多个模式中的任何一个,并用模板值替换模式中的文本。这是我们的应用程序中的一项功能,旨在防止敏感信息落入日志。由于信息可以来自各种各样的来源,因此在所有输入上应用过滤器是不切实际的。除了日志记录之外还有toString()的用法,我不希望toString()统一屏蔽所有调用(仅记录日志)。

我尝试在logback.xml中使用%replace方法:

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %replace(%msg){'f k\="pin">(.*?)&lt;/f','f k\="pin">**********&lt;/f'}%n</pattern>

这是成功的(在用字符实体替换尖括号后),但它只能替换单个模式。我也想表现相当于

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %replace(%msg){'pin=(.*?),','pin=**********,'}%n</pattern>

同时,但不能。无法在一个%替换中屏蔽两个模式。

在interblags上松散讨论的另一种方法是在appender / encoder / layout层次结构上进行扩展,但每次拦截ILoggingEvent的尝试都会导致整个系统崩溃,通常是通过实例化错误或UnsupportedOperationException。

例如,我尝试扩展PatternLayout:

@Component("maskingPatternLayout")
public class MaskingPatternLayout extends PatternLayout {

    @Autowired
    private Environment env;

    @Override
    public String doLayout(ILoggingEvent event) {
        String message=super.doLayout(event);

        String patternsProperty = env.getProperty("bowdleriser.patterns");

        if( patternsProperty != null ) {
            String[] patterns = patternsProperty.split("|");
            for (int i = 0; i < patterns.length; i++ ) {
                Pattern pattern = Pattern.compile(patterns[i]);
                Matcher matcher = pattern.matcher(event.getMessage());
                matcher.replaceAll("*");
            }
        } else {
            System.out.println("Bowdleriser not cleaning! Naughty strings are getting through!");
        }

        return message;
    }
}

然后调整logback.xml

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <layout class="com.touchcorp.touchpoint.utils.MaskingPatternLayout">
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </layout>
    </encoder>
  </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <file>logs/touchpoint.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>logs/touchpoint.%i.log.zip</fileNamePattern>
            <minIndex>1</minIndex>
            <maxIndex>3</maxIndex>
        </rollingPolicy>

        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>10MB</maxFileSize>
        </triggeringPolicy>
      <encoder>
          <layout class="com.touchcorp.touchpoint.utils.MaskingPatternLayout">
            <pattern>%date{YYYY-MM-dd HH:mm:ss} %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
          </layout>
      </encoder>
    </appender>


  <logger name="com.touchcorp.touchpoint" level="DEBUG" />
  <logger name="org.springframework.web.servlet.mvc" level="TRACE" />

  <root level="INFO">
    <appender-ref ref="FILE" />
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

我尝试了很多其他的插入,所以我想知道是否有人实际上已经实现了我正在尝试的东西,以及他们是否可以提供任何线索或解决方案。


2368
2017-08-13 04:13


起源



答案:


你需要使用包装布局 LayoutWrappingEncoder。而且我相信你不能在这里使用spring,因为logback不是由spring管理的。

这是更新的课程。

public class MaskingPatternLayout extends PatternLayout {

    private String patternsProperty;

    public String getPatternsProperty() {
        return patternsProperty;
    }

    public void setPatternsProperty(String patternsProperty) {
        this.patternsProperty = patternsProperty;
    }

    @Override
    public String doLayout(ILoggingEvent event) {
        String message = super.doLayout(event);

        if (patternsProperty != null) {
            String[] patterns = patternsProperty.split("\\|");
            for (int i = 0; i < patterns.length; i++) {
                Pattern pattern = Pattern.compile(patterns[i]);

                Matcher matcher = pattern.matcher(event.getMessage());
                if (matcher.find()) {
                    message = matcher.replaceAll("*");
                }
            }
        } else {

        }

        return message;
    }

}

并示例logback.xml

<appender name="fileAppender1" class="ch.qos.logback.core.FileAppender">
    <file>c:/logs/kp-ws.log</file>
    <append>true</append>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="com.kp.MaskingPatternLayout">
            <patternsProperty>.*password.*|.*karthik.*</patternsProperty>
            <pattern>%d [%thread] %-5level %logger{35} - %msg%n</pattern>
        </layout>
    </encoder>
</appender>
<root level="DEBUG">
    <appender-ref ref="fileAppender1" />
</root>


UPDATE

这是更好的方法,在init期间设置Pattern。这样我们可以避免一次又一次地重新创建Pattern,这种实现接近于实际的用例。

公共类MaskingPatternLayout扩展PatternLayout {

private String patternsProperty;
private Optional<Pattern> pattern;

public String getPatternsProperty() {
    return patternsProperty;
}

public void setPatternsProperty(String patternsProperty) {
    this.patternsProperty = patternsProperty;
    if (this.patternsProperty != null) {
        this.pattern = Optional.of(Pattern.compile(patternsProperty, Pattern.MULTILINE));
    } else {
        this.pattern = Optional.empty();
    }
}

    @Override
    public String doLayout(ILoggingEvent event) {
        final StringBuilder message = new StringBuilder(super.doLayout(event));

        if (pattern.isPresent()) {
            Matcher matcher = pattern.get().matcher(message);
            while (matcher.find()) {

                int group = 1;
                while (group <= matcher.groupCount()) {
                    if (matcher.group(group) != null) {
                        for (int i = matcher.start(group); i < matcher.end(group); i++) {
                            message.setCharAt(i, '*');
                        }
                    }
                    group++;
                }
            }
        }
        return message.toString();
    }

}

和更新的配置文件。

<appender name="fileAppender1" class="ch.qos.logback.core.FileAppender">
    <file>c:/logs/kp-ws.log</file>
    <append>true</append>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="com.kp.MaskingPatternLayout">
            <patternsProperty>(password)|(karthik)</patternsProperty>
            <pattern>%d [%thread] %-5level %logger{35} - %msg%n</pattern>
        </layout>
    </encoder>
</appender>
<root level="DEBUG">
    <appender-ref ref="fileAppender1" />
</root>

产量

My username=test and password=*******

11
2017-08-13 07:05



谢谢Karthik,这是一个很好的解决方案,我只需要稍微调整一下,不要只替换整个字符串的部分模式。 - Michael Coxon
将此与弹簧相结合的一种方法是将该类标记为 @Component。该类必须具有默认构造函数和 @Autowired 构造函数。然后,自动装配的构造函数可以在类上设置静态属性以填充所需的信息。或者你可以使用 org.springframework.core.io.support.PathMatchingResourcePatternResolver 检查claspath。这不是使用Spring目录,但允许您查找带注释类的内容。 - Max
此外,如代码所示,看起来只会应用最后一个模式匹配。 - Corwin Newall
@CorwinNewall更新答案......采用更好的方法 - Karthik Prasad
如果我使用log4j而不是logback,我怎么能这样做呢? - vigamage


答案:


你需要使用包装布局 LayoutWrappingEncoder。而且我相信你不能在这里使用spring,因为logback不是由spring管理的。

这是更新的课程。

public class MaskingPatternLayout extends PatternLayout {

    private String patternsProperty;

    public String getPatternsProperty() {
        return patternsProperty;
    }

    public void setPatternsProperty(String patternsProperty) {
        this.patternsProperty = patternsProperty;
    }

    @Override
    public String doLayout(ILoggingEvent event) {
        String message = super.doLayout(event);

        if (patternsProperty != null) {
            String[] patterns = patternsProperty.split("\\|");
            for (int i = 0; i < patterns.length; i++) {
                Pattern pattern = Pattern.compile(patterns[i]);

                Matcher matcher = pattern.matcher(event.getMessage());
                if (matcher.find()) {
                    message = matcher.replaceAll("*");
                }
            }
        } else {

        }

        return message;
    }

}

并示例logback.xml

<appender name="fileAppender1" class="ch.qos.logback.core.FileAppender">
    <file>c:/logs/kp-ws.log</file>
    <append>true</append>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="com.kp.MaskingPatternLayout">
            <patternsProperty>.*password.*|.*karthik.*</patternsProperty>
            <pattern>%d [%thread] %-5level %logger{35} - %msg%n</pattern>
        </layout>
    </encoder>
</appender>
<root level="DEBUG">
    <appender-ref ref="fileAppender1" />
</root>


UPDATE

这是更好的方法,在init期间设置Pattern。这样我们可以避免一次又一次地重新创建Pattern,这种实现接近于实际的用例。

公共类MaskingPatternLayout扩展PatternLayout {

private String patternsProperty;
private Optional<Pattern> pattern;

public String getPatternsProperty() {
    return patternsProperty;
}

public void setPatternsProperty(String patternsProperty) {
    this.patternsProperty = patternsProperty;
    if (this.patternsProperty != null) {
        this.pattern = Optional.of(Pattern.compile(patternsProperty, Pattern.MULTILINE));
    } else {
        this.pattern = Optional.empty();
    }
}

    @Override
    public String doLayout(ILoggingEvent event) {
        final StringBuilder message = new StringBuilder(super.doLayout(event));

        if (pattern.isPresent()) {
            Matcher matcher = pattern.get().matcher(message);
            while (matcher.find()) {

                int group = 1;
                while (group <= matcher.groupCount()) {
                    if (matcher.group(group) != null) {
                        for (int i = matcher.start(group); i < matcher.end(group); i++) {
                            message.setCharAt(i, '*');
                        }
                    }
                    group++;
                }
            }
        }
        return message.toString();
    }

}

和更新的配置文件。

<appender name="fileAppender1" class="ch.qos.logback.core.FileAppender">
    <file>c:/logs/kp-ws.log</file>
    <append>true</append>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="com.kp.MaskingPatternLayout">
            <patternsProperty>(password)|(karthik)</patternsProperty>
            <pattern>%d [%thread] %-5level %logger{35} - %msg%n</pattern>
        </layout>
    </encoder>
</appender>
<root level="DEBUG">
    <appender-ref ref="fileAppender1" />
</root>

产量

My username=test and password=*******

11
2017-08-13 07:05



谢谢Karthik,这是一个很好的解决方案,我只需要稍微调整一下,不要只替换整个字符串的部分模式。 - Michael Coxon
将此与弹簧相结合的一种方法是将该类标记为 @Component。该类必须具有默认构造函数和 @Autowired 构造函数。然后,自动装配的构造函数可以在类上设置静态属性以填充所需的信息。或者你可以使用 org.springframework.core.io.support.PathMatchingResourcePatternResolver 检查claspath。这不是使用Spring目录,但允许您查找带注释类的内容。 - Max
此外,如代码所示,看起来只会应用最后一个模式匹配。 - Corwin Newall
@CorwinNewall更新答案......采用更好的方法 - Karthik Prasad
如果我使用log4j而不是logback,我怎么能这样做呢? - vigamage


从文档:

replace(p){r, t}    

模式 p 可以是任意复杂的,特别是可以包含多个转换关键字。

面对同样的问题,不得不在消息中替换2个模式,我只是试着 chain 所以 p 在我的例子中,只是一个替换的调用:

%replace(  %replace(%msg){'regex1', 'replacement1'}  ){'regex2', 'replacement2'}

工作得很好,虽然我不知道我是否会推动它 p 确实可以说是任意复杂的。


4
2018-01-17 00:28



我确认使用%replace链接的多个转换关键字的解决方案也对我有用,非常感谢Dmitri !!! @EladTabak这个解决方案对我有用。 - Nicole Naumann
错过了那里的双重替换。像魅力一样工作。谢谢! - Elad Tabak