Skip to content

Commit 994b483

Browse files
authored
fix: Add log masking class for sensitive logs (#2003)
* Add logging mask class Signed-off-by: Carson Cook <carson.cook@ibm.com> * Clean up masking layout Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add json value helpers to masker Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add log mask to all logback configurations Signed-off-by: Carson Cook <carson.cook@ibm.com> * Fix typo in json patterns Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add unit tests Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add license header Signed-off-by: Carson Cook <carson.cook@ibm.com>
1 parent 56a4c2b commit 994b483

File tree

5 files changed

+162
-12
lines changed

5 files changed

+162
-12
lines changed

apiml-common/src/main/resources/logback.xml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
</if>
2222
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
2323
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
24-
<encoder>
25-
<pattern>${apimlLogPattern}</pattern>
24+
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
25+
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
26+
<pattern>${apimlLogPattern}</pattern>
27+
</layout>
2628
</encoder>
2729
</appender>
2830

@@ -39,8 +41,10 @@
3941
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
4042
</triggeringPolicy>
4143

42-
<encoder>
43-
<pattern>${apimlLogPattern}</pattern>
44+
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
45+
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
46+
<pattern>${apimlLogPattern}</pattern>
47+
</layout>
4448
</encoder>
4549
</appender>
4650

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
package org.zowe.apiml.product.logging;
11+
12+
import ch.qos.logback.classic.PatternLayout;
13+
import ch.qos.logback.classic.spi.ILoggingEvent;
14+
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
import java.util.regex.Matcher;
18+
import java.util.regex.Pattern;
19+
import java.util.stream.IntStream;
20+
21+
public class MaskingLogPatternLayout extends PatternLayout {
22+
private static final String MASK_VALUE = "***";
23+
private static final Pattern maskPatterns = new MaskPatternBuilder()
24+
.addJsonValue("password")
25+
.addJsonValue("newPassword")
26+
.build();
27+
28+
@Override
29+
public String doLayout(ILoggingEvent event) {
30+
return maskMessage(super.doLayout(event));
31+
}
32+
33+
protected String maskMessage(String message) {
34+
StringBuilder sb = new StringBuilder(message);
35+
Matcher matcher = maskPatterns.matcher(sb);
36+
while (matcher.find()) {
37+
IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> {
38+
if (matcher.group(group) != null) {
39+
sb.replace(matcher.start(group), matcher.end(group), MASK_VALUE);
40+
}
41+
});
42+
}
43+
return sb.toString();
44+
}
45+
46+
public static class MaskPatternBuilder {
47+
private final List<String> maskPatterns = new ArrayList<>();
48+
49+
public MaskPatternBuilder add(String prefix, String capture) {
50+
return add(prefix, capture, "");
51+
}
52+
53+
public MaskPatternBuilder add(String prefix, String capture, String postfix) {
54+
maskPatterns.add(prefix + "(" + capture + ")" + postfix);
55+
return this;
56+
}
57+
58+
public MaskPatternBuilder addJsonValue(String jsonKey, String... keys) {
59+
// pattern to get \"KEY\":\"VALUE\" with optional white space separating them
60+
add("\\\"" + jsonKey + "\\\"\\s*:\\s*", "\\\".*?\\\"");
61+
for (String k : keys) {
62+
add("\\\"" + k + "\\\"\\s*:\\s*", "\\\".*?\\\"");
63+
}
64+
return this;
65+
}
66+
67+
public Pattern build() {
68+
return Pattern.compile(String.join("|", maskPatterns), Pattern.MULTILINE);
69+
}
70+
}
71+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
package org.zowe.apiml.product.logging;
11+
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Nested;
14+
import org.junit.jupiter.api.Test;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
18+
public class MaskingLogPatternLayoutTest {
19+
@Nested
20+
class WhenBuildMask {
21+
private MaskingLogPatternLayout.MaskPatternBuilder underTest;
22+
23+
@BeforeEach
24+
void setup() {
25+
underTest = new MaskingLogPatternLayout.MaskPatternBuilder();
26+
}
27+
28+
@Test
29+
void givenMultiplePatterns_thenReturnMaskRegexForBoth() {
30+
String expectedRegex = "one(cap1)|two(cap2)post";
31+
underTest.add("one", "cap1").add("two", "cap2", "post");
32+
33+
assertEquals(expectedRegex, underTest.build().pattern());
34+
}
35+
36+
@Test
37+
void givenJsons_thenMaskRegexWithJsonFormat() {
38+
String expectedRegex = "\\\"key1\\\"\\s*:\\s*(\\\".*?\\\")|\\\"key2\\\"\\s*:\\s*(\\\".*?\\\")";
39+
underTest.addJsonValue("key1", "key2");
40+
41+
assertEquals(expectedRegex, underTest.build().pattern());
42+
}
43+
}
44+
45+
@Nested
46+
class WhenMaskMessage {
47+
private MaskingLogPatternLayout underTest;
48+
49+
@BeforeEach
50+
void setup() {
51+
underTest = new MaskingLogPatternLayout();
52+
}
53+
54+
@Test
55+
void givenNothingToMask_thenNoMask() {
56+
String message = "expected log";
57+
String actual = underTest.maskMessage(message);
58+
assertEquals(message, actual);
59+
}
60+
61+
@Test
62+
void givenSensitiveData_thenMaskData() {
63+
String actual = underTest.maskMessage("\"password\":\"mypassword\"");
64+
assertEquals("\"password\":***", actual);
65+
}
66+
}
67+
}

caching-service/src/main/resources/logback.xml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
</turboFilter>
1616
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
1717
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
18-
<encoder>
19-
<pattern>${apimlLogPattern}</pattern>
18+
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
19+
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
20+
<pattern>${apimlLogPattern}</pattern>
21+
</layout>
2022
</encoder>
2123
</appender>
2224

@@ -33,8 +35,10 @@
3335
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
3436
</triggeringPolicy>
3537

36-
<encoder>
37-
<pattern>${apimlLogPattern}</pattern>
38+
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
39+
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
40+
<pattern>${apimlLogPattern}</pattern>
41+
</layout>
3842
</encoder>
3943
</appender>
4044

metrics-service/src/main/resources/logback.xml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
</turboFilter>
1616
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
1717
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
18-
<encoder>
19-
<pattern>${apimlLogPattern}</pattern>
18+
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
19+
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
20+
<pattern>${apimlLogPattern}</pattern>
21+
</layout>
2022
</encoder>
2123
</appender>
2224

@@ -33,8 +35,10 @@
3335
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
3436
</triggeringPolicy>
3537

36-
<encoder>
37-
<pattern>${apimlLogPattern}</pattern>
38+
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
39+
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
40+
<pattern>${apimlLogPattern}</pattern>
41+
</layout>
3842
</encoder>
3943
</appender>
4044

0 commit comments

Comments
 (0)