diff --git a/MSBuildStructuredLog.sln b/MSBuildStructuredLog.sln
index c77296dd1..30bd7127b 100644
--- a/MSBuildStructuredLog.sln
+++ b/MSBuildStructuredLog.sln
@@ -28,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourcesGenerator", "src\R
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BinlogTool", "src\BinlogTool\BinlogTool.csproj", "{35F44EA6-7259-43CC-8C17-E058F3EB86D3}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredLogger.Utils", "src\StructuredLogger.Utils\StructuredLogger.Utils.csproj", "{AC634B46-D57C-44C5-BF56-480843182F21}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Mixed Platforms = Debug|Mixed Platforms
@@ -66,6 +68,10 @@ Global
{35F44EA6-7259-43CC-8C17-E058F3EB86D3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{35F44EA6-7259-43CC-8C17-E058F3EB86D3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{35F44EA6-7259-43CC-8C17-E058F3EB86D3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {AC634B46-D57C-44C5-BF56-480843182F21}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {AC634B46-D57C-44C5-BF56-480843182F21}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {AC634B46-D57C-44C5-BF56-480843182F21}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {AC634B46-D57C-44C5-BF56-480843182F21}.Release|Mixed Platforms.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/BinlogTool/BinlogTool.csproj b/src/BinlogTool/BinlogTool.csproj
index 4946edf75..172b2a1d9 100644
--- a/src/BinlogTool/BinlogTool.csproj
+++ b/src/BinlogTool/BinlogTool.csproj
@@ -2,7 +2,7 @@
Exe
- 1.0.6
+ 1.0.7
net7.0
latest
embedded
@@ -23,11 +23,12 @@
+
-
+
diff --git a/src/BinlogTool/Program.cs b/src/BinlogTool/Program.cs
index cbef55236..8ad53ca3b 100644
--- a/src/BinlogTool/Program.cs
+++ b/src/BinlogTool/Program.cs
@@ -1,7 +1,9 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Logging.StructuredLogger;
+using StructuredLogger.Utils;
namespace BinlogTool
{
@@ -16,7 +18,8 @@ binlogtool listtools input.binlog
binlogtool savefiles input.binlog output_path
binlogtool reconstruct input.binlog output_path
binlogtool savestrings input.binlog output.txt
- binlogtool search *.binlog search string");
+ binlogtool search *.binlog search string
+ binlogtool redact --input:path --recurse --in-place -p:list -p:of -p:secrets -p:to -p:redact");
return;
}
@@ -71,6 +74,58 @@ binlogtool savestrings input.binlog output.txt
return;
}
+ if (firstArg == "redact")
+ {
+ List redactTokens = new List();
+ List inputPaths = new List();
+ bool recurse = false;
+ bool inPlace = false;
+
+ foreach (var arg in args.Skip(1))
+ {
+ if (arg.StartsWith("--input:", StringComparison.OrdinalIgnoreCase))
+ {
+ var input = arg.Substring("--input:".Length);
+ if (string.IsNullOrEmpty(input))
+ {
+ Console.Error.WriteLine("Invalid input path");
+ return;
+ }
+
+ inputPaths.Add(input);
+ }
+ else if (arg.StartsWith("-p:", StringComparison.OrdinalIgnoreCase))
+ {
+ var redactToken = arg.Substring("-p:".Length);
+ if (string.IsNullOrEmpty(redactToken))
+ {
+ Console.Error.WriteLine("Invalid redact token");
+ return;
+ }
+
+ redactTokens.Add(redactToken);
+ }
+ else if (arg.Equals("--recurse", StringComparison.OrdinalIgnoreCase))
+ {
+ recurse = true;
+ }
+ else if (arg.Equals("--in-place", StringComparison.OrdinalIgnoreCase))
+ {
+ inPlace = true;
+ }
+ else
+ {
+ Console.Error.WriteLine($"Invalid argument: {arg}");
+ Console.Error.WriteLine("binlogtool redact --input:path --recurse --in-place -p:list -p:of -p:secrets -p:to -p:redact");
+ Console.Error.WriteLine("All arguments are optional (missing input assumes current working directory. Missing tokens lead only to autoredactions. Missing --in-place will create new logs with suffix.)");
+ return;
+ }
+ }
+
+ Redact.Run(inputPaths, redactTokens, inPlace, recurse);
+ return;
+ }
+
Console.Error.WriteLine("Invalid arguments");
}
diff --git a/src/BinlogTool/Redact.cs b/src/BinlogTool/Redact.cs
new file mode 100644
index 000000000..c1e5fbe6c
--- /dev/null
+++ b/src/BinlogTool/Redact.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using StructuredLogger.Utils;
+
+namespace BinlogTool
+{
+ internal static class Redact
+ {
+ public static void Run(List inputs, List tokens, bool inPlace, bool recurse)
+ {
+ if (!inputs.Any())
+ {
+ // Default - current directory
+ inputs.Add(string.Empty);
+ }
+
+ var inputBinlogs = inputs.SelectMany(input => Searcher.FindBinlogs(input, recurse)).ToList();
+
+ if (!inputBinlogs.Any())
+ {
+ Log.WriteError("No binlogs found.");
+ return;
+ }
+
+ if (inputBinlogs.Count > 1)
+ {
+ Log.WriteLine(
+ $"Found {inputBinlogs.Count} binlog files. Will redact secrets in all. (found files: {(string.Join(',', inputBinlogs))})");
+ }
+
+ BinlogRedactorOptions options = new BinlogRedactorOptions(string.Empty)
+ {
+ TokensToRedact = tokens.ToArray(),
+ IdentifyReplacemenets = true,
+ AutodetectCommonPatterns = true,
+ AutodetectUsername = true,
+ ProcessEmbeddedFiles = true,
+ };
+
+ var overallStopwatch = Stopwatch.StartNew();
+
+ foreach (var inputBinlog in inputBinlogs)
+ {
+ options.InputPath = inputBinlog;
+ options.OutputFileName = GetOutputFileName(inputBinlog);
+
+ Log.WriteLine($"Redacting binlog {inputBinlog} to {options.OutputFileName} ({GetFileSizeInKB(inputBinlog)} KB)");
+
+ var stopwatch = Stopwatch.StartNew();
+
+ BinlogRedactor.RedactSecrets(options, progress: null);
+
+ stopwatch.Stop();
+ Log.WriteLine($"Redacting done. Duration: {stopwatch.Elapsed}");
+ }
+
+ overallStopwatch.Stop();
+ if(inputBinlogs.Count > 1)
+ {
+ Log.WriteLine($"Redacting all binlogs done. Duration: {overallStopwatch.Elapsed}");
+ }
+
+ string GetOutputFileName(string inputFileName)
+ {
+ if (inPlace)
+ {
+ return inputFileName;
+ }
+
+ return Path.ChangeExtension(inputFileName, ".redacted.binlog");
+ }
+
+ long GetFileSizeInKB(string path)
+ => new FileInfo(path).Length / 1024;
+ }
+ }
+}
diff --git a/src/BinlogTool/Search.cs b/src/BinlogTool/Search.cs
index efa124a72..50d39808f 100644
--- a/src/BinlogTool/Search.cs
+++ b/src/BinlogTool/Search.cs
@@ -1,4 +1,5 @@
-using System;
+using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
@@ -7,23 +8,39 @@
namespace BinlogTool
{
- public class Searcher
+ public static class Searcher
{
public static void Search(string binlogs, string search)
{
- if (string.IsNullOrEmpty(binlogs))
+ var files = FindBinlogs(binlogs, recurse: true).ToList();
+ Search(files, search);
+ }
+
+ public static IEnumerable FindBinlogs(string inputPath, bool recurse)
+ {
+ if (string.IsNullOrEmpty(inputPath))
{
- binlogs = "*.binlog";
+ inputPath = "*.binlog";
}
- binlogs = binlogs.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+ inputPath = inputPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+
+ if (File.Exists(inputPath))
+ {
+ return new[] { inputPath };
+ }
+
+ if (Directory.Exists(inputPath))
+ {
+ inputPath = Path.Combine(inputPath, "*.binlog");
+ }
string fileName;
string directory;
- if (binlogs.Contains(Path.DirectorySeparatorChar))
+ if (inputPath.Contains(Path.DirectorySeparatorChar))
{
- fileName = Path.GetFileName(binlogs);
- directory = Path.GetDirectoryName(binlogs);
+ fileName = Path.GetFileName(inputPath);
+ directory = Path.GetDirectoryName(inputPath);
if (!Path.IsPathRooted(directory))
{
directory = Path.GetFullPath(directory);
@@ -31,15 +48,15 @@ public static void Search(string binlogs, string search)
}
else
{
- fileName = binlogs;
+ fileName = inputPath;
directory = Environment.CurrentDirectory;
}
- var files = Directory.GetFiles(directory, fileName, SearchOption.AllDirectories);
- Search(files, search);
+ return Directory.EnumerateFiles(directory, fileName,
+ new EnumerationOptions() { IgnoreInaccessible = true, RecurseSubdirectories = recurse, });
}
- private static void Search(string[] files, string search)
+ private static void Search(IEnumerable files, string search)
{
foreach (var file in files)
{
@@ -88,4 +105,4 @@ public static void PrintTree(BaseNode node, int indent = 0)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/StructuredLogViewer/Controls/RedactInputControl.xaml b/src/StructuredLogViewer/Controls/RedactInputControl.xaml
new file mode 100644
index 000000000..32f4415ae
--- /dev/null
+++ b/src/StructuredLogViewer/Controls/RedactInputControl.xaml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/StructuredLogViewer/Controls/RedactInputControl.xaml.cs b/src/StructuredLogViewer/Controls/RedactInputControl.xaml.cs
new file mode 100644
index 000000000..74ee75a9c
--- /dev/null
+++ b/src/StructuredLogViewer/Controls/RedactInputControl.xaml.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace StructuredLogViewer.Controls
+{
+ ///
+ /// Interaction logic for RedactInputControl.xaml
+ ///
+ public partial class RedactInputControl : Window
+ {
+ private readonly Func _getSaveAsDestination;
+ public string DestinationFile { get; private set; }
+ public bool RedactCommonCredentials { get; private set; } = true;
+ public bool RedactUsername { get; set; } = true;
+ public bool RedactEmbeddedFiles { get; set; } = true;
+ public bool DistinguishSecretsReplacements { get; set; } = true;
+ public string SecretsBlock
+ {
+ get { return ChckbxCustomSecrets.IsChecked == true ? TxtSecrets.Text : null; }
+ }
+
+ public RedactInputControl(Func getSaveAsDestination)
+ {
+ _getSaveAsDestination = getSaveAsDestination;
+ InitializeComponent();
+ }
+
+ private void btnDialogOk_Click(object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = true;
+ }
+
+ private void btnSaveAs_Click(object sender, RoutedEventArgs e)
+ {
+ var destination = _getSaveAsDestination();
+ if (destination != null)
+ {
+ this.DestinationFile = destination;
+ this.DialogResult = true;
+ }
+ }
+
+ private void Window_ContentRendered(object sender, EventArgs e)
+ {
+ ChckbxUsername.IsChecked = RedactUsername;
+ ChckbxCommonCredentials.IsChecked = RedactCommonCredentials;
+ ChckbxCustomSecrets.IsChecked = false;
+ TxtSecrets.IsEnabled = false;
+ ChckbxEmbeddedFiles.IsChecked = RedactEmbeddedFiles;
+ ChckbxDistinguishReplacements.IsChecked = DistinguishSecretsReplacements;
+
+ TxtSecrets.SelectAll();
+ TxtSecrets.Focus();
+ }
+
+ private void ChckbxCustomSecrets_OnChanged(object sender, RoutedEventArgs e)
+ {
+ TxtSecrets.IsEnabled = ChckbxCustomSecrets.IsChecked == true;
+ }
+
+ private void ChckbxUsername_OnChanged(object sender, RoutedEventArgs e)
+ {
+ RedactUsername = ChckbxUsername.IsChecked == true;
+ }
+
+ private void ChckbxCommonCredentials_OnChanged(object sender, RoutedEventArgs e)
+ {
+ RedactCommonCredentials = ChckbxCommonCredentials.IsChecked == true;
+ }
+
+ private void ChckbxEmbeddedFiles_OnChanged(object sender, RoutedEventArgs e)
+ {
+ RedactEmbeddedFiles = ChckbxEmbeddedFiles.IsChecked == true;
+ }
+
+ private void ChckbxDistinguishReplacements_OnChanged(object sender, RoutedEventArgs e)
+ {
+ DistinguishSecretsReplacements = ChckbxDistinguishReplacements.IsChecked == true;
+ }
+ }
+}
diff --git a/src/StructuredLogViewer/MSBuildStructuredLogViewer.nuspec b/src/StructuredLogViewer/MSBuildStructuredLogViewer.nuspec
index b47e2368b..cf8b394d0 100644
--- a/src/StructuredLogViewer/MSBuildStructuredLogViewer.nuspec
+++ b/src/StructuredLogViewer/MSBuildStructuredLogViewer.nuspec
@@ -32,6 +32,7 @@
+
@@ -42,6 +43,7 @@
+
diff --git a/src/StructuredLogViewer/MainWindow.xaml b/src/StructuredLogViewer/MainWindow.xaml
index 41715ffde..e7ef22b09 100644
--- a/src/StructuredLogViewer/MainWindow.xaml
+++ b/src/StructuredLogViewer/MainWindow.xaml
@@ -1,4 +1,4 @@
-
-
+
+