mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30: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
|
@ -41,6 +41,9 @@ Raise log level and include lib signal logs.
|
||||||
Write log output to the given file.
|
Write log output to the given file.
|
||||||
If `--verbose` is also given, the detailed logs will only be written to the log file.
|
If `--verbose` is also given, the detailed logs will only be written to the log file.
|
||||||
|
|
||||||
|
*--scrub-log*::
|
||||||
|
Scrub possibly sensitive information from the log, like phone numbers and UUIDs.
|
||||||
|
|
||||||
*--config* CONFIG::
|
*--config* CONFIG::
|
||||||
Set the path, where to store the config.
|
Set the path, where to store the config.
|
||||||
Make sure you have full read/write access to the given directory.
|
Make sure you have full read/write access to the given directory.
|
||||||
|
@ -230,7 +233,8 @@ Read the message from standard input.
|
||||||
|
|
||||||
*-a* [ATTACHMENT [ATTACHMENT ...]], *--attachment* [ATTACHMENT [ATTACHMENT ...]]::
|
*-a* [ATTACHMENT [ATTACHMENT ...]], *--attachment* [ATTACHMENT [ATTACHMENT ...]]::
|
||||||
Add one or more files as attachment.
|
Add one or more files as attachment.
|
||||||
Can be either a file path or a data URI. Data URI encoded attachments must follow the RFC 2397.
|
Can be either a file path or a data URI.
|
||||||
|
Data URI encoded attachments must follow the RFC 2397.
|
||||||
Additionally a file name can be added:
|
Additionally a file name can be added:
|
||||||
e.g.: `data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>`
|
e.g.: `data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>`
|
||||||
|
|
||||||
|
@ -257,8 +261,7 @@ Specify the mentions of the original message (same format as `--mention`).
|
||||||
|
|
||||||
*--preview-url*::
|
*--preview-url*::
|
||||||
Specify the url for the link preview.
|
Specify the url for the link preview.
|
||||||
The same url must also appear in the message body, otherwise the preview won't be
|
The same url must also appear in the message body, otherwise the preview won't be displayed by the apps.
|
||||||
displayed by the apps.
|
|
||||||
|
|
||||||
*--preview-title*::
|
*--preview-title*::
|
||||||
Specify the title for the link preview (mandatory).
|
Specify the title for the link preview (mandatory).
|
||||||
|
|
|
@ -71,6 +71,9 @@ public class App {
|
||||||
parser.addArgument("--log-file")
|
parser.addArgument("--log-file")
|
||||||
.type(File.class)
|
.type(File.class)
|
||||||
.help("Write log output to the given file. If --verbose is also given, the detailed logs will only be written to the log file.");
|
.help("Write log output to the given file. If --verbose is also given, the detailed logs will only be written to the log file.");
|
||||||
|
parser.addArgument("--scrub-log")
|
||||||
|
.action(Arguments.storeTrue())
|
||||||
|
.help("Scrub possibly sensitive information from the log, like phone numbers and UUIDs.");
|
||||||
parser.addArgument("-c", "--config")
|
parser.addArgument("-c", "--config")
|
||||||
.help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
|
.help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||||
import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
|
import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
|
||||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||||
|
import org.asamk.signal.logging.LogConfigurator;
|
||||||
import org.asamk.signal.manager.ManagerLogger;
|
import org.asamk.signal.manager.ManagerLogger;
|
||||||
import org.asamk.signal.util.SecurityProvider;
|
import org.asamk.signal.util.SecurityProvider;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
@ -47,7 +48,8 @@ public class Main {
|
||||||
final var nsLog = parseArgs(args);
|
final var nsLog = parseArgs(args);
|
||||||
final var verboseLevel = nsLog == null ? 0 : nsLog.getInt("verbose");
|
final var verboseLevel = nsLog == null ? 0 : nsLog.getInt("verbose");
|
||||||
final var logFile = nsLog == null ? null : nsLog.<File>get("log-file");
|
final var logFile = nsLog == null ? null : nsLog.<File>get("log-file");
|
||||||
configureLogging(verboseLevel, logFile);
|
final var scrubLog = nsLog != null && nsLog.getBoolean("scrub-log");
|
||||||
|
configureLogging(verboseLevel, logFile, scrubLog);
|
||||||
|
|
||||||
var parser = App.buildArgumentParser();
|
var parser = App.buildArgumentParser();
|
||||||
|
|
||||||
|
@ -82,6 +84,7 @@ public class Main {
|
||||||
.defaultHelp(false);
|
.defaultHelp(false);
|
||||||
parser.addArgument("-v", "--verbose").action(Arguments.count());
|
parser.addArgument("-v", "--verbose").action(Arguments.count());
|
||||||
parser.addArgument("--log-file").type(File.class);
|
parser.addArgument("--log-file").type(File.class);
|
||||||
|
parser.addArgument("--scrub-log").action(Arguments.storeTrue());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return parser.parseKnownArgs(args, null);
|
return parser.parseKnownArgs(args, null);
|
||||||
|
@ -90,9 +93,10 @@ public class Main {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void configureLogging(final int verboseLevel, final File logFile) {
|
private static void configureLogging(final int verboseLevel, final File logFile, final boolean scrubLog) {
|
||||||
LogConfigurator.setVerboseLevel(verboseLevel);
|
LogConfigurator.setVerboseLevel(verboseLevel);
|
||||||
LogConfigurator.setLogFile(logFile);
|
LogConfigurator.setLogFile(logFile);
|
||||||
|
LogConfigurator.setScrubSensitiveInformation(scrubLog);
|
||||||
|
|
||||||
if (verboseLevel > 0) {
|
if (verboseLevel > 0) {
|
||||||
java.util.logging.Logger.getLogger("")
|
java.util.logging.Logger.getLogger("")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.asamk.signal;
|
package org.asamk.signal.logging;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ public class LogConfigurator extends ContextAwareBase implements Configurator {
|
||||||
|
|
||||||
private static int verboseLevel = 0;
|
private static int verboseLevel = 0;
|
||||||
private static File logFile = null;
|
private static File logFile = null;
|
||||||
|
private static boolean scrubSensitiveInformation = false;
|
||||||
|
|
||||||
public static void setVerboseLevel(int verboseLevel) {
|
public static void setVerboseLevel(int verboseLevel) {
|
||||||
LogConfigurator.verboseLevel = verboseLevel;
|
LogConfigurator.verboseLevel = verboseLevel;
|
||||||
|
@ -29,6 +30,10 @@ public class LogConfigurator extends ContextAwareBase implements Configurator {
|
||||||
LogConfigurator.logFile = logFile;
|
LogConfigurator.logFile = logFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setScrubSensitiveInformation(final boolean scrubSensitiveInformation) {
|
||||||
|
LogConfigurator.scrubSensitiveInformation = scrubSensitiveInformation;
|
||||||
|
}
|
||||||
|
|
||||||
public ExecutionStatus configure(LoggerContext lc) {
|
public ExecutionStatus configure(LoggerContext lc) {
|
||||||
final var rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
|
final var rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
|
||||||
|
@ -98,18 +103,26 @@ public class LogConfigurator extends ContextAwareBase implements Configurator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private PatternLayout createSimpleLoggingLayout(final LoggerContext lc) {
|
private PatternLayout createSimpleLoggingLayout(final LoggerContext lc) {
|
||||||
return new PatternLayout() {{
|
final var patternLayout = getPatternLayout();
|
||||||
setPattern("%-5level %logger{0} - %msg%n");
|
patternLayout.setPattern("%-5level %logger{0} - %msg%n");
|
||||||
setContext(lc);
|
patternLayout.setContext(lc);
|
||||||
start();
|
patternLayout.start();
|
||||||
}};
|
return patternLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PatternLayout createDetailedLoggingLayout(final LoggerContext lc) {
|
private PatternLayout createDetailedLoggingLayout(final LoggerContext lc) {
|
||||||
return new PatternLayout() {{
|
final var patternLayout = getPatternLayout();
|
||||||
setPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXX} [%thread] %-5level %logger{36} - %msg%n");
|
patternLayout.setPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXX} [%thread] %-5level %logger{36} - %msg%n");
|
||||||
setContext(lc);
|
patternLayout.setContext(lc);
|
||||||
start();
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
org.asamk.signal.LogConfigurator
|
org.asamk.signal.logging.LogConfigurator
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue