diff --git a/.editorconfig b/.editorconfig index 7f6cb2ac..48c5b234 100644 --- a/.editorconfig +++ b/.editorconfig @@ -811,6 +811,10 @@ dotnet_diagnostic.CA1852.severity = suggestion # CA1854: Prefer 'TryGetValue' over 'ContainsKey' and 'Item' when accessing dictionary items dotnet_diagnostic.CA1854.severity = suggestion +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1859 +# CA1859: Use culture-aware string operations +dotnet_diagnostic.CA1859.severity = suggestion + # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1860 # CA1860: Avoid using 'Enumerable.Any()' extension method. dotnet_diagnostic.CA1860.severity = suggestion @@ -819,18 +823,6 @@ dotnet_diagnostic.CA1860.severity = suggestion # CA1861: Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly dotnet_diagnostic.CA1861.severity = suggestion -# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 -# CA1862: Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison -dotnet_diagnostic.CA1862.severity = suggestion - -# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865 -# CA1865: Use 'string.StartsWith(char)' instead of 'string.StartsWith(string)' when you have a string with a single char -dotnet_diagnostic.CA1865.severity = suggestion - -# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 -# CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. -dotnet_diagnostic.CA1869.severity = suggestion - # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000 # CA2000: Dispose objects before losing scope dotnet_diagnostic.CA2000.severity = suggestion @@ -1043,14 +1035,6 @@ dotnet_diagnostic.IDE0220.severity = suggestion # IDE0251: Member can be made 'readonly' dotnet_diagnostic.IDE0251.severity = suggestion -# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0290 -# IDE0290: Use primary constructor -dotnet_diagnostic.IDE0290.severity = suggestion - -# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0300 -# IDE0290: Collection initialization can be simplified -dotnet_diagnostic.IDE0300.severity = suggestion - # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide1006 # IDE1006: Naming rule violation: These words must begin with upper case characters: ... dotnet_diagnostic.IDE1006.severity = suggestion diff --git a/Directory.Build.props b/Directory.Build.props index 5c17dd66..64e721e3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,6 @@ AllEnabledByDefault true $(NoWarn);CA1014;CS8002 - true diff --git a/docs/sbom-tool-api-reference.md b/docs/sbom-tool-api-reference.md index 880753aa..998a7c97 100644 --- a/docs/sbom-tool-api-reference.md +++ b/docs/sbom-tool-api-reference.md @@ -262,3 +262,81 @@ var result = await generator.GenerateSBOMAsync(rootPath: scanPath, * The `packages` parameter contains a list of `SBOMPackage` objects. * The `metadata` and `runtimeConfiguration` parameters accept the [`SBOMMetadata`](#sbommetadata) and [`RuntimeConfiguration`](#runtimeconfiguration) objects (respectively). * If users want the API to generate the output SBOM in a different folder other the default location, they need to provide the path in the `manifestDirPath` parameter. Users will find the SBOM file under the `_manifest` directory at the user-specified path. + +## SBOM Validation + +Now that you have generated the SBOM file, you can validate it using the `SBOMValidator` class. Setup for this will be very similar to the `SBOMGenerator` class. Here is an example: + +```C# +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Sbom.Extensions.DependencyInjection; + +namespace SBOMApiExample; +class Program +{ + public static async Task Main(string[] args) + { + await Host.CreateDefaultBuilder(args) + .ConfigureServices((host, services) => + { + services + .AddHostedService() + .AddSbomTool(); + }) + .RunConsoleAsync(x => x.SuppressStatusMessages = true); + } +} +``` + +After the Host is set up, you can inject the `ISBOMValidator` interface into your service and use it to validate the SBOM file. Here is an example: +Note that the only arguments required are the `buildDropPath`, the `outputPath`, and the `SbomSpecification`. The `buildDropPath` is the path to the directory containing the _manifest directory. The `outPath` is the path to the file where the validation output will be written. The only `SbomSpecification` currently supported is `SPDX 2.2`. +All other arguments are optional. + +```C# +using Microsoft.Extensions.Hosting; +using Microsoft.Sbom.Contracts; + +namespace SBOMApiExample +{ + public class ValidationService : IHostedService + { + private readonly ISBOMValidator sbomValidator; + private readonly IHostApplicationLifetime hostApplicationLifetime; + + public ValidationService( + ISBOMValidator sbomValidator, + IHostApplicationLifetime hostApplicationLifetime) + { + this.sbomValidator = sbomValidator; + this.hostApplicationLifetime = hostApplicationLifetime; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + string buildDropPath = "C:/repos/samplePath"; + string outputPath = "C:/temp/ValidationOutput.json"; + IList spdx22Specification = new List() + { + new SbomSpecification("SPDX","2.2") + }; + + RuntimeConfiguration configuration = new RuntimeConfiguration() + { + Verbosity = System.Diagnostics.Tracing.EventLevel.Information, + }; + + var result = await sbomValidator.ValidateSbomAsync(buildDropPath, outputPath, spdx22Specification, runtimeConfiguration: configuration); + + + hostApplicationLifetime.StopApplication(); + } + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + } +} + +``` diff --git a/global.json b/global.json index 42fe8224..95bdf7f0 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,7 @@ { "sdk": { - "version": "8.0.100-rc.1.23463.5", - "rollForward": "latestMajor" + "version": "7.0.400", + "rollForward": "latestMajor", + "allowPrerelease": false } } diff --git a/src/Microsoft.Sbom.Api/SBOMValidator.cs b/src/Microsoft.Sbom.Api/SBOMValidator.cs index 29f8f215..7184e219 100644 --- a/src/Microsoft.Sbom.Api/SBOMValidator.cs +++ b/src/Microsoft.Sbom.Api/SBOMValidator.cs @@ -1,11 +1,20 @@ -// Copyright (c) Microsoft. All rights reserved. +// 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.ComponentModel; using System.Linq; using System.Threading.Tasks; +using Microsoft.Sbom.Api.Config; +using Microsoft.Sbom.Api.Config.Extensions; using Microsoft.Sbom.Api.Output.Telemetry; using Microsoft.Sbom.Api.Workflows; +using Microsoft.Sbom.Common.Config; +using Microsoft.Sbom.Common.Config.Validators; +using Microsoft.Sbom.Contracts; +using Microsoft.Sbom.Contracts.Enums; +using PowerArgs; namespace Microsoft.Sbom.Api; @@ -13,13 +22,17 @@ public class SbomValidator : ISBOMValidator { private readonly IWorkflow sbomParserBasedValidationWorkflow; private readonly IRecorder recorder; + private readonly IEnumerable configValidators; public SbomValidator( IWorkflow sbomParserBasedValidationWorkflow, - IRecorder recorder) + IRecorder recorder, + IEnumerable configValidators, + ConfigSanitizer configSanitizer) { this.sbomParserBasedValidationWorkflow = sbomParserBasedValidationWorkflow ?? throw new ArgumentNullException(nameof(sbomParserBasedValidationWorkflow)); this.recorder = recorder ?? throw new ArgumentNullException(nameof(recorder)); + this.configValidators = configValidators; } public async Task ValidateSbomAsync() @@ -31,4 +44,58 @@ public async Task ValidateSbomAsync() return isSuccess; } + + public async Task ValidateSbomAsync( + string buildDropPath, + string outputPath, + IList specifications, + string manifestDirPath = null, + bool validateSignature = false, + bool ignoreMissing = false, + string rootPathFilter = null, + RuntimeConfiguration runtimeConfiguration = null, + AlgorithmName algorithmName = null) + { + // If the API user does not specify a manifest directory path, we will default to the build drop path. + if (string.IsNullOrWhiteSpace(manifestDirPath)) + { + manifestDirPath = $"{buildDropPath}\\_manifest"; + } + + var inputConfig = ApiConfigurationBuilder.GetConfiguration( + buildDropPath, + outputPath, + specifications, + algorithmName, + manifestDirPath, + validateSignature, + ignoreMissing, + rootPathFilter, + runtimeConfiguration); + + inputConfig = ValidateConfig(inputConfig); + + inputConfig.ToConfiguration(); + + var isSuccess = await sbomParserBasedValidationWorkflow.RunAsync(); + await recorder.FinalizeAndLogTelemetryAsync(); + + var entityErrors = recorder.Errors.Select(error => error.ToEntityError()).ToList(); + + return isSuccess; + } + + private InputConfiguration ValidateConfig(InputConfiguration config) + { + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(config)) + { + configValidators.ForEach(v => + { + v.CurrentAction = config.ManifestToolAction; + v.Validate(property.DisplayName, property.GetValue(config), property.Attributes); + }); + } + + return config; + } } diff --git a/src/Microsoft.Sbom.Contracts/ISBOMValidator.cs b/src/Microsoft.Sbom.Contracts/ISBOMValidator.cs index 028cee77..8130054d 100644 --- a/src/Microsoft.Sbom.Contracts/ISBOMValidator.cs +++ b/src/Microsoft.Sbom.Contracts/ISBOMValidator.cs @@ -1,9 +1,11 @@ -// Copyright (c) Microsoft. All rights reserved. +// 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.Threading.Tasks; +using Microsoft.Sbom.Contracts.Enums; -namespace Microsoft.Sbom; +namespace Microsoft.Sbom.Contracts; /// /// Provides an interface to validate a SBOM. @@ -15,4 +17,27 @@ public interface ISBOMValidator /// and writes JSON output to the outputPath file location. /// Task ValidateSbomAsync(); + + /// + /// Validates all the files in a given SBOM with the files present in the build drop path + /// and writes JSON output to the outputPath file location. + /// The path to the root of the drop." + /// The path to a writeable location where the output json should be written. + /// The list of specifications to use for validation. + /// The path to the directory that contains the _manifest folder. If null then buildDropPath will be used + /// If true, validate the signature of the SBOM. + /// The root path filter to use for validation. + /// The runtime configuration to use for validation. + /// The algorithm to use for hashing. + /// + Task ValidateSbomAsync( + string buildDropPath, + string outputPath, + IList specifications, + string manifestDirPath = null, + bool validateSignature = false, + bool ignoreMissing = false, + string rootPathFilter = null, + RuntimeConfiguration runtimeConfiguration = null, + AlgorithmName algorithmName = null); } diff --git a/src/Microsoft.Sbom.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/Microsoft.Sbom.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index 348b67c4..21a33dc5 100644 --- a/src/Microsoft.Sbom.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Microsoft.Sbom.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -170,6 +170,7 @@ public static IServiceCollection AddSbomTool(this IServiceCollection services, L typeof(IManifestInterface))) .AsImplementedInterfaces()) .AddScoped() + .AddScoped() .AddSingleton(x => { var fileSystemUtils = x.GetRequiredService();