Skip to content

Commit

Permalink
fix: Add log masking class for sensitive logs (#2003)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
CarsonCook committed Jan 14, 2022
1 parent 56a4c2b commit 994b483
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 12 deletions.
12 changes: 8 additions & 4 deletions apiml-common/src/main/resources/logback.xml
Expand Up @@ -21,8 +21,10 @@
</if>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${apimlLogPattern}</pattern>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
<pattern>${apimlLogPattern}</pattern>
</layout>
</encoder>
</appender>

Expand All @@ -39,8 +41,10 @@
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>${apimlLogPattern}</pattern>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
<pattern>${apimlLogPattern}</pattern>
</layout>
</encoder>
</appender>

Expand Down
@@ -0,0 +1,71 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.product.logging;

import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

public class MaskingLogPatternLayout extends PatternLayout {
private static final String MASK_VALUE = "***";
private static final Pattern maskPatterns = new MaskPatternBuilder()
.addJsonValue("password")
.addJsonValue("newPassword")
.build();

@Override
public String doLayout(ILoggingEvent event) {
return maskMessage(super.doLayout(event));
}

protected String maskMessage(String message) {
StringBuilder sb = new StringBuilder(message);
Matcher matcher = maskPatterns.matcher(sb);
while (matcher.find()) {
IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> {
if (matcher.group(group) != null) {
sb.replace(matcher.start(group), matcher.end(group), MASK_VALUE);
}
});
}
return sb.toString();
}

public static class MaskPatternBuilder {
private final List<String> maskPatterns = new ArrayList<>();

public MaskPatternBuilder add(String prefix, String capture) {
return add(prefix, capture, "");
}

public MaskPatternBuilder add(String prefix, String capture, String postfix) {
maskPatterns.add(prefix + "(" + capture + ")" + postfix);
return this;
}

public MaskPatternBuilder addJsonValue(String jsonKey, String... keys) {
// pattern to get \"KEY\":\"VALUE\" with optional white space separating them
add("\\\"" + jsonKey + "\\\"\\s*:\\s*", "\\\".*?\\\"");
for (String k : keys) {
add("\\\"" + k + "\\\"\\s*:\\s*", "\\\".*?\\\"");
}
return this;
}

public Pattern build() {
return Pattern.compile(String.join("|", maskPatterns), Pattern.MULTILINE);
}
}
}
@@ -0,0 +1,67 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.product.logging;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MaskingLogPatternLayoutTest {
@Nested
class WhenBuildMask {
private MaskingLogPatternLayout.MaskPatternBuilder underTest;

@BeforeEach
void setup() {
underTest = new MaskingLogPatternLayout.MaskPatternBuilder();
}

@Test
void givenMultiplePatterns_thenReturnMaskRegexForBoth() {
String expectedRegex = "one(cap1)|two(cap2)post";
underTest.add("one", "cap1").add("two", "cap2", "post");

assertEquals(expectedRegex, underTest.build().pattern());
}

@Test
void givenJsons_thenMaskRegexWithJsonFormat() {
String expectedRegex = "\\\"key1\\\"\\s*:\\s*(\\\".*?\\\")|\\\"key2\\\"\\s*:\\s*(\\\".*?\\\")";
underTest.addJsonValue("key1", "key2");

assertEquals(expectedRegex, underTest.build().pattern());
}
}

@Nested
class WhenMaskMessage {
private MaskingLogPatternLayout underTest;

@BeforeEach
void setup() {
underTest = new MaskingLogPatternLayout();
}

@Test
void givenNothingToMask_thenNoMask() {
String message = "expected log";
String actual = underTest.maskMessage(message);
assertEquals(message, actual);
}

@Test
void givenSensitiveData_thenMaskData() {
String actual = underTest.maskMessage("\"password\":\"mypassword\"");
assertEquals("\"password\":***", actual);
}
}
}
12 changes: 8 additions & 4 deletions caching-service/src/main/resources/logback.xml
Expand Up @@ -15,8 +15,10 @@
</turboFilter>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${apimlLogPattern}</pattern>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
<pattern>${apimlLogPattern}</pattern>
</layout>
</encoder>
</appender>

Expand All @@ -33,8 +35,10 @@
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>${apimlLogPattern}</pattern>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
<pattern>${apimlLogPattern}</pattern>
</layout>
</encoder>
</appender>

Expand Down
12 changes: 8 additions & 4 deletions metrics-service/src/main/resources/logback.xml
Expand Up @@ -15,8 +15,10 @@
</turboFilter>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${apimlLogPattern}</pattern>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
<pattern>${apimlLogPattern}</pattern>
</layout>
</encoder>
</appender>

Expand All @@ -33,8 +35,10 @@
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>${apimlLogPattern}</pattern>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.zowe.apiml.product.logging.MaskingLogPatternLayout">
<pattern>${apimlLogPattern}</pattern>
</layout>
</encoder>
</appender>

Expand Down

0 comments on commit 994b483

Please sign in to comment.