From 0a682316236ab346b7262780471de7aec2223b55 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Wed, 3 Aug 2022 12:11:38 -0500 Subject: [PATCH 01/13] Feature: Added GUID list and query suppression support --- .../SuppressCommand.cs | 65 ++++++++++++++++++- .../SuppressOptions.cs | 19 +++++- src/Sarif/Visitors/SuppressVisitor.cs | 27 ++++++-- .../SuppressCommandTests.cs | 56 ++++++++++++++-- .../Baseline/DefaultBaselineUnitTests.cs | 2 +- .../Baseline/StrictBaselineUnitTests.cs | 2 +- .../RandomSarifLogGenerator.cs | 7 +- .../Visitors/SuppressVisitorTests.cs | 59 +++++++++++------ 8 files changed, 200 insertions(+), 37 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 5cf1813fa..8be831778 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -2,9 +2,16 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; + +using Kusto.Cloud.Platform.Utils; using Microsoft.CodeAnalysis.Sarif.Driver; +using Microsoft.CodeAnalysis.Sarif.Query; +using Microsoft.CodeAnalysis.Sarif.Query.Evaluators; using Microsoft.CodeAnalysis.Sarif.Readers; using Microsoft.CodeAnalysis.Sarif.Visitors; using Microsoft.CodeAnalysis.Sarif.Writers; @@ -34,12 +41,19 @@ public int Run(SuppressOptions options) options.Formatting, out string _); + if (!string.IsNullOrWhiteSpace(options.Expression)) + { + // Merge list of guids if exit?? + options.Guids = ReturnQueryExpressionGuids(options); + } + SarifLog reformattedLog = new SuppressVisitor(options.Justification, options.Alias, - options.Guids, + options.UniqueIdentifiers, options.Timestamps, options.ExpiryInDays, - options.Status).VisitSarifLog(currentSarifLog); + options.Status, + options.Guids).VisitSarifLog(currentSarifLog); string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options); if (options.SarifOutputVersion == SarifVersion.OneZeroZero) @@ -66,6 +80,53 @@ public int Run(SuppressOptions options) return SUCCESS; } + private IEnumerable ReturnQueryExpressionGuids(SuppressOptions options) + { + int originalTotal = 0; + int matchCount = 0; + // Parse the Query and create a Result evaluator for it + IExpression expression = ExpressionParser.ParseExpression(options.Expression); + IExpressionEvaluator evaluator = expression.ToEvaluator(SarifEvaluators.ResultEvaluator); + + // Read the log + SarifLog log = ReadSarifFile(this.FileSystem, options.InputFilePath); + + foreach (Run run in log.Runs) + { + if (run.Results == null) { continue; } + run.SetRunOnResults(); + + originalTotal += run.Results.Count; + + // Find matches for Results in the Run + BitArray matches = new BitArray(run.Results.Count); + evaluator.Evaluate(run.Results, matches); + + // Count the new matches + matchCount += matches.TrueCount(); + + // Filter the Run.Results to the matches + run.Results = matches.MatchingSubset(run.Results); + + //// Write to console, if caller requested + //if (options.WriteToConsole) + //{ + // foreach (Result result in run.Results) + // { + // Console.WriteLine(result.FormatForVisualStudio()); + // } + //} + } + + // Remove any Runs with no remaining matches + log.Runs = log.Runs.Where(r => (r?.Results?.Count ?? 0) > 0).ToList(); + + var guids = log.Runs.SelectMany(x => x.Results.Select(y => y.Guid)).ToList(); + + return guids; + + } + private bool ValidateOptions(SuppressOptions options) { bool valid = true; diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index ef2162763..a10a9a52e 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Generic; + using CommandLine; using Microsoft.CodeAnalysis.Sarif.Driver; @@ -22,9 +24,22 @@ public class SuppressOptions : SingleFileOptionsBase public string Alias { get; set; } [Option( - "guids", + "uuids", HelpText = "A UUID that will be associated with a suppression.")] - public bool Guids { get; set; } + public bool UniqueIdentifiers { get; set; } + + [Value(0, + MetaName = "", + HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", + Required = false)] + public IEnumerable Guids { get; set; } + + [Option( + 'e', + "expression", + HelpText = "Result Expression to Evaluate (ex: (BaselineState != 'Unchanged'))", + Required = false)] + public string Expression { get; set; } [Option( "timestamps", diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index a421a5398..56c727268 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.CodeAnalysis.Sarif.Visitors { public class SuppressVisitor : SarifRewritingVisitor { - private readonly bool guids; + private readonly bool uuids; + private readonly IEnumerable guids; private readonly string alias; private readonly bool timestamps; private readonly DateTime timeUtc; @@ -19,19 +21,21 @@ public class SuppressVisitor : SarifRewritingVisitor public SuppressVisitor(string justification, string alias, - bool guids, + bool uuids, bool timestamps, int expiryInDays, - SuppressionStatus suppressionStatus) + SuppressionStatus suppressionStatus, + IEnumerable guids) { this.alias = alias; - this.guids = guids; + this.uuids = uuids; this.timestamps = timestamps; this.timeUtc = DateTime.UtcNow; this.expiryInDays = expiryInDays; this.justification = justification; this.suppressionStatus = suppressionStatus; this.expiryUtc = this.timeUtc.AddDays(expiryInDays); + this.guids = guids; } public override Result VisitResult(Result node) @@ -53,7 +57,7 @@ public override Result VisitResult(Result node) suppression.SetProperty(nameof(alias), alias); } - if (guids) + if (this.uuids) { suppression.Guid = Guid.NewGuid().ToString(SarifConstants.GuidFormat); } @@ -68,7 +72,18 @@ public override Result VisitResult(Result node) suppression.SetProperty(nameof(expiryUtc), expiryUtc); } - node.Suppressions.Add(suppression); + if (this.guids != null && this.guids.Any()) + { + if (this.guids.Contains(node.Guid)) + { + node.Suppressions.Add(suppression); + } + } + else + { + node.Suppressions.Add(suppression); + } + return base.VisitResult(node); } } diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index f95856d47..2da48fe11 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; using FluentAssertions; @@ -56,6 +57,14 @@ public void SuppressCommand_ShouldReturnFailure_WhenBadArgumentsAreSupplied() OutputFilePath = outputPath, Status = SuppressionStatus.Accepted }, + new SuppressOptions + { + ExpiryInDays = 1, + Justification = "some justification", + OutputFilePath = outputPath, + Expression = "fail", + Status = SuppressionStatus.Accepted + }, }; var mock = new Mock(); @@ -91,20 +100,43 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - Guids = true, + UniqueIdentifiers = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + Status = SuppressionStatus.Accepted + }, + new SuppressOptions + { + UniqueIdentifiers = true, + ExpiryInDays = 5, + Timestamps = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + Status = SuppressionStatus.Accepted + }, + new SuppressOptions + { + UniqueIdentifiers = true, + ExpiryInDays = 5, + Timestamps = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", + Expression = "BaseLineState = \"New\"", Status = SuppressionStatus.Accepted }, new SuppressOptions { - Guids = true, + UniqueIdentifiers = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", + Guids = new List() { "NEWGUID"}, + Expression = string.Empty, Status = SuppressionStatus.Accepted }, }; @@ -127,7 +159,9 @@ private static void VerifySuppressCommand(SuppressOptions options) { new Result { - RuleId = "Test0001" + RuleId = "Test0001", + Guid = "NEWGUID", + BaselineState = BaselineState.New } } } @@ -135,11 +169,20 @@ private static void VerifySuppressCommand(SuppressOptions options) }; var transformedContents = new StringBuilder(); + var currentStream = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(current))); + + var currentContents = new StringBuilder(JsonConvert.SerializeObject(current)); var mockFileSystem = new Mock(); + mockFileSystem .Setup(x => x.FileReadAllText(options.InputFilePath)) .Returns(JsonConvert.SerializeObject(current)); + mockFileSystem + .Setup(x => x.FileOpenRead(options.InputFilePath)) + .Returns(() => + currentStream); + mockFileSystem .Setup(x => x.FileCreate(options.OutputFilePath)) .Returns(() => new MemoryStreamToStringBuilder(transformedContents)); @@ -160,11 +203,16 @@ private static void VerifySuppressCommand(SuppressOptions options) suppression.GetProperty("alias").Should().Be(options.Alias); } - if (options.Guids) + if (options.UniqueIdentifiers) { suppression.Guid.Should().NotBeNullOrEmpty(); } + if (!string.IsNullOrWhiteSpace(options.Expression)) + { + suppressed.Runs[0].Results[0].BaselineState.Should().Be(BaselineState.New); + } + if (options.Timestamps && suppression.TryGetProperty("timeUtc", out DateTime timeUtc)) { timeUtc.Should().BeCloseTo(DateTime.UtcNow, DateTimeAssertPrecision); diff --git a/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs b/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs index d3e491782..1f612a8d8 100644 --- a/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs +++ b/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs @@ -42,7 +42,7 @@ public void DefaultBaseline_NewResultAdded_New() Random random = RandomSarifLogGenerator.GenerateRandomAndLog(this.output); Run baseline = RandomSarifLogGenerator.GenerateRandomRunWithoutDuplicateIssues(random, DefaultBaseline.ResultBaselineEquals.DefaultInstance, random.Next(100) + 5); Run next = baseline.DeepClone(); - next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { new Uri(@"c:\test\testfile") }, 1).First()); + next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { "NEWGUID"}, new List() { new Uri(@"c:\test\testfile") }, 1).First()); Run result = defaultBaseliner.CreateBaselinedRun(baseline, next); diff --git a/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs b/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs index fa778d22e..fb58cfff8 100644 --- a/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs +++ b/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs @@ -42,7 +42,7 @@ public void StrictBaseline_NewResultAdded_New() Random random = RandomSarifLogGenerator.GenerateRandomAndLog(this.output); Run baseline = RandomSarifLogGenerator.GenerateRandomRunWithoutDuplicateIssues(random, Result.ValueComparer, random.Next(100) + 5); Run next = baseline.DeepClone(); - next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { new Uri(@"c:\test\testfile") }, 1).First()); + next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { "NEWGUID" }, new List() { new Uri(@"c:\test\testfile") }, 1).First()); Run result = strictBaseliner.CreateBaselinedRun(baseline, next); diff --git a/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs b/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs index 8ec52b4b9..1f81c3a93 100644 --- a/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs +++ b/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs @@ -49,6 +49,7 @@ public static SarifLog GenerateSarifLogWithRuns(Random randomGen, int runCount, public static Run GenerateRandomRun(Random random, int? resultCount = null, RandomDataFields dataFields = RandomDataFields.None) { List ruleIds = new List() { "TEST001", "TEST002", "TEST003", "TEST004", "TEST005" }; + List guids = new List() { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" }; List filePaths = GenerateFakeFiles(GeneratorBaseUri, random.Next(20) + 1).Select(a => new Uri(a)).ToList(); int results = resultCount == null ? random.Next(100) : (int)resultCount; @@ -64,7 +65,7 @@ public static Run GenerateRandomRun(Random random, int? resultCount = null, Rand } }, Artifacts = GenerateFiles(filePaths), - Results = GenerateFakeResults(random, ruleIds, filePaths, results, dataFields) + Results = GenerateFakeResults(random, ruleIds, guids, filePaths, results, dataFields) }; } @@ -96,17 +97,19 @@ public static IEnumerable GenerateFakeFiles(string baseAddress, int coun return results; } - public static IList GenerateFakeResults(Random random, List ruleIds, List filePaths, int resultCount, RandomDataFields dataFields = RandomDataFields.None) + public static IList GenerateFakeResults(Random random, List ruleIds, List guids, List filePaths, int resultCount, RandomDataFields dataFields = RandomDataFields.None) { List results = new List(); for (int i = 0; i < resultCount; i++) { int fileIndex = random.Next(filePaths.Count); int ruleIndex = random.Next(ruleIds.Count); + int guidIndex = random.Next(guids.Count); results.Add(new Result() { RuleId = ruleIds[ruleIndex], RuleIndex = ruleIndex, + Guid = guids[guidIndex], Locations = new Location[] { new Location diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index 742fcb6f2..8019d3454 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentAssertions; @@ -19,61 +20,78 @@ public class SuppressVisitorTests [Fact] public void SuppressVisitor_ShouldFlowPropertiesCorrectly() { + var guids = new List() { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" }; var testCases = new[] { new { Alias = string.Empty, Justification = "some suppress justification", - Guids = false, + Uuids = false, Timestamps = false, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = false, + Uuids = false, Timestamps = false, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = false, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = true, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = true, ExpiryInDays = 1, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = true, ExpiryInDays = 1, - SuppressionStatus = SuppressionStatus.UnderReview + SuppressionStatus = SuppressionStatus.UnderReview, + Guids = new List() + }, + new + { + Alias = "some alias", + Justification = "some suppress justification", + Uuids = true, + Timestamps = true, + ExpiryInDays = 1, + SuppressionStatus = SuppressionStatus.Accepted, + Guids = guids.ToList() }, }; @@ -81,26 +99,29 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly() { VerifySuppressVisitor(testCase.Alias, testCase.Justification, - testCase.Guids, + testCase.Uuids, testCase.Timestamps, testCase.ExpiryInDays, - testCase.SuppressionStatus); + testCase.SuppressionStatus, + testCase.Guids); } } private static void VerifySuppressVisitor(string alias, string justification, - bool guids, + bool uuids, bool timestamps, int expiryInDays, - SuppressionStatus suppressionStatus) + SuppressionStatus suppressionStatus, + IEnumerable guids) { var visitor = new SuppressVisitor(justification, alias, - guids, + uuids, timestamps, expiryInDays, - suppressionStatus); + suppressionStatus, + guids); var random = new Random(); SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1); @@ -120,7 +141,7 @@ private static void VerifySuppressVisitor(string alias, suppression.GetProperty("alias").Should().Be(alias); } - if (guids) + if (uuids) { suppression.Guid.Should().NotBeNullOrEmpty(); } From a08d8da3ced9f8342d8382c0de7cb3214624a5bf Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Wed, 3 Aug 2022 15:58:11 -0500 Subject: [PATCH 02/13] Fixed bug: suppress options with guids type --- src/Sarif.Multitool.Library/SuppressOptions.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index a10a9a52e..ca0b745ec 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -28,17 +28,16 @@ public class SuppressOptions : SingleFileOptionsBase HelpText = "A UUID that will be associated with a suppression.")] public bool UniqueIdentifiers { get; set; } - [Value(0, - MetaName = "", + [Option( + "", HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", - Required = false)] + Default = null)] public IEnumerable Guids { get; set; } [Option( 'e', "expression", - HelpText = "Result Expression to Evaluate (ex: (BaselineState != 'Unchanged'))", - Required = false)] + HelpText = "Result Expression to Evaluate (ex: (BaselineState != 'Unchanged'))")] public string Expression { get; set; } [Option( From 4c62456df3edea3115e31d643f265d52eafedb27 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Wed, 3 Aug 2022 16:47:07 -0500 Subject: [PATCH 03/13] fixed bug in SuppressOptions with guids options --- src/Sarif.Multitool.Library/SuppressOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index ca0b745ec..1e98933f9 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -29,7 +29,7 @@ public class SuppressOptions : SingleFileOptionsBase public bool UniqueIdentifiers { get; set; } [Option( - "", + "guids", HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", Default = null)] public IEnumerable Guids { get; set; } From 6f0174e58996ad46feb79f178106944b7bc201c8 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Thu, 4 Aug 2022 15:03:36 -0500 Subject: [PATCH 04/13] Bug: Allowed using results-guids and expression arguments together. --- .../SuppressCommand.cs | 28 ++++++++----------- .../SuppressOptions.cs | 10 +++---- src/Sarif/Visitors/SuppressVisitor.cs | 10 +++---- .../SuppressCommandTests.cs | 26 ++++++++++++----- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 8be831778..c7ec612ee 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -43,17 +43,24 @@ public int Run(SuppressOptions options) if (!string.IsNullOrWhiteSpace(options.Expression)) { - // Merge list of guids if exit?? - options.Guids = ReturnQueryExpressionGuids(options); + var expressionGuids = ReturnQueryExpressionGuids(options); + if (options.ResultsGuids != null && options.ResultsGuids.Any()) + { + options.ResultsGuids.Union(expressionGuids); + } + else + { + options.ResultsGuids = expressionGuids; + } } SarifLog reformattedLog = new SuppressVisitor(options.Justification, options.Alias, - options.UniqueIdentifiers, + options.Guids, options.Timestamps, options.ExpiryInDays, options.Status, - options.Guids).VisitSarifLog(currentSarifLog); + options.ResultsGuids).VisitSarifLog(currentSarifLog); string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options); if (options.SarifOutputVersion == SarifVersion.OneZeroZero) @@ -107,24 +114,13 @@ private IEnumerable ReturnQueryExpressionGuids(SuppressOptions options) // Filter the Run.Results to the matches run.Results = matches.MatchingSubset(run.Results); - - //// Write to console, if caller requested - //if (options.WriteToConsole) - //{ - // foreach (Result result in run.Results) - // { - // Console.WriteLine(result.FormatForVisualStudio()); - // } - //} } // Remove any Runs with no remaining matches log.Runs = log.Runs.Where(r => (r?.Results?.Count ?? 0) > 0).ToList(); - var guids = log.Runs.SelectMany(x => x.Results.Select(y => y.Guid)).ToList(); - return guids; - + return guids; } private bool ValidateOptions(SuppressOptions options) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index 1e98933f9..e2b8a2aca 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -24,15 +24,15 @@ public class SuppressOptions : SingleFileOptionsBase public string Alias { get; set; } [Option( - "uuids", + "guids", HelpText = "A UUID that will be associated with a suppression.")] - public bool UniqueIdentifiers { get; set; } + public bool Guids { get; set; } [Option( - "guids", - HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", + "results-guids", + HelpText = "Guid(s) to SARIF log result(s) comprising the current result guid, without result matching information", Default = null)] - public IEnumerable Guids { get; set; } + public IEnumerable ResultsGuids { get; set; } [Option( 'e', diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index 56c727268..6b1694ac1 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Sarif.Visitors public class SuppressVisitor : SarifRewritingVisitor { private readonly bool uuids; - private readonly IEnumerable guids; + private readonly IEnumerable resultsGuids; private readonly string alias; private readonly bool timestamps; private readonly DateTime timeUtc; @@ -25,7 +25,7 @@ public SuppressVisitor(string justification, bool timestamps, int expiryInDays, SuppressionStatus suppressionStatus, - IEnumerable guids) + IEnumerable resultsGuids) { this.alias = alias; this.uuids = uuids; @@ -35,7 +35,7 @@ public SuppressVisitor(string justification, this.justification = justification; this.suppressionStatus = suppressionStatus; this.expiryUtc = this.timeUtc.AddDays(expiryInDays); - this.guids = guids; + this.resultsGuids = resultsGuids; } public override Result VisitResult(Result node) @@ -72,9 +72,9 @@ public override Result VisitResult(Result node) suppression.SetProperty(nameof(expiryUtc), expiryUtc); } - if (this.guids != null && this.guids.Any()) + if (this.resultsGuids != null && this.resultsGuids.Any()) { - if (this.guids.Contains(node.Guid)) + if (this.resultsGuids.Contains(node.Guid)) { node.Suppressions.Add(suppression); } diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index 2da48fe11..999b70432 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -100,7 +100,7 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", @@ -108,7 +108,7 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", @@ -118,7 +118,7 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", @@ -129,16 +129,28 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", - Guids = new List() { "NEWGUID"}, + ResultsGuids = new List() { "GUID"}, Expression = string.Empty, Status = SuppressionStatus.Accepted }, + new SuppressOptions + { + Guids = true, + ExpiryInDays = 5, + Timestamps = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + ResultsGuids = new List() { "GUID", "GUID2"}, + Expression = "BaseLineState = \"New\"", + Status = SuppressionStatus.Accepted + }, }; foreach (SuppressOptions options in optionsTestCases) @@ -160,7 +172,7 @@ private static void VerifySuppressCommand(SuppressOptions options) new Result { RuleId = "Test0001", - Guid = "NEWGUID", + Guid = "GUID", BaselineState = BaselineState.New } } @@ -203,7 +215,7 @@ private static void VerifySuppressCommand(SuppressOptions options) suppression.GetProperty("alias").Should().Be(options.Alias); } - if (options.UniqueIdentifiers) + if (options.Guids) { suppression.Guid.Should().NotBeNullOrEmpty(); } From 8c80a874608f04dbb667708f907a91c54193eeb5 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 5 Aug 2022 08:57:38 -0500 Subject: [PATCH 05/13] chore: typo in log message --- src/Sarif.Multitool.Library/SuppressCommand.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index c7ec612ee..de61885f1 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -51,7 +51,7 @@ public int Run(SuppressOptions options) else { options.ResultsGuids = expressionGuids; - } + } } SarifLog reformattedLog = new SuppressVisitor(options.Justification, @@ -76,7 +76,7 @@ public int Run(SuppressOptions options) } w.Stop(); - Console.WriteLine($"Supress completed in {w.Elapsed}."); + Console.WriteLine($"Suppress completed in {w.Elapsed}."); } catch (Exception ex) { @@ -120,7 +120,7 @@ private IEnumerable ReturnQueryExpressionGuids(SuppressOptions options) log.Runs = log.Runs.Where(r => (r?.Results?.Count ?? 0) > 0).ToList(); var guids = log.Runs.SelectMany(x => x.Results.Select(y => y.Guid)).ToList(); - return guids; + return guids; } private bool ValidateOptions(SuppressOptions options) From 4b91d6b110c443be04c87d5a2c661aee171c25c3 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 5 Aug 2022 09:12:45 -0500 Subject: [PATCH 06/13] feat: console log number of suppressions applied --- src/Sarif.Multitool.Library/SuppressCommand.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index de61885f1..419af0a69 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -53,6 +53,7 @@ public int Run(SuppressOptions options) options.ResultsGuids = expressionGuids; } } + Console.WriteLine($"Suppressing {options.ResultsGuids.Count()} of {currentSarifLog.Runs.Sum(i => i.Results.Count)} results."); SarifLog reformattedLog = new SuppressVisitor(options.Justification, options.Alias, From 46e65de2168460a1e5931cf9200bace6cdc972d0 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 5 Aug 2022 09:13:00 -0500 Subject: [PATCH 07/13] bug: comma delimit results arg --- src/Sarif.Multitool.Library/SuppressOptions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index e2b8a2aca..bf156b062 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -28,10 +28,11 @@ public class SuppressOptions : SingleFileOptionsBase HelpText = "A UUID that will be associated with a suppression.")] public bool Guids { get; set; } - [Option( + [Option( "results-guids", - HelpText = "Guid(s) to SARIF log result(s) comprising the current result guid, without result matching information", - Default = null)] + HelpText = "A comma delimited list of SARIF log result guid(s) to suppress.", + Default = null, + Separator = ',')] public IEnumerable ResultsGuids { get; set; } [Option( From 15ec4134151224ade0dbd6e0bfdcbd2b0cfa69fc Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 8 Aug 2022 16:07:35 -0500 Subject: [PATCH 08/13] feat: cis cat pro json converter --- docs/multitool-usage.md | 2 + .../BuiltInConverterFactory.cs | 1 + src/Sarif.Converters/CisCatConverter.cs | 168 ++++++++++++++++++ .../CisCatObjectModel/CisCatReport.cs | 33 ++++ .../CisCatObjectModel/CisCatReportReader.cs | 25 +++ .../CisCatObjectModel/CisCatRule.cs | 19 ++ src/Sarif.Converters/ToolFormat.cs | 3 + src/Sarif.Multitool.Library/ConvertOptions.cs | 2 +- src/Sarif/HashUtilities.cs | 11 ++ 9 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 src/Sarif.Converters/CisCatConverter.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index 151f880c2..53d129a23 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -69,9 +69,11 @@ Sarif.Multitool validate Other.sarif ``` ## Supported Converters + Run ```Sarif.Multitool convert --help``` for the current list. - AndroidStudio +- CisCat - ClangAnalyzer - ClangTidy - CppCheck diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index 8b1b1838d..b4804f890 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -25,6 +25,7 @@ private static Dictionary> CreateBuiltInConv { var result = new Dictionary>(); CreateConverterRecord(result, ToolFormat.AndroidStudio); + CreateConverterRecord(result, ToolFormat.CisCat); CreateConverterRecord(result, ToolFormat.CppCheck); CreateConverterRecord(result, ToolFormat.ClangAnalyzer); CreateConverterRecord(result, ToolFormat.ClangTidy); diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs new file mode 100644 index 000000000..2744dbeda --- /dev/null +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class CisCatConverter : ToolFileConverterBase + { + private readonly LogReader logReader; + + public CisCatConverter() + { + logReader = new CisCatReportReader(); + } + + public override string ToolName => ToolFormat.CisCat; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + //Read CIS CAT data + CisCatReport log = logReader.ReadLog(input); + + //Top level run object for the scan data + var run = new Run(); + + //Set the tool details + run.Tool = new Tool(); + run.Tool.Driver = CreateDriver(log); + + //Set the list of tool rules + run.Tool.Driver.Rules = new List(); + foreach (CisCatRule rule in log.Rules) + { + run.Tool.Driver.Rules.Add(CreateReportDescriptor(rule)); + } + + var results = new List(); + foreach (CisCatRule rule in log.Rules) + { + results.Add(CreateResult(rule)); + } + + PersistResults(output, results, run); + } + + internal ToolComponent CreateDriver(CisCatReport report) + { + + var driver = new ToolComponent(); + + driver.Name = this.ToolName; + driver.FullName = report.BenchmarkTitle; + driver.Version = report.BenchmarkVersion; + driver.SemanticVersion = report.BenchmarkVersion; + driver.InformationUri = new Uri("https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre"); + + driver.SetProperty("benchmarkId", report.BenchmarkId); + driver.SetProperty("profileId", report.ProfileId); + driver.SetProperty("profileTitle", report.ProfileTitle); + driver.SetProperty("score", report.Score); + + return driver; + } + + internal ReportingDescriptor CreateReportDescriptor(CisCatRule rule) + { + ReportingDescriptor descriptor = new ReportingDescriptor(); + + descriptor.Id = rule.RuleId; + descriptor.Name = rule.RuleTitle; + descriptor.ShortDescription = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + descriptor.FullDescription = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + descriptor.Help = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + + return descriptor; + } + + internal Result CreateResult(CisCatRule rule) + { + //set the result metadata + Result result = new Result + { + RuleId = rule.RuleId, + Message = new Message { Text = rule.RuleTitle }, + }; + + //Kind & Level determine the status + //Result: "fail": Level = Error, Kind = Fail + //Result: "info|notchecked|pass|unknown": Level = None, Kind = Informational|NotApplicable|Pass|Review + switch (rule.Result) + { + case "fail": + result.Level = FailureLevel.Error; + result.Kind = ResultKind.Fail; + break; + case "pass": + result.Level = FailureLevel.None; + result.Kind = ResultKind.Pass; + break; + case "notchecked": + result.Level = FailureLevel.None; + result.Kind = ResultKind.NotApplicable; + break; + case "informational": + result.Level = FailureLevel.None; + result.Kind = ResultKind.Informational; + break; + case "unknown": + default: + result.Level = FailureLevel.None; + result.Kind = ResultKind.Review; + break; + }; + + //Set the unique fingerprint + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); + + // var region = new Region + // { + // StartColumn = int.Parse(defect.Column), + // StartLine = int.Parse(defect.Line) + // }; + + // var fileUri = new Uri($"{defect.FilePath}", UriKind.RelativeOrAbsolute); + // var physicalLocation = new PhysicalLocation + // { + // ArtifactLocation = new ArtifactLocation + // { + // Uri = fileUri + // }, + // Region = region + // }; + + // var location = new Location + // { + // PhysicalLocation = physicalLocation + // }; + + // result.Locations = new List + // { + // location + // }; + + return result; + } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs new file mode 100644 index 000000000..ea6710f95 --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatReport + { + [JsonProperty("benchmark-id")] + public string BenchmarkId { get; set; } + + [JsonProperty("benchmark-title")] + public string BenchmarkTitle { get; set; } + + [JsonProperty("benchmark-version")] + public string BenchmarkVersion { get; set; } + + [JsonProperty("profile-id")] + public string ProfileId { get; set; } + + [JsonProperty("profile-title")] + public string ProfileTitle { get; set; } + + [JsonProperty("score")] + public string Score { get; set; } + + [JsonProperty("rules")] + public IEnumerable Rules { get; set; } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs new file mode 100644 index 000000000..8f2b107e6 --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatReportReader : LogReader + { + public override CisCatReport ReadLog(Stream input) + { + string reportData; + + using (TextReader streamReader = new StreamReader(input)) + { + reportData = streamReader.ReadToEnd(); + } + + return JsonConvert.DeserializeObject(reportData); + } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs new file mode 100644 index 000000000..b4d7f1bfc --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatRule + { + [JsonProperty("rule-id")] + public string RuleId { get; set; } + + [JsonProperty("rule-title")] + public string RuleTitle { get; set; } + + [JsonProperty("result")] + public string Result { get; set; } + } +} diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index 562e1cd9b..a44694fa8 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -12,6 +12,9 @@ public static class ToolFormat /// Android Studio's file format. public const string AndroidStudio = nameof(AndroidStudio); + /// CIS CAT file format. + public const string CisCat = nameof(CisCat); + /// Clang analyzer's file format. public const string ClangAnalyzer = nameof(ClangAnalyzer); diff --git a/src/Sarif.Multitool.Library/ConvertOptions.cs b/src/Sarif.Multitool.Library/ConvertOptions.cs index cbc869926..ce9450df5 100644 --- a/src/Sarif.Multitool.Library/ConvertOptions.cs +++ b/src/Sarif.Multitool.Library/ConvertOptions.cs @@ -13,7 +13,7 @@ public class ConvertOptions : SingleFileOptionsBase [Option( 't', "tool", - HelpText = "The tool format of the input file. Must be one of: AndroidStudio, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", + HelpText = "The tool format of the input file. Must be one of: AndroidStudio, CisCat, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", Required = true)] public string ToolFormat { get; set; } diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index 3e8ca9434..00946de47 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif @@ -185,5 +186,15 @@ public static string ComputeMD5Hash(string fileName) catch (UnauthorizedAccessException) { } return md5; } + + + public static string ComputeSha256HashValue(string value) + { + using (var sha = SHA256.Create()) + { + byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(value)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } } } From e366b48a8b309766fe0a2d1e5ad69d0bfc0d942e Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 9 Aug 2022 10:08:50 -0500 Subject: [PATCH 09/13] bug: exclude passing tests from results for match forward baseline status --- src/Sarif.Converters/CisCatConverter.cs | 41 +++++-------------- .../CisCatObjectModel/CisCatRule.cs | 5 +++ 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index 2744dbeda..a0fccab5f 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -45,7 +45,10 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm var results = new List(); foreach (CisCatRule rule in log.Rules) { - results.Add(CreateResult(rule)); + if (!rule.IsPass()) + { + results.Add(CreateResult(rule)); + } } PersistResults(output, results, run); @@ -109,14 +112,16 @@ internal Result CreateResult(CisCatRule rule) //Result: "info|notchecked|pass|unknown": Level = None, Kind = Informational|NotApplicable|Pass|Review switch (rule.Result) { + //PASS CASES ARE NOT INCLUDED IN THE RESULTS, AS MATCH FORWARD DOES NOT PRODUCE + //THE CORRECT ABSENT / NEW STATES WHEN THEY EXIST + // case "pass": + // result.Level = FailureLevel.None; + // result.Kind = ResultKind.Pass; + // break; case "fail": result.Level = FailureLevel.Error; result.Kind = ResultKind.Fail; break; - case "pass": - result.Level = FailureLevel.None; - result.Kind = ResultKind.Pass; - break; case "notchecked": result.Level = FailureLevel.None; result.Kind = ResultKind.NotApplicable; @@ -136,32 +141,6 @@ internal Result CreateResult(CisCatRule rule) result.Fingerprints = new Dictionary(); result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); - // var region = new Region - // { - // StartColumn = int.Parse(defect.Column), - // StartLine = int.Parse(defect.Line) - // }; - - // var fileUri = new Uri($"{defect.FilePath}", UriKind.RelativeOrAbsolute); - // var physicalLocation = new PhysicalLocation - // { - // ArtifactLocation = new ArtifactLocation - // { - // Uri = fileUri - // }, - // Region = region - // }; - - // var location = new Location - // { - // PhysicalLocation = physicalLocation - // }; - - // result.Locations = new List - // { - // location - // }; - return result; } } diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs index b4d7f1bfc..43e340708 100644 --- a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs @@ -15,5 +15,10 @@ public class CisCatRule [JsonProperty("result")] public string Result { get; set; } + + public bool IsPass() + { + return this.Result == "pass"; + } } } From 3f84ae41f3a7df13403adfa680048299c72a9a73 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 11 Aug 2022 15:57:58 -0500 Subject: [PATCH 10/13] feat: nessus converter v0 --- docs/multitool-usage.md | 1 + .../BuiltInConverterFactory.cs | 1 + src/Sarif.Converters/NessusConverter.cs | 203 ++++++++++++++++++ .../NessusObjectModel/FamilyItem.cs | 13 ++ .../NessusObjectModel/FamilySelection.cs | 15 ++ .../NessusObjectModel/HostProperties.cs | 15 ++ .../NessusObjectModel/HostTag.cs | 16 ++ .../IndividualPluginSelection.cs | 14 ++ .../NessusObjectModel/Item.cs | 31 +++ .../NessusObjectModel/NessusClientData.cs | 17 ++ .../NessusObjectModel/PluginItem.cs | 22 ++ .../NessusObjectModel/PluginsPreferences.cs | 14 ++ .../NessusObjectModel/Policy.cs | 26 +++ .../NessusObjectModel/Preference.cs | 16 ++ .../NessusObjectModel/Preferences.cs | 17 ++ .../NessusObjectModel/Report.cs | 17 ++ .../NessusObjectModel/ReportHost.cs | 20 ++ .../NessusObjectModel/ReportItem.cs | 101 +++++++++ .../NessusObjectModel/ServerPreference.cs | 14 ++ src/Sarif.Converters/ToolFileConverterBase.cs | 6 + src/Sarif.Converters/ToolFormat.cs | 3 + src/Sarif.Multitool.Library/ConvertOptions.cs | 2 +- src/Sarif/HashUtilities.cs | 10 + src/Sarif/IResultLogWriter.cs | 8 + src/Sarif/Writers/ResultLogJsonWriter.cs | 6 + 25 files changed, 607 insertions(+), 1 deletion(-) create mode 100644 src/Sarif.Converters/NessusConverter.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/FamilyItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/FamilySelection.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/HostProperties.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/HostTag.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Item.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/NessusClientData.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/PluginItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Policy.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Preference.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Preferences.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Report.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ReportHost.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ReportItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ServerPreference.cs diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index 151f880c2..fbc0d7e7a 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -79,6 +79,7 @@ Run ```Sarif.Multitool convert --help``` for the current list. - Fortify - FortifyFpr - FxCop +- Nessus - PREfast - Pylint - SemmleQL diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index 8b1b1838d..86d19dcf0 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -34,6 +34,7 @@ private static Dictionary> CreateBuiltInConv CreateConverterRecord(result, ToolFormat.FxCop); CreateConverterRecord(result, ToolFormat.FlawFinder); CreateConverterRecord(result, ToolFormat.Hdf); + CreateConverterRecord(result, ToolFormat.Nessus); CreateConverterRecord(result, ToolFormat.PREfast); CreateConverterRecord(result, ToolFormat.Pylint); CreateConverterRecord(result, ToolFormat.SemmleQL); diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs new file mode 100644 index 000000000..3995daf34 --- /dev/null +++ b/src/Sarif.Converters/NessusConverter.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Serialization; + +using Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class NessusConverter : ToolFileConverterBase + { + public override string ToolName => ToolFormat.Nessus; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + //LogicalLocations.Clear(); + + XmlReaderSettings settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Ignore, + XmlResolver = null + }; + + //Parse nessus data + var serializer = new XmlSerializer(typeof(NessusClientData)); + + //List of runs (one for each nessus host scanned) + var log = new SarifLog(); + log.Runs = new List(); + + using (var reader = XmlReader.Create(input, settings)) + { + var nessusClientData = (NessusClientData)serializer.Deserialize(reader); + + foreach (ReportHost host in nessusClientData.Report.ReportHosts) + { + var run = new Run(); + + //Init objects + run.Tool = new Tool(); + run.Results = new List(); + + //Set driver details + run.Tool.Driver = CreateDriver(nessusClientData, host.Name); + + //Set the list of tool rules + foreach (ReportItem item in host.ReportItems) + { + //Add rule (plugin id) if not exits + if (!run.Tool.Driver.Rules.Any(i => i.Id == item.PluginId)) + { + run.Tool.Driver.Rules.Add(CreateReportDescriptor(item)); + } + + run.Results.Add(CreateResult(item, host.Name)); + } + + log.Runs.Add(run); + } + + PersistResults(output, log); + } + } + + private ToolComponent CreateDriver(NessusClientData data, string targetId) + { + var driver = new ToolComponent(); + driver.Rules = new List(); + + driver.Name = this.ToolName; + driver.FullName = data.Report.Name; + driver.Version = data.Policy.Preferences.ServerPreference.Preferences.FirstOrDefault(i => i.Name.Equals("sc_version"))?.Value; + driver.InformationUri = new Uri("https://static.tenable.com/documentation/nessus_v2_file_format.pdf"); + driver.SetProperty("targetId", targetId); + return driver; + } + + private ReportingDescriptor CreateReportDescriptor(ReportItem item) + { + ReportingDescriptor descriptor = new ReportingDescriptor(); + + descriptor.Id = item.PluginId; + descriptor.Name = item.PluginName; + descriptor.ShortDescription = new MultiformatMessageString() + { + Text = item.Synopsis, + Markdown = item.Synopsis, + }; + descriptor.FullDescription = new MultiformatMessageString() + { + Text = item.Description, + Markdown = item.Description, + }; + + if (!string.IsNullOrWhiteSpace(item.SeeAlso)) + { + descriptor.Help = new MultiformatMessageString() + { + Text = item.SeeAlso, + Markdown = item.SeeAlso, + }; + } + + descriptor.SetProperty("pluginFamily", item.PluginFamily); + descriptor.SetProperty("pluginModificationDate", item.PluginModificationDate); + descriptor.SetProperty("pluginPublicationDate", item.PluginPublicationDate); + descriptor.SetProperty("pluginType", item.PluginType); + + return descriptor; + } + + internal Result CreateResult(ReportItem item, string hostName) + { + //set the result metadata + Result result = new Result + { + RuleId = item.PluginId, + Message = new Message { Text = item.PluginOutput }, + }; + + //set misc properties + result.SetProperty("port", item.Port); + result.SetProperty("protocol", item.Protocol); + result.SetProperty("service", item.ServiceName); + + //set solution if present + if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) + result.SetProperty("solution", item.Solution); + + //set severity (rank) + //ignore risk factor (H/M/L) as it conflicts with rank + result.Kind = ResultKind.Fail; + result.Rank = double.Parse(item.Severity); + + //vulnerable packages contain cve / cvss data + if (!string.IsNullOrEmpty(item.Cvss3BaseScore)) + { + result.SetProperty("cvss3BaseScore", item.Cvss3BaseScore); + } + + if (!string.IsNullOrEmpty(item.Cvss3TemporalScore)) + { + result.SetProperty("cvss3TemporalScore", item.Cvss3TemporalScore); + } + + if (!string.IsNullOrEmpty(item.Cvss3Vector)) + { + result.SetProperty("cvss3Vector", item.Cvss3Vector); + } + + if (!string.IsNullOrEmpty(item.Cvss3TemporalVector)) + { + result.SetProperty("cvss3TemporalVector", item.Cvss3TemporalVector); + } + + if (!string.IsNullOrEmpty(item.VulnPublicationDate)) + { + result.SetProperty("vulnPublicationDate", item.VulnPublicationDate); + } + + if (!string.IsNullOrEmpty(item.PatchPublicationDate)) + { + result.SetProperty("patchPublicationDate", item.PatchPublicationDate); + } + + if (item.Cves.Any()) + { + result.SetProperty("cve", item.Cves); + } + + if (item.Xrefs.Any()) + { + result.SetProperty("xref", item.Xrefs); + } + + if (!string.IsNullOrEmpty(item.ExploitAvailable)) + { + result.SetProperty("exploitAvailable", bool.Parse(item.ExploitAvailable)); + } + + //Set the unique fingerprint per item + var fingerprints = new List() { + item.PluginId, + item.Port, + item.Protocol, + item.ServiceName, + }; + + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(string.Join(".", fingerprints)).ToLower()); + + return result; + } + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs b/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs new file mode 100644 index 000000000..79db92f53 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class FamilyItem + { + [XmlElement("FamilyName")] + public string Name { get; set; } = string.Empty; + + [XmlElement("Status")] + public string Status { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs b/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs new file mode 100644 index 000000000..0cfb18074 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class FamilySelection + { + [XmlElement("FamilyItem")] + public List FamilyItems { get; set; } = new List(); + + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/HostProperties.cs b/src/Sarif.Converters/NessusObjectModel/HostProperties.cs new file mode 100644 index 000000000..ba3efcb16 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/HostProperties.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class HostProperties + { + [XmlElement("tag")] + public List Tags { get; set; } = new List(); + + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/HostTag.cs b/src/Sarif.Converters/NessusObjectModel/HostTag.cs new file mode 100644 index 000000000..d78d41923 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/HostTag.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class HostTag + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlText] + public string Value { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs b/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs new file mode 100644 index 000000000..b1ff6558d --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class IndividualPluginSelection + { + [XmlElement("PluginItem")] + public List PluginItems { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Item.cs b/src/Sarif.Converters/NessusObjectModel/Item.cs new file mode 100644 index 000000000..16f18555e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Item.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Item + { + [XmlElement("fullName")] + public string FullName { get; set; } = string.Empty; + + [XmlElement("pluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlElement("pluginId")] + public string PluginId { get; set; } = string.Empty; + + [XmlElement("preferenceName")] + public string PreferenceName { get; set; } = string.Empty; + + [XmlElement("preferenceType")] + public string PreferenceType { get; set; } = string.Empty; + + [XmlElement("preferenceValues")] + public string PreferenceValues { get; set; } = string.Empty; + + [XmlElement("selectedValue")] + public string SelectedValue { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs b/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs new file mode 100644 index 000000000..7f0b0a96e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + [XmlRoot("NessusClientData_v2")] + public class NessusClientData + { + [XmlElement("Policy")] + public Policy Policy { get; set; } = new Policy(); + + [XmlElement("Report")] + public Report Report { get; set; } = new Report(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/PluginItem.cs b/src/Sarif.Converters/NessusObjectModel/PluginItem.cs new file mode 100644 index 000000000..1a0df86fa --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/PluginItem.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class PluginItem + { + [XmlElement("PluginId")] + public string PluginId { get; set; } = string.Empty; + + [XmlElement("PluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlElement("Family")] + public string Family { get; set; } = string.Empty; + + [XmlElement("Status")] + public string Status { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs b/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs new file mode 100644 index 000000000..ba4fdccc4 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class PluginsPreferences + { + [XmlElement("item")] + public List Items { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Policy.cs b/src/Sarif.Converters/NessusObjectModel/Policy.cs new file mode 100644 index 000000000..acf6bffd9 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Policy.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Policy + { + [XmlElement("policyName")] + public string PolicyName { get; set; } = string.Empty; + + [XmlElement("policyComments")] + public string PolicyComments { get; set; } = string.Empty; + + [XmlElement("Preferences")] + public Preferences Preferences { get; set; } = new Preferences(); + + [XmlElement("FamilySelection")] + public FamilySelection FamilySelection { get; set; } = new FamilySelection(); + + [XmlElement("IndividualPluginSelection")] + public IndividualPluginSelection IndividualPluginSelection { get; set; } = new IndividualPluginSelection(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Preference.cs b/src/Sarif.Converters/NessusObjectModel/Preference.cs new file mode 100644 index 000000000..0694c9553 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Preference.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Preference + { + [XmlElement("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("value")] + public string Value { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Preferences.cs b/src/Sarif.Converters/NessusObjectModel/Preferences.cs new file mode 100644 index 000000000..4efead73c --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Preferences.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Preferences + { + [XmlElement("ServerPreferences")] + public ServerPreference ServerPreference { get; set; } = new ServerPreference(); + + [XmlElement("PluginsPreferences")] + public PluginsPreferences PluginsPreferences { get; set; } = new PluginsPreferences(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Report.cs b/src/Sarif.Converters/NessusObjectModel/Report.cs new file mode 100644 index 000000000..923ba605e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Report.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Report + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("ReportHost")] + public List ReportHosts { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Sarif.Converters/NessusObjectModel/ReportHost.cs b/src/Sarif.Converters/NessusObjectModel/ReportHost.cs new file mode 100644 index 000000000..b93653323 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ReportHost.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ReportHost + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("HostProperties")] + public HostProperties HostProperties { get; set; } = new HostProperties(); + + [XmlElement("ReportItem")] + public List ReportItems { get; set; } + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/ReportItem.cs b/src/Sarif.Converters/NessusObjectModel/ReportItem.cs new file mode 100644 index 000000000..017267fc2 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ReportItem.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ReportItem + { + [XmlAttribute("severity")] + public string Severity { get; set; } = string.Empty; + + [XmlAttribute("port")] + public string Port { get; set; } = string.Empty; + + [XmlAttribute("pluginFamily")] + public string PluginFamily { get; set; } = string.Empty; + + [XmlAttribute("pluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlAttribute("pluginID")] + public string PluginId { get; set; } = string.Empty; + + [XmlAttribute("protocol")] + public string Protocol { get; set; } = string.Empty; + + [XmlAttribute("svc_name")] + public string ServiceName { get; set; } = string.Empty; + + [XmlElement("plugin_modification_date")] + public string PluginModificationDate { get; set; } = string.Empty; + + [XmlElement("plugin_publication_date")] + public string PluginPublicationDate { get; set; } = string.Empty; + + [XmlElement("plugin_type")] + public string PluginType { get; set; } = string.Empty; + + [XmlElement("solution")] + public string Solution { get; set; } = string.Empty; + + [XmlElement("description")] + public string Description { get; set; } = string.Empty; + + [XmlElement("synopsis")] + public string Synopsis { get; set; } = string.Empty; + + [XmlElement("risk_factor")] + public string RiskFactor { get; set; } = string.Empty; + + [XmlElement("script_version")] + public string ScriptVersion { get; set; } = string.Empty; + + [XmlElement("plugin_output")] + public string PluginOutput { get; set; } = string.Empty; + + [XmlElement("see_also")] + public string SeeAlso { get; set; } = string.Empty; + + [XmlElement("cvss_base_score")] + public string CvssBaseScore { get; set; } = string.Empty; + + [XmlElement("cvss_temporal_score")] + public string CvssTemporalScore { get; set; } = string.Empty; + + [XmlElement("cvss3_base_score")] + public string Cvss3BaseScore { get; set; } = string.Empty; + + [XmlElement("cvss3_temporal_score")] + public string Cvss3TemporalScore { get; set; } = string.Empty; + + [XmlElement("exploit_available")] + public string ExploitAvailable { get; set; } = string.Empty; + + [XmlElement("patch_publication_date")] + public string PatchPublicationDate { get; set; } = string.Empty; + + [XmlElement("vuln_publication_date")] + public string VulnPublicationDate { get; set; } = string.Empty; + + [XmlElement("cvss3_temporal_vector")] + public string Cvss3TemporalVector { get; set; } = string.Empty; + + [XmlElement("cvss3_vector")] + public string Cvss3Vector { get; set; } = string.Empty; + + [XmlElement("cvss_temporal_vector")] + public string CvssTemporalVector { get; set; } = string.Empty; + + [XmlElement("cvss_vector")] + public string CvssVector { get; set; } = string.Empty; + + [XmlElement("cve")] + public List Cves { get; set; } = new List(); + + [XmlElement("xref")] + public List Xrefs { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs b/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs new file mode 100644 index 000000000..455c2b1b1 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ServerPreference + { + [XmlElement("preference")] + public List Preferences { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/ToolFileConverterBase.cs b/src/Sarif.Converters/ToolFileConverterBase.cs index e83cabfb4..4fc2020c6 100644 --- a/src/Sarif.Converters/ToolFileConverterBase.cs +++ b/src/Sarif.Converters/ToolFileConverterBase.cs @@ -105,5 +105,11 @@ protected static Run PersistResults(IResultLogWriter output, IList resul return run; } + + protected static void PersistResults(IResultLogWriter output, SarifLog log) + { + output.WriteLog(log); + return; + } } } diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index 562e1cd9b..c8259c7f4 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -39,6 +39,9 @@ public static class ToolFormat /// Heimdall Tools's file format. public const string Hdf = nameof(Hdf); + /// Tenable Nessus XML file format. + public const string Nessus = nameof(Nessus); + /// PREfast's file format. public const string PREfast = nameof(PREfast); diff --git a/src/Sarif.Multitool.Library/ConvertOptions.cs b/src/Sarif.Multitool.Library/ConvertOptions.cs index cbc869926..05cc6521e 100644 --- a/src/Sarif.Multitool.Library/ConvertOptions.cs +++ b/src/Sarif.Multitool.Library/ConvertOptions.cs @@ -13,7 +13,7 @@ public class ConvertOptions : SingleFileOptionsBase [Option( 't', "tool", - HelpText = "The tool format of the input file. Must be one of: AndroidStudio, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", + HelpText = "The tool format of the input file. Must be one of: AndroidStudio, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, Nessus, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", Required = true)] public string ToolFormat { get; set; } diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index 3e8ca9434..a46458943 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif @@ -185,5 +186,14 @@ public static string ComputeMD5Hash(string fileName) catch (UnauthorizedAccessException) { } return md5; } + + public static string ComputeSha256HashValue(string value) + { + using (var sha = SHA256.Create()) + { + byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(value)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } } } diff --git a/src/Sarif/IResultLogWriter.cs b/src/Sarif/IResultLogWriter.cs index b6b8736b7..00f1c460a 100644 --- a/src/Sarif/IResultLogWriter.cs +++ b/src/Sarif/IResultLogWriter.cs @@ -116,5 +116,13 @@ public interface IResultLogWriter /// The invocations to write. /// void WriteInvocations(IEnumerable invocations); + + /// + /// Write a full SARIF log object to the output stream. + /// + /// + /// The SarifLog to write. + /// + void WriteLog(SarifLog log); } } diff --git a/src/Sarif/Writers/ResultLogJsonWriter.cs b/src/Sarif/Writers/ResultLogJsonWriter.cs index 9e26fdac9..cbcefcab0 100644 --- a/src/Sarif/Writers/ResultLogJsonWriter.cs +++ b/src/Sarif/Writers/ResultLogJsonWriter.cs @@ -427,5 +427,11 @@ private void EnsureResultsArrayIsNotOpen() throw new InvalidOperationException(SdkResources.ResultsSerializationNotComplete); } } + + public void WriteLog(SarifLog log) + { + _serializer.Serialize(_jsonWriter, log); + _writeConditions |= Conditions.RunCompleted; + } } } From bec3834bda5266ec6377417d6ec0c726ca242310 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 12 Aug 2022 09:23:37 -0500 Subject: [PATCH 11/13] bug: fingerprint targetid + empty plugin output --- src/Sarif.Converters/NessusConverter.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs index 3995daf34..3f77e793f 100644 --- a/src/Sarif.Converters/NessusConverter.cs +++ b/src/Sarif.Converters/NessusConverter.cs @@ -123,13 +123,17 @@ internal Result CreateResult(ReportItem item, string hostName) Result result = new Result { RuleId = item.PluginId, - Message = new Message { Text = item.PluginOutput }, + Message = new Message + { + Text = string.IsNullOrWhiteSpace(item.PluginOutput.Trim()) ? item.Synopsis.Trim() : item.PluginOutput.Trim(), + }, }; //set misc properties result.SetProperty("port", item.Port); result.SetProperty("protocol", item.Protocol); result.SetProperty("service", item.ServiceName); + result.SetProperty("targetId", hostName); //set solution if present if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) @@ -188,6 +192,7 @@ internal Result CreateResult(ReportItem item, string hostName) //Set the unique fingerprint per item var fingerprints = new List() { + hostName, item.PluginId, item.Port, item.Protocol, From 2a250aaecb460f2d7e5928b2e15c54cccc33e6ba Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 15 Aug 2022 09:14:48 -0500 Subject: [PATCH 12/13] feat: cis cat unit tests --- src/Sarif/HashUtilities.cs | 1 - .../CisCatConverterTests.cs | 62 +++++++++++++++++++ .../Test.UnitTests.Sarif.Converters.csproj | 12 +++- .../ExpectedOutputs/NoResults.sarif | 25 ++++++++ .../ExpectedOutputs/ValidResults.sarif | 62 +++++++++++++++++++ .../Inputs/InvalidResults.json | 8 +++ .../CisCatConverter/Inputs/NoResults.json | 9 +++ .../CisCatConverter/Inputs/ValidResults.json | 20 ++++++ .../ResultLogObjectWriter.cs | 5 ++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index f0f7ed200..a46458943 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -8,7 +8,6 @@ using System.IO; using System.Security.Cryptography; using System.Text; -using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif diff --git a/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs new file mode 100644 index 000000000..3b33b9687 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; +using FluentAssertions; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class CisCatConverterTests : ConverterTestsBase + { + private static readonly string NoOutputExpected = string.Empty; + + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new CisCatConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new CisCatConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenResultRowIsInvalid_ThrowsExpectedException() + { + string input = Extractor.GetResourceInputText("InvalidResults.json"); + Action action = () => RunTestCase(input, NoOutputExpected); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(CisCatConverterTests)); + private const string ResourceNamePrefix = ToolFormat.CisCat; + } +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index 4a994089d..e078fad95 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -46,6 +46,11 @@ + + + + + @@ -69,6 +74,11 @@ + + + + + @@ -94,4 +104,4 @@ - + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif new file mode 100644 index 000000000..10a400280 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif @@ -0,0 +1,25 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [], + "tool": { + "driver": { + "name": "CisCat", + "fullName": "CIS Benchmark Title", + "version": "2.1.0", + "semanticVersion": "2.1.0", + "informationUri": "https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre", + "properties": { + "benchmarkId": "cis.benchmark.id", + "profileId": "profile.id", + "profileTitle": "profile.title", + "score": "0.0" + } + } + }, + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif new file mode 100644 index 000000000..175458bcb --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif @@ -0,0 +1,62 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "rule.id.2", + "level": "error", + "message": { + "text": "Rule id 2 title" + }, + "fingerprints": { + "0": "d23eea429d06cfe703ab69b8d0d0c1abd41fc9e26b4167575e3356733c97bf61" + } + } + ], + "tool": { + "driver": { + "name": "CisCat", + "fullName": "CIS Benchmark Title", + "version": "2.1.0", + "semanticVersion": "2.1.0", + "informationUri": "https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre", + "rules": [ + { + "id": "rule.id.1", + "name": "Rule id 1 title", + "fullDescription": { + "text": "Rule id 1 title", + "markdown": "Rule id 1 title" + }, + "help": { + "text": "Rule id 1 title", + "markdown": "Rule id 1 title" + } + }, + { + "id": "rule.id.2", + "name": "Rule id 2 title", + "fullDescription": { + "text": "Rule id 2 title", + "markdown": "Rule id 2 title" + }, + "help": { + "text": "Rule id 2 title", + "markdown": "Rule id 2 title" + } + } + ], + "properties": { + "benchmarkId": "cis.benchmark.id", + "profileId": "profile.id", + "profileTitle": "profile.title", + "score": "50.00" + } + } + }, + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json new file mode 100644 index 000000000..645453a3f --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json @@ -0,0 +1,8 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "ProfileId", + "profile-title": "Title", + "score": "0.0" +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json new file mode 100644 index 000000000..e15b964ec --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json @@ -0,0 +1,9 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "profile.id", + "profile-title": "profile.title", + "score": "0.0", + "rules": [] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json new file mode 100644 index 000000000..c4a3bc64d --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json @@ -0,0 +1,20 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "profile.id", + "profile-title": "profile.title", + "score": "50.00", + "rules": [ + { + "rule-id": "rule.id.1", + "rule-title": "Rule id 1 title", + "result": "pass" + }, + { + "rule-id": "rule.id.2", + "rule-title": "Rule id 2 title", + "result": "fail" + } + ] +} \ No newline at end of file diff --git a/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs b/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs index 27bb3a542..21cc4e808 100644 --- a/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs +++ b/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs @@ -65,5 +65,10 @@ public void WriteRules(IList rules) { throw new NotImplementedException(); } + + public void WriteLog(SarifLog log) + { + + } } } From 2304ccd6ac8808c70a0b1ca2227178b5a8dccbca Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 15 Aug 2022 14:31:59 -0500 Subject: [PATCH 13/13] feat: nessus unit tests --- .../NessusConverterTests.cs | 60 +++++++++ .../Test.UnitTests.Sarif.Converters.csproj | 12 ++ .../ExpectedOutputs/InvalidResults.sarif | 5 + .../ExpectedOutputs/NoResults.sarif | 34 +++++ .../ExpectedOutputs/ValidResults.sarif | 119 ++++++++++++++++++ .../Inputs/InvalidResults.nessus.xml | 9 ++ .../Inputs/NoResults.nessus.xml | 58 +++++++++ .../Inputs/ValidResults.nessus.xml | 86 +++++++++++++ 8 files changed, 383 insertions(+) create mode 100644 src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml diff --git a/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs new file mode 100644 index 000000000..c7eb093f2 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; +using FluentAssertions; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class NessusConverterTests : ConverterTestsBase + { + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new NessusConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new NessusConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenResultRowIsInvalid_ReturnsEmptyResults() + { + string input = Extractor.GetResourceInputText("InvalidResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("InvalidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(NessusConverterTests)); + private const string ResourceNamePrefix = ToolFormat.Nessus; + } +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index e078fad95..cc9389ec9 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -51,6 +51,12 @@ + + + + + + @@ -79,6 +85,12 @@ + + + + + + diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif new file mode 100644 index 000000000..787ce0d80 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif @@ -0,0 +1,5 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif new file mode 100644 index 000000000..9d9dfaa1e --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif @@ -0,0 +1,34 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "properties": { + "targetId": "10.0.0.1" + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "properties": { + "targetId": "10.0.0.2" + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif new file mode 100644 index 000000000..86987d502 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif @@ -0,0 +1,119 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "rules": [ + { + "id": "12345", + "name": "PluginName1", + "fullDescription": { + "text": "Description text", + "markdown": "Description text" + }, + "help": { + "text": "https://github.com/microsoft/sarif-sdk", + "markdown": "https://github.com/microsoft/sarif-sdk" + }, + "shortDescription": { + "text": "Synopsis test", + "markdown": "Synopsis test" + }, + "properties": { + "pluginFamily": "Family Name", + "pluginModificationDate": "2022/08/15", + "pluginPublicationDate": "2021/08/15", + "pluginType": "remote" + } + } + ], + "properties": { + "targetId": "10.0.0.1" + } + } + }, + "results": [ + { + "ruleId": "12345", + "message": { + "text": "plugin output text" + }, + "fingerprints": { + "0": "1e6bb36ff29ed6c40f06c82791515391820f7b546e700886adbd88b2c897279c" + }, + "rank": 1.0, + "properties": { + "port": "99999", + "protocol": "tcp", + "service": "unknown", + "targetId": "10.0.0.1" + } + } + ], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "rules": [ + { + "id": "12345", + "name": "CVE Scan", + "fullDescription": { + "text": "description text", + "markdown": "description text" + }, + "help": { + "text": "https://github.com/microsoft/sarif-sdk", + "markdown": "https://github.com/microsoft/sarif-sdk" + }, + "shortDescription": { + "text": "Synopis text", + "markdown": "Synopis text" + }, + "properties": { + "pluginFamily": "Family Name", + "pluginModificationDate": "2022/08/15", + "pluginPublicationDate": "2021/08/15", + "pluginType": "remote" + } + } + ], + "properties": { + "targetId": "10.0.0.2" + } + } + }, + "results": [ + { + "ruleId": "12345", + "message": { + "text": "plugin output" + }, + "fingerprints": { + "0": "c630cd9cdc8784d7ecdbbbe2ae5b6e0e30609841d2df1cef336b5e646391d876" + }, + "rank": 2.0, + "properties": { + "port": "0", + "protocol": "tcp", + "service": "unknown", + "targetId": "10.0.0.2", + "solution": "Solution text or generate a proper SSL certificate for this service.", + "cvss3BaseScore": "9.0", + "cvss3Vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml new file mode 100644 index 000000000..ba9be8e83 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml @@ -0,0 +1,9 @@ + + + + InvalidNessusConverterTest + + + + + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml new file mode 100644 index 000000000..2d18ca8ef --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml @@ -0,0 +1,58 @@ + + + + NessusConverterTest + policy comments text + + + + TARGET + 10.0.0.1,10.0.0.2 + + + preference1 + value1 + + + + + PluginName1 + 12345 + Plugin Full Name 12345 + Plugin Name 12345 Short Hame + radio + None + None + + + + + + Family Name + enabled + + + + + 12345 + Selected Plugin 1 + Family Name + enabled + + + + + + + value1 + 10.0.0.1 + + + + + value1 + 10.0.0.1 + + + + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml new file mode 100644 index 000000000..0f67ecbac --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml @@ -0,0 +1,86 @@ + + + + NessusConverterTest + policy comments text + + + + TARGET + 10.0.0.1,10.0.0.2 + + + preference1 + value1 + + + + + PluginName1 + 12345 + Plugin Full Name 12345 + Plugin Name 12345 Short Hame + radio + None + None + + + + + + Family Name + enabled + + + + + 12345 + Selected Plugin 1 + Family Name + enabled + + + + + + + value1 + 10.0.0.1 + + + 2022/08/15 + 2021/08/15 + remote + n/a + Description text + Synopsis test + https://github.com/microsoft/sarif-sdk + None + 1.0 + plugin output text + + + + + value1 + 10.0.0.1 + + + 9.0 + 9.0 + 2022/08/15 + 2021/08/15 + remote + Solution text or generate a proper SSL certificate for this service. + AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N + AV:N/AC:L/AU:N/C:P/I:P/A:N + description text + Synopis text + https://github.com/microsoft/sarif-sdk + High + 1.0 + plugin output + + + + \ No newline at end of file