I need to be able to search an event for any one of a number of patterns and replace the text in the pattern with a masked value. This is a feature in our application intended to prevent sensitive information falling into the logs. As the information can be from a large variety of sources, it is not practical to apply filters on all the inputs. Besides there are uses for toString() beyond logging and I don't want toString() to uniformly mask for all calls (only logging).
I have tried using the %replace method in logback.xml:
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %replace(%msg){'f k\="pin">(.*?)</f','f k\="pin">**********</f'}%n</pattern>
This was successful (after replacing the angle brackets with character entities), but it can only replace a single pattern. I would also like to perform the equivalent of
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %replace(%msg){'pin=(.*?),','pin=**********,'}%n</pattern>
at the same time, but cannot. There is no way to mask two patterns in the one %replace.
The other way that has been loosely discussed on the interblags is extending something on the appender/encoder/layout hierarchy, but every attempt to intercept the ILoggingEvent has resulted in a collapse of the whole system, usually through instantiation errors or UnsupportedOperationException.
For example, I tried extending 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;
}
}
and then adjusting the 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>
I have tried many other insertions, so I was wondering if anyone has actually achieved what I am attempting and if they could provide any clues or a solution.
I've got censors in https://github.com/tersesystems/terse-logback that allow you to define a censor in one place and then refer to it in multiple appenders.
From the documentation:
The pattern
p
can be arbitrarily complex and in particular can contain multiple conversion keywords.Facing same problem having to replace 2 patterns in a message, I just tried to
chain
sop
is just an invocation of replace, in my case:Worked great, though I wonder if I'm pushing it a bit and
p
can be indeed that arbitrarily complex.I've used censor based on RegexCensor from library https://github.com/tersesystems/terse-logback. In logback.xml
where i put list regex replacements.
and used it in logback.xml like this
You need to wrap layout using
LayoutWrappingEncoder
. And also I believe you cannot use spring here as logback is not managed by spring.Here is the updated class.
And sample logback.xml
UPDATE
Here its better approach, set Pattern during init itself. such that we can avoid recreating Pattern again and again and this implementation is close to realistic usecase.
public class MaskingPatternLayout extends PatternLayout {
And the updated Configuration file.
Output
A very similar but slightly different approach evolves around customizing CompositeConverter and defining a
<conversionRule ...>
within the logback that references the custom converter.In one of my tech-demo projects I defined a MaskingConverter class that defines a series of patterns the logging event is analyzed with and on a match updated which is used inside my logback configuration.
As link-only answers are not that beloved here at SO I'll post the important parts of the code here and explain what it does and why it is set up like that. Starting with the Java-based custom converter class:
This class defines a number of RegEx patterns the respective log-line should be compared against and on a match lead to an update of the event by masking the passwords.
Note that this code sample assumes that a log line only contains one kind of password. You are of course free to adapt the bahvior to your needs in case you want to probe each line for multiple pattern matches.
To apply this converter one simply has to add the following line to the logback configuration:
which defines a new function
mask
which can be used in a pattern in order to mask any log events matching any of the patterns defined in the custom converter. This function can now be used inside a pattern to tell Logback to perform the logic on each log event. The respective pattern might be something along the lines below:where
%mask(%msg)
will take the original log-line as input and perform the password masking on each of the lines passed to that function.As probing each line for one or multiple pattern matches might be costly, the Java code above includes Markers that can be used in log statements to send certain meta information on the log statement itself to Logback/SLF4J. Based on such markers different behaviors might be achievable. In the scenario presented a marker interface can be used to tell Logback that the respective log line contains confidential information and thus requires masking if it matches. Any log line that isn't marked as confidential will be ignored by this converter which helps in pumping out the lines faster as no pattern matching needs to be performed on those lines.
In Java such a marker can be added to a log statement like this:
which might produce a log line similar to
Received basic auth header: Basic QlRXXXlQ=
for the above mentioned custom converter, which leaves the first and last couple of characters in tact but obfuscates the middle bits withXXX
.