mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-30 11:00:38 +00:00
Add --scrub-log flag to remove possibly sensitive information from the log
This commit is contained in:
parent
2e8e81a926
commit
1d77153a2b
7 changed files with 308 additions and 17 deletions
128
src/main/java/org/asamk/signal/logging/LogConfigurator.java
Normal file
128
src/main/java/org/asamk/signal/logging/LogConfigurator.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
package org.asamk.signal.logging;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.PatternLayout;
|
||||
import ch.qos.logback.classic.spi.Configurator;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.ConsoleAppender;
|
||||
import ch.qos.logback.core.FileAppender;
|
||||
import ch.qos.logback.core.Layout;
|
||||
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
|
||||
import ch.qos.logback.core.filter.Filter;
|
||||
import ch.qos.logback.core.spi.ContextAwareBase;
|
||||
import ch.qos.logback.core.spi.FilterReply;
|
||||
|
||||
public class LogConfigurator extends ContextAwareBase implements Configurator {
|
||||
|
||||
private static int verboseLevel = 0;
|
||||
private static File logFile = null;
|
||||
private static boolean scrubSensitiveInformation = false;
|
||||
|
||||
public static void setVerboseLevel(int verboseLevel) {
|
||||
LogConfigurator.verboseLevel = verboseLevel;
|
||||
}
|
||||
|
||||
public static void setLogFile(File logFile) {
|
||||
LogConfigurator.logFile = logFile;
|
||||
}
|
||||
|
||||
public static void setScrubSensitiveInformation(final boolean scrubSensitiveInformation) {
|
||||
LogConfigurator.scrubSensitiveInformation = scrubSensitiveInformation;
|
||||
}
|
||||
|
||||
public ExecutionStatus configure(LoggerContext lc) {
|
||||
final var rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
|
||||
final var defaultLevel = verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.DEBUG : Level.INFO;
|
||||
rootLogger.setLevel(defaultLevel);
|
||||
|
||||
final var consoleLayout = verboseLevel == 0 || logFile != null
|
||||
? createSimpleLoggingLayout(lc)
|
||||
: createDetailedLoggingLayout(lc);
|
||||
final var consoleAppender = createLoggingConsoleAppender(lc, createLayoutWrappingEncoder(consoleLayout));
|
||||
rootLogger.addAppender(consoleAppender);
|
||||
|
||||
lc.getLogger("com.zaxxer.hikari")
|
||||
.setLevel(verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.INFO : Level.WARN);
|
||||
|
||||
if (logFile != null) {
|
||||
consoleAppender.addFilter(new Filter<>() {
|
||||
@Override
|
||||
public FilterReply decide(final ILoggingEvent event) {
|
||||
return event.getLevel().isGreaterOrEqual(Level.INFO)
|
||||
&& !"LibSignal".equals(event.getLoggerName())
|
||||
&& (
|
||||
event.getLevel().isGreaterOrEqual(Level.WARN) || !event.getLoggerName()
|
||||
.startsWith("com.zaxxer.hikari")
|
||||
)
|
||||
|
||||
? FilterReply.NEUTRAL : FilterReply.DENY;
|
||||
}
|
||||
});
|
||||
|
||||
final var fileLayout = createDetailedLoggingLayout(lc);
|
||||
final var fileAppender = createLoggingFileAppender(lc, createLayoutWrappingEncoder(fileLayout));
|
||||
rootLogger.addAppender(fileAppender);
|
||||
}
|
||||
return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
|
||||
}
|
||||
|
||||
private ConsoleAppender<ILoggingEvent> createLoggingConsoleAppender(
|
||||
final LoggerContext lc, final LayoutWrappingEncoder<ILoggingEvent> layoutEncoder
|
||||
) {
|
||||
return new ConsoleAppender<>() {{
|
||||
setContext(lc);
|
||||
setName("console");
|
||||
setTarget("System.err");
|
||||
setEncoder(layoutEncoder);
|
||||
start();
|
||||
}};
|
||||
}
|
||||
|
||||
private FileAppender<ILoggingEvent> createLoggingFileAppender(
|
||||
final LoggerContext lc, final LayoutWrappingEncoder<ILoggingEvent> layoutEncoder
|
||||
) {
|
||||
return new FileAppender<>() {{
|
||||
setContext(lc);
|
||||
setName("file");
|
||||
setFile(logFile.getAbsolutePath());
|
||||
setEncoder(layoutEncoder);
|
||||
start();
|
||||
}};
|
||||
}
|
||||
|
||||
private LayoutWrappingEncoder<ILoggingEvent> createLayoutWrappingEncoder(final Layout<ILoggingEvent> l) {
|
||||
return new LayoutWrappingEncoder<>() {{
|
||||
setContext(l.getContext());
|
||||
setLayout(l);
|
||||
}};
|
||||
}
|
||||
|
||||
private PatternLayout createSimpleLoggingLayout(final LoggerContext lc) {
|
||||
final var patternLayout = getPatternLayout();
|
||||
patternLayout.setPattern("%-5level %logger{0} - %msg%n");
|
||||
patternLayout.setContext(lc);
|
||||
patternLayout.start();
|
||||
return patternLayout;
|
||||
}
|
||||
|
||||
private PatternLayout createDetailedLoggingLayout(final LoggerContext lc) {
|
||||
final var patternLayout = getPatternLayout();
|
||||
patternLayout.setPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXX} [%thread] %-5level %logger{36} - %msg%n");
|
||||
patternLayout.setContext(lc);
|
||||
patternLayout.start();
|
||||
return patternLayout;
|
||||
}
|
||||
|
||||
private PatternLayout getPatternLayout() {
|
||||
if (scrubSensitiveInformation) {
|
||||
return new ScrubberPatternLayout();
|
||||
} else {
|
||||
return new PatternLayout();
|
||||
}
|
||||
}
|
||||
}
|
256
src/main/java/org/asamk/signal/logging/Scrubber.java
Normal file
256
src/main/java/org/asamk/signal/logging/Scrubber.java
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.asamk.signal.logging;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Scrub data for possibly sensitive information.
|
||||
*/
|
||||
public final class Scrubber {
|
||||
|
||||
private Scrubber() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The middle group will be censored.
|
||||
* Supposedly, the shortest international phone numbers in use contain seven digits.
|
||||
* Handles URL encoded +, %2B
|
||||
*/
|
||||
private static final Pattern E164_PATTERN = Pattern.compile("(\\+|%2B)(\\d{5,13})(\\d{2})");
|
||||
private static final String E164_CENSOR = "*************";
|
||||
|
||||
/**
|
||||
* The second group will be censored.
|
||||
*/
|
||||
private static final Pattern CRUDE_EMAIL_PATTERN = Pattern.compile("\\b([^\\s/])([^\\s/]*@[^\\s]+)");
|
||||
private static final String EMAIL_CENSOR = "...@...";
|
||||
|
||||
/**
|
||||
* The middle group will be censored.
|
||||
*/
|
||||
private static final Pattern UUID_PATTERN = Pattern.compile(
|
||||
"(JOB::)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{10})([0-9a-f]{2})",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
private static final String UUID_CENSOR = "********-****-****-****-**********";
|
||||
|
||||
/**
|
||||
* The entire string is censored.
|
||||
*/
|
||||
private static final Pattern IPV4_PATTERN = Pattern.compile("\\b"
|
||||
+ "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
|
||||
+ "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
|
||||
+ "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
|
||||
+ "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
|
||||
+ "\\b");
|
||||
private static final String IPV4_CENSOR = "...ipv4...";
|
||||
|
||||
/**
|
||||
* The domain name except for TLD will be censored.
|
||||
*/
|
||||
private static final Pattern DOMAIN_PATTERN = Pattern.compile("([a-z0-9]+\\.)+([a-z0-9\\-]*[a-z\\-][a-z0-9\\-]*)",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
private static final String DOMAIN_CENSOR = "***.";
|
||||
private static final Set<String> TOP_100_TLDS = new HashSet<>(Arrays.asList("com",
|
||||
"net",
|
||||
"org",
|
||||
"jp",
|
||||
"de",
|
||||
"uk",
|
||||
"fr",
|
||||
"br",
|
||||
"it",
|
||||
"ru",
|
||||
"es",
|
||||
"me",
|
||||
"gov",
|
||||
"pl",
|
||||
"ca",
|
||||
"au",
|
||||
"cn",
|
||||
"co",
|
||||
"in",
|
||||
"nl",
|
||||
"edu",
|
||||
"info",
|
||||
"eu",
|
||||
"ch",
|
||||
"id",
|
||||
"at",
|
||||
"kr",
|
||||
"cz",
|
||||
"mx",
|
||||
"be",
|
||||
"tv",
|
||||
"se",
|
||||
"tr",
|
||||
"tw",
|
||||
"al",
|
||||
"ua",
|
||||
"ir",
|
||||
"vn",
|
||||
"cl",
|
||||
"sk",
|
||||
"ly",
|
||||
"cc",
|
||||
"to",
|
||||
"no",
|
||||
"fi",
|
||||
"us",
|
||||
"pt",
|
||||
"dk",
|
||||
"ar",
|
||||
"hu",
|
||||
"tk",
|
||||
"gr",
|
||||
"il",
|
||||
"news",
|
||||
"ro",
|
||||
"my",
|
||||
"biz",
|
||||
"ie",
|
||||
"za",
|
||||
"nz",
|
||||
"sg",
|
||||
"ee",
|
||||
"th",
|
||||
"io",
|
||||
"xyz",
|
||||
"pe",
|
||||
"bg",
|
||||
"hk",
|
||||
"lt",
|
||||
"link",
|
||||
"ph",
|
||||
"club",
|
||||
"si",
|
||||
"site",
|
||||
"mobi",
|
||||
"by",
|
||||
"cat",
|
||||
"wiki",
|
||||
"la",
|
||||
"ga",
|
||||
"xxx",
|
||||
"cf",
|
||||
"hr",
|
||||
"ng",
|
||||
"jobs",
|
||||
"online",
|
||||
"kz",
|
||||
"ug",
|
||||
"gq",
|
||||
"ae",
|
||||
"is",
|
||||
"lv",
|
||||
"pro",
|
||||
"fm",
|
||||
"tips",
|
||||
"ms",
|
||||
"sa",
|
||||
"app"));
|
||||
|
||||
public static CharSequence scrub(CharSequence in) {
|
||||
|
||||
in = scrubE164(in);
|
||||
in = scrubEmail(in);
|
||||
in = scrubUuids(in);
|
||||
in = scrubDomains(in);
|
||||
in = scrubIpv4(in);
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
private static CharSequence scrubE164(CharSequence in) {
|
||||
return scrub(in,
|
||||
E164_PATTERN,
|
||||
(matcher, output) -> output.append(matcher.group(1))
|
||||
.append(E164_CENSOR, 0, matcher.group(2).length())
|
||||
.append(matcher.group(3)));
|
||||
}
|
||||
|
||||
private static CharSequence scrubEmail(CharSequence in) {
|
||||
return scrub(in,
|
||||
CRUDE_EMAIL_PATTERN,
|
||||
(matcher, output) -> output.append(matcher.group(1)).append(EMAIL_CENSOR));
|
||||
}
|
||||
|
||||
private static CharSequence scrubUuids(CharSequence in) {
|
||||
return scrub(in, UUID_PATTERN, (matcher, output) -> {
|
||||
if (matcher.group(1) != null && !matcher.group(1).isEmpty()) {
|
||||
output.append(matcher.group(1)).append(matcher.group(2)).append(matcher.group(3));
|
||||
} else {
|
||||
output.append(UUID_CENSOR).append(matcher.group(3));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CharSequence scrubDomains(CharSequence in) {
|
||||
return scrub(in, DOMAIN_PATTERN, (matcher, output) -> {
|
||||
String match = matcher.group(0);
|
||||
if (matcher.groupCount() == 2
|
||||
&& TOP_100_TLDS.contains(matcher.group(2).toLowerCase(Locale.US))
|
||||
&& !match.endsWith("whispersystems.org")
|
||||
&& !match.endsWith("signal.org")) {
|
||||
output.append(DOMAIN_CENSOR).append(matcher.group(2));
|
||||
} else {
|
||||
output.append(match);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CharSequence scrubIpv4(CharSequence in) {
|
||||
return scrub(in, IPV4_PATTERN, (matcher, output) -> output.append(IPV4_CENSOR));
|
||||
}
|
||||
|
||||
private static CharSequence scrub(
|
||||
CharSequence in, Pattern pattern, ProcessMatch processMatch
|
||||
) {
|
||||
final StringBuilder output = new StringBuilder(in.length());
|
||||
final Matcher matcher = pattern.matcher(in);
|
||||
|
||||
int lastEndingPos = 0;
|
||||
|
||||
while (matcher.find()) {
|
||||
output.append(in, lastEndingPos, matcher.start());
|
||||
|
||||
processMatch.scrubMatch(matcher, output);
|
||||
|
||||
lastEndingPos = matcher.end();
|
||||
}
|
||||
|
||||
if (lastEndingPos == 0) {
|
||||
// there were no matches, save copying all the data
|
||||
return in;
|
||||
} else {
|
||||
output.append(in, lastEndingPos, in.length());
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private interface ProcessMatch {
|
||||
|
||||
void scrubMatch(Matcher matcher, StringBuilder output);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.asamk.signal.logging;
|
||||
|
||||
import ch.qos.logback.classic.PatternLayout;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
|
||||
public class ScrubberPatternLayout extends PatternLayout {
|
||||
|
||||
@Override
|
||||
public String doLayout(ILoggingEvent event) {
|
||||
return Scrubber.scrub(super.doLayout(event)).toString();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue