Skip to content

Commit

Permalink
Merge pull request #829 from YuliiaKovalova/dev/ykovalova/add_secrets…
Browse files Browse the repository at this point in the history
…_search

Add secrets search and bump DotUtils.MsBuild.BinlogRedactor.SensitiveDataDetector version
  • Loading branch information
KirillOsenkov authored Nov 1, 2024
2 parents e9fc4bc + 44b5115 commit 49aaad1
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<PackageVersion Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<PackageVersion Include="DiffPlex" Version="1.7.2" />
<PackageVersion Include="DotUtils.MsBuild.BinlogRedactor.SensitiveDataDetector" Version="0.0.8" />
<PackageVersion Include="DotUtils.MsBuild.BinlogRedactor.SensitiveDataDetector" Version="0.0.9" />
<PackageVersion Include="DotUtils.StreamUtils.Sources" Version="0.0.8" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="GuiLabs.Language.Xml" Version="1.2.93" />
Expand Down
29 changes: 24 additions & 5 deletions src/StructuredLogViewer/Controls/BuildControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public partial class BuildControl : UserControl

private PropertiesAndItemsSearch propertiesAndItemsSearch;

private SecretsSearch secretsSearch;

public BuildControl(Build build, string logFilePath)
{
InitializeComponent();
Expand Down Expand Up @@ -137,6 +139,7 @@ public BuildControl(Build build, string logFilePath)
propertiesAndItemsControl.WatermarkDisplayed += UpdatePropertiesAndItemsWatermark;
propertiesAndItemsControl.RecentItemsCategory = "PropertiesAndItems";

secretsSearch = (SecretsSearch)build.SearchExtensions.FirstOrDefault(se => se is SecretsSearch);
SetProjectContext(null);

VirtualizingPanel.SetIsVirtualizing(treeView, SettingsService.EnableTreeViewVirtualization);
Expand Down Expand Up @@ -643,6 +646,7 @@ private void PopulateProjectGraph()
"$task $time",
"$message CompilerServer failed",
"will be compiled because",
"$secret"
};

private static string[] nodeKinds = new[]
Expand All @@ -662,7 +666,8 @@ private void PopulateProjectGraph()
"$csc",
"$rar",
"$import",
"$noimport"
"$noimport",
"$secret"
};

private static Inline MakeLink(string query, SearchAndResultsControl searchControl, string before = " \u2022 ", string after = "\r\n")
Expand Down Expand Up @@ -1025,18 +1030,32 @@ private object FindInFiles(string searchText, int maxResults, CancellationToken
{
var results = new List<(string, IEnumerable<(int, string)>)>();

NodeQueryMatcher notQueryMatcher = new NodeQueryMatcher(searchText);
bool isSecretsSearch = !string.IsNullOrEmpty(searchText) && searchText.StartsWith("$secret");

foreach (var file in archiveFile.Files)
{
if (cancellationToken.IsCancellationRequested)
{
return null;
}

var haystack = file.Value;
var resultsInFile = haystack.Find(searchText);
if (resultsInFile.Count > 0)
if (isSecretsSearch)
{
results.Add((file.Key, resultsInFile.Select(lineNumber => (lineNumber, haystack.GetLineText(lineNumber)))));
var searchResults = secretsSearch.SearchSecrets(file.Value.Text, notQueryMatcher.NotMatchers, maxResults);
if (searchResults.Count > 0)
{
results.Add((file.Key, searchResults.Select(sr => (sr.Line - 1, sr.Secret))));
}
}
else
{
var haystack = file.Value;
var resultsInFile = haystack.Find(searchText);
if (resultsInFile.Count > 0)
{
results.Add((file.Key, resultsInFile.Select(lineNumber => (lineNumber, haystack.GetLineText(lineNumber)))));
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/StructuredLogViewer/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ await System.Threading.Tasks.Task.Run(() =>
try
{
BuildAnalyzer.AnalyzeBuild(build);
build.SearchExtensions.Add(new SecretsSearch(build));
build.SearchExtensions.Add(new NuGetSearch(build));
}
catch (Exception ex)
Expand Down
3 changes: 1 addition & 2 deletions src/StructuredLogger.Utils/BinlogRedactor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Threading;
using Microsoft.Build.Logging;
using Microsoft.Build.Logging.StructuredLogger;
using Microsoft.Build.SensitiveDataDetector;

Expand Down Expand Up @@ -61,7 +60,7 @@ public static void RedactSecrets(
sensitiveDataKind |= SensitiveDataKind.Username;
}

ISensitiveDataRedactor sensitiveDataRedactor = SensitiveDataDetectorFactory.GetSecretsDetector(
ISensitiveDataRedactor sensitiveDataRedactor = SensitiveDataDetectorFactory.GetSecretsRedactor(
sensitiveDataKind,
redactorOptions.IdentifyReplacemenets,
redactorOptions.TokensToRedact);
Expand Down
137 changes: 137 additions & 0 deletions src/StructuredLogger.Utils/SecretsSearch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using DotUtils.MsBuild.SensitiveDataDetector;
using Microsoft.Build.SensitiveDataDetector;
using StructuredLogViewer;

namespace Microsoft.Build.Logging.StructuredLogger
{
public class SecretsSearch : ISearchExtension
{
private readonly Build _build;
private readonly Dictionary<SensitiveDataKind, ISensitiveDataDetector> _detectors;
private readonly Dictionary<string, Dictionary<SensitiveDataKind, List<SecretDescriptor>>> _secretCache = new();

public SecretsSearch(Build build)
{
_build = build ?? throw new ArgumentNullException(nameof(build));

_detectors = new()
{
{ SensitiveDataKind.CommonSecrets, SensitiveDataDetectorFactory.GetSecretsDetector(SensitiveDataKind.CommonSecrets, false) },
{ SensitiveDataKind.ExplicitSecrets, SensitiveDataDetectorFactory.GetSecretsDetector(SensitiveDataKind.ExplicitSecrets, false) },
{ SensitiveDataKind.Username, SensitiveDataDetectorFactory.GetSecretsDetector(SensitiveDataKind.Username, false) }
};
}

public bool TryGetResults(NodeQueryMatcher matcher, IList<SearchResult> results, int maxResults)
{
if (string.Equals(matcher.TypeKeyword, "secret", StringComparison.OrdinalIgnoreCase))
{
var activeDetectors = GetActiveDetectors(matcher.NotMatchers);
var foundResults = ScanForSecrets(_build.StringTable.Instances, activeDetectors, maxResults);

if (foundResults.Any())
{
foreach (var result in foundResults)
{
results.Add(result);
}
}
else
{
results.Add(new SearchResult(new Message { Text = "No secret(s) were detected in the tree." }));
}

return true;
}

return false;
}

public List<SecretDescriptor> SearchSecrets(string text, IList<NodeQueryMatcher> matcher, int maxResults)
{
var activeDetectors = GetActiveDetectors(matcher);

var secrets = DetectSecrets(text, activeDetectors);

return secrets.Take(maxResults).ToList();
}

private IEnumerable<SearchResult> ScanForSecrets(IEnumerable<string> stringsPool, Dictionary<SensitiveDataKind, ISensitiveDataDetector> detectors, int maxResults)
{
var secretsSet = new HashSet<string>();
foreach (var text in stringsPool)
{
var secretResults = DetectSecrets(text, detectors);
foreach (SecretDescriptor secretDescriptor in secretResults)
{
secretsSet.Add(secretDescriptor.Secret);
}
}

var results = new List<SearchResult>();
if (_build.SearchIndex is { } index)
{
foreach (var text in secretsSet)
{
index.MaxResults = maxResults;
index.MarkResultsInTree = false;
IEnumerable<SearchResult> indexResults = index.FindNodes(text, CancellationToken.None);
if (indexResults.Any())
{
results.AddRange(indexResults);
}
}
}

return results;
}

private List<SecretDescriptor> DetectSecrets(string text, Dictionary<SensitiveDataKind, ISensitiveDataDetector> detectors)
{
if (_secretCache.TryGetValue(text, out var cachedSecrets))
{
return cachedSecrets
.Where(kv => detectors.Any(d => d.Key == kv.Key))
.SelectMany(kv => kv.Value)
.ToList();
}

var results = new Dictionary<SensitiveDataKind, List<SecretDescriptor>>();

foreach (var detector in detectors)
{
Dictionary<SensitiveDataKind, List<SecretDescriptor>> detectedSecrets = detector.Value.Detect(text);
foreach (KeyValuePair<SensitiveDataKind, List<SecretDescriptor>> kv in detectedSecrets)
{
if (kv.Value.Any())
{
results[kv.Key] = kv.Value;
}
}
}

if (results.Any())
{
_secretCache[text] = results;
}

return results.Values.SelectMany(v => v).ToList();
}

private Dictionary<SensitiveDataKind, ISensitiveDataDetector> GetActiveDetectors(IList<NodeQueryMatcher> notMatchers)
{
if (!notMatchers.Any())
{
return new Dictionary<SensitiveDataKind, ISensitiveDataDetector>(_detectors);
}

return _detectors
.Where(d => !notMatchers.Any(m => Enum.TryParse<SensitiveDataKind>(m.Query, true, out var kind) && kind == d.Key))
.ToDictionary(k => k.Key, v => v.Value);
}
}
}

0 comments on commit 49aaad1

Please sign in to comment.