diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/DataMaskingPatternClassicLayout.java b/server/sonar-process/src/main/java/org/sonar/process/logging/DataMaskingPatternClassicLayout.java new file mode 100644 index 000000000000..ae15b77fc9a2 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/DataMaskingPatternClassicLayout.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.logging; + +import static org.sonar.process.logging.LogMaskingUtil.maskEmail; + +import ch.qos.logback.classic.PatternLayout; +import ch.qos.logback.classic.spi.ILoggingEvent; + +public class DataMaskingPatternClassicLayout extends PatternLayout { + + @Override + public String doLayout(ILoggingEvent event) { + return maskEmail(super.doLayout(event)); + } +} \ No newline at end of file diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogMaskingUtil.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogMaskingUtil.java new file mode 100644 index 000000000000..bae2ef74e213 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogMaskingUtil.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.logging; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; +import org.apache.commons.lang.StringUtils; + + +public class LogMaskingUtil { + + public static final Pattern MASK_EMAIL_PATTERN = Pattern.compile("([\\w.\\-+_]+)@[\\w.\\-+_]+\\.\\w+", + Pattern.MULTILINE); + + public static String maskMessage(String message, Pattern pattern) { + if (StringUtils.isNotEmpty(message)) { + StringBuilder sb = new StringBuilder(message); + Matcher matcher = pattern.matcher(sb); + while (matcher.find()) { + IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> { + if (matcher.group(group) != null) { + IntStream.range(matcher.start(group), + matcher.end(group)).forEach(i -> sb.setCharAt(i, '*')); + } + }); + } + return sb.toString(); + } + return message; + } + + public static String maskEmail(String message) { + return maskMessage(message, MASK_EMAIL_PATTERN); + } +} \ No newline at end of file diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java index 56904ffb05e8..52290ffe6d3f 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackJsonLayout.java @@ -19,6 +19,10 @@ */ package org.sonar.process.logging; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static org.sonar.process.logging.LogMaskingUtil.maskEmail; + import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.StackTraceElementProxy; @@ -35,9 +39,6 @@ import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - /** * Formats logs in JSON. *

@@ -76,11 +77,14 @@ public String doLayout(ILoggingEvent event) { json.name(entry.getKey()).value(entry.getValue()); } } + + String formattedMsg = NEWLINE_REGEXP.matcher(event.getFormattedMessage()).replaceAll("\r"); + json .name("timestamp").value(DATE_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()))) .name("severity").value(event.getLevel().toString()) .name("logger").value(event.getLoggerName()) - .name("message").value(NEWLINE_REGEXP.matcher(event.getFormattedMessage()).replaceAll("\r")); + .name("message").value(maskEmail(formattedMsg)); IThrowableProxy tp = event.getThrowableProxy(); if (tp != null) { json.name("stacktrace").beginArray(); diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/PatternLayoutEncoder.java b/server/sonar-process/src/main/java/org/sonar/process/logging/PatternLayoutEncoder.java index 8540951cae32..a69c085228c2 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/logging/PatternLayoutEncoder.java +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/PatternLayoutEncoder.java @@ -29,7 +29,7 @@ public class PatternLayoutEncoder extends PatternLayoutEncoderBase { + + @Override + public void start() { + PatternLayout patternLayout = new DataMaskingPatternAccessLayout(); + patternLayout.setContext(context); + patternLayout.setPattern(getPattern()); + patternLayout.start(); + this.layout = patternLayout; + super.start(); + } +} \ No newline at end of file diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java b/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java index 23e9ca527c5e..8232001debae 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatAccessLog.java @@ -19,15 +19,14 @@ */ package org.sonar.server.app; -import ch.qos.logback.access.PatternLayoutEncoder; import ch.qos.logback.core.FileAppender; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; import org.apache.catalina.startup.Tomcat; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.process.logging.LogbackHelper; import org.sonar.process.Props; +import org.sonar.process.logging.LogbackHelper; class TomcatAccessLog { @@ -47,7 +46,7 @@ private static void configureLogbackAccess(Tomcat tomcat, Props props) { LogbackHelper helper = new LogbackHelper(); LogbackHelper.RollingPolicy policy = helper.createRollingPolicy(valve, props, "access"); FileAppender appender = policy.createAppender("ACCESS_LOG"); - PatternLayoutEncoder fileEncoder = new PatternLayoutEncoder(); + PatternAccessLayoutEncoder fileEncoder = new PatternAccessLayoutEncoder(); fileEncoder.setContext(valve); fileEncoder.setPattern(props.value(PROPERTY_PATTERN, DEFAULT_SQ_ACCESS_LOG_PATTERN)); fileEncoder.start(); diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/UserSessionFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/UserSessionFilter.java index 641b5e29b9cd..02a6962d57d9 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/UserSessionFilter.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/UserSessionFilter.java @@ -19,6 +19,8 @@ */ package org.sonar.server.platform.web; +import static org.sonar.process.logging.LogMaskingUtil.maskEmail; + import ch.qos.logback.classic.ClassicConstants; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; @@ -114,7 +116,7 @@ private void insertIntoMDC(ServletRequest request) { MDC.put(ClassicConstants.REQUEST_REQUEST_URL, requestURL.toString()); } MDC.put(ClassicConstants.REQUEST_METHOD, httpServletRequest.getMethod()); - MDC.put(ClassicConstants.REQUEST_QUERY_STRING, httpServletRequest.getQueryString()); + MDC.put(ClassicConstants.REQUEST_QUERY_STRING, maskEmail(httpServletRequest.getQueryString())); MDC.put(ClassicConstants.REQUEST_USER_AGENT_MDC_KEY, httpServletRequest.getHeader("User-Agent")); MDC.put(ClassicConstants.REQUEST_X_FORWARDED_FOR, httpServletRequest.getHeader("X-Forwarded-For"));