Skip to content

Commit

Permalink
Add validate-format verb with placeholder for future validation (#577)
Browse files Browse the repository at this point in the history
* Wire up a validate-format verb that runs the format validation logic for a specified file. This is a hidden verb that I imagine being used during development, and in future by DRI to explore an SBOM or troubleshoot a validation failure.

* Fix PR comment - removing duplicated parameter validation code.
  • Loading branch information
alisonlomaka authored May 20, 2024
1 parent 29bc8fb commit 1d832e0
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 1 deletion.
23 changes: 23 additions & 0 deletions src/Microsoft.Sbom.Api/Config/Args/FormatValidationArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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 Microsoft.Sbom.Api.Utils;
using Microsoft.Sbom.Extensions.Entities;
using PowerArgs;

namespace Microsoft.Sbom.Api.Config.Args;

/// <summary>
/// The command line arguments provided for the validate action in ManifestTool.
/// </summary>
public class FormatValidationArgs : CommonArgs
{
/// <summary>
/// Gets or sets the file path of the SBOM to validate.
/// </summary>
[ArgShortcut("sp")]
[ArgDescription("The file path of the SBOM to validate.")]
public string? SbomPath { get; set; }
}
15 changes: 15 additions & 0 deletions src/Microsoft.Sbom.Api/Config/ConfigSanitizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public IConfiguration SanitizeConfig(IConfiguration configuration)
configuration.BuildDropPath = GetTempBuildDropPath(configuration);
}

CheckValidateFormatConfig(configuration);

configuration.HashAlgorithm = GetHashAlgorithmName(configuration);

// set ManifestDirPath after validation of DirectoryExist and DirectoryPathIsWritable, this wouldn't exist because it needs to be created by the tool.
Expand All @@ -87,6 +89,19 @@ public IConfiguration SanitizeConfig(IConfiguration configuration)
return configuration;
}

private void CheckValidateFormatConfig(IConfiguration config)
{
if (config.ManifestToolAction != ManifestToolActions.ValidateFormat)
{
return;
}

if (config.SbomPath?.Value == null)
{
throw new ValidationArgException($"Please provide a value for the SbomPath (-sp) parameter to validate the SBOM.");
}
}

private ConfigurationSetting<IList<ManifestInfo>> GetDefaultManifestInfoForValidationAction(IConfiguration configuration)
{
if (configuration.ManifestToolAction != ManifestToolActions.Validate
Expand Down
4 changes: 4 additions & 0 deletions src/Microsoft.Sbom.Api/Config/ConfigurationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public async Task<InputConfiguration> GetConfiguration(T args)
redactArgs.ManifestToolAction = ManifestToolActions.Redact;
commandLineArgs = mapper.Map<InputConfiguration>(redactArgs);
break;
case FormatValidationArgs formatValidationArgs:
formatValidationArgs.ManifestToolAction = ManifestToolActions.ValidateFormat;
commandLineArgs = mapper.Map<InputConfiguration>(formatValidationArgs);
break;
default:
throw new ValidationArgException($"Unsupported configuration type found {typeof(T)}");
}
Expand Down
14 changes: 14 additions & 0 deletions src/Microsoft.Sbom.Api/Config/SbomToolCmdRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ public ValidationArgs Validate(ValidationArgs validationArgs)
return validationArgs;
}

/// <summary>
/// Validate a build artifact using the manifest. Optionally also verify the signing certificate of the manifest.
/// </summary>
/// <param name="validationArgs"></param>
[ArgShortcut("validate-format")]
[ArgShortcut("vf")]
[ArgActionMethod]
[ArgDescription("Validate the version and format of the SBOM")]
[OmitFromUsageDocs]
public FormatValidationArgs ValidateFormat(FormatValidationArgs validationArgs)
{
return validationArgs;
}

/// <summary>
/// Generate a manifest.json and a bsi.json for all the files in the given build drop folder.
/// </summary>
Expand Down
27 changes: 27 additions & 0 deletions src/Microsoft.Sbom.Api/ConfigurationProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,33 @@ public ConfigurationProfile()
.ForMember(c => c.DeleteManifestDirIfPresent, o => o.Ignore())
.ForMember(c => c.PackageSupplier, o => o.Ignore());

CreateMap<FormatValidationArgs, InputConfiguration>()
#pragma warning disable CS0618 // 'Configuration.ManifestPath' is obsolete: 'This field is not provided by the user or configFile, set by system'
.ForMember(c => c.ManifestPath, o => o.Ignore())
#pragma warning restore CS0618 // 'Configuration.ManifestPath' is obsolete: 'This field is not provided by the user or configFile, set by system'
.ForMember(c => c.HashAlgorithm, o => o.Ignore())
.ForMember(c => c.RootPathFilter, o => o.Ignore())
.ForMember(c => c.CatalogFilePath, o => o.Ignore())
.ForMember(c => c.ValidateSignature, o => o.Ignore())
.ForMember(c => c.PackagesList, o => o.Ignore())
.ForMember(c => c.FilesList, o => o.Ignore())
.ForMember(c => c.IgnoreMissing, o => o.Ignore())
.ForMember(c => c.FailIfNoPackages, o => o.Ignore())
.ForMember(c => c.PackageName, o => o.Ignore())
.ForMember(c => c.PackageVersion, o => o.Ignore())
.ForMember(c => c.BuildListFile, o => o.Ignore())
.ForMember(c => c.ExternalDocumentReferenceListFile, o => o.Ignore())
.ForMember(c => c.BuildComponentPath, o => o.Ignore())
.ForMember(c => c.PackagesList, o => o.Ignore())
.ForMember(c => c.FilesList, o => o.Ignore())
.ForMember(c => c.DockerImagesToScan, o => o.Ignore())
.ForMember(c => c.AdditionalComponentDetectorArgs, o => o.Ignore())
.ForMember(c => c.GenerationTimestamp, o => o.Ignore())
.ForMember(c => c.NamespaceUriUniquePart, o => o.Ignore())
.ForMember(c => c.NamespaceUriBase, o => o.Ignore())
.ForMember(c => c.DeleteManifestDirIfPresent, o => o.Ignore())
.ForMember(c => c.PackageSupplier, o => o.Ignore());

// Create config for the generation args, ignoring other action members
CreateMap<GenerationArgs, InputConfiguration>()
#pragma warning disable CS0618 // 'Configuration.ManifestPath' is obsolete: 'This field is not provided by the user or configFile, set by system'
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.Sbom.Common/Config/ManifestToolActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public enum ManifestToolActions
Validate = 1,
Generate = 2,
Redact = 4,
All = Validate | Generate | Redact
ValidateFormat = 8,
All = Validate | Generate | Redact | ValidateFormat
}
52 changes: 52 additions & 0 deletions src/Microsoft.Sbom.DotNetTool/FormatValidationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Sbom.Api;
using Microsoft.Sbom.Api.Output.Telemetry;
using Microsoft.Sbom.Common.Config;

namespace Microsoft.Sbom.Tool;

public class FormatValidationService : IHostedService
{
private readonly IConfiguration config;
private readonly IRecorder recorder;
private readonly IHostApplicationLifetime hostApplicationLifetime;

public FormatValidationService(IConfiguration config,
IRecorder recorder,
IHostApplicationLifetime hostApplicationLifetime)
{
this.config = config;
this.recorder = recorder;
this.hostApplicationLifetime = hostApplicationLifetime;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
Console.WriteLine($"Format validation service called for {config.SbomPath.Value}");

await recorder.FinalizeAndLogTelemetryAsync();
Environment.ExitCode = true ? (int)ExitCode.Success : (int)ExitCode.ValidationError;
}
catch (Exception e)
{
var message = e.InnerException != null ? e.InnerException.Message : e.Message;
Console.WriteLine($"Encountered error while running format validation. Error: {message}");
Environment.ExitCode = (int)ExitCode.GeneralError;
}

hostApplicationLifetime.StopApplication();
}

public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
52 changes: 52 additions & 0 deletions src/Microsoft.Sbom.Tool/FormatValidationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Sbom.Api;
using Microsoft.Sbom.Api.Output.Telemetry;
using Microsoft.Sbom.Common.Config;

namespace Microsoft.Sbom.Tool;

public class FormatValidationService : IHostedService
{
private readonly IConfiguration config;
private readonly IRecorder recorder;
private readonly IHostApplicationLifetime hostApplicationLifetime;

public FormatValidationService(IConfiguration config,
IRecorder recorder,
IHostApplicationLifetime hostApplicationLifetime)
{
this.config = config;
this.recorder = recorder;
this.hostApplicationLifetime = hostApplicationLifetime;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
Console.WriteLine($"Format validation service called for {config.SbomPath.Value}");

await recorder.FinalizeAndLogTelemetryAsync();
Environment.ExitCode = true ? (int)ExitCode.Success : (int)ExitCode.ValidationError;
}
catch (Exception e)
{
var message = e.InnerException != null ? e.InnerException.Message : e.Message;
Console.WriteLine($"Encountered error while running format validation. Error: {message}");
Environment.ExitCode = (int)ExitCode.GeneralError;
}

hostApplicationLifetime.StopApplication();
}

public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
3 changes: 3 additions & 0 deletions src/Microsoft.Sbom.Tool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ await Host.CreateDefaultBuilder(args)
ValidationArgs v => services.AddHostedService<ValidationService>(),
GenerationArgs g => services.AddHostedService<GenerationService>(),
RedactArgs r => services.AddHostedService<RedactService>(),
FormatValidationArgs f => services.AddHostedService<FormatValidationService>(),
_ => services
};

Expand All @@ -62,11 +63,13 @@ await Host.CreateDefaultBuilder(args)
var validationConfigurationBuilder = x.GetService<IConfigurationBuilder<ValidationArgs>>();
var generationConfigurationBuilder = x.GetService<IConfigurationBuilder<GenerationArgs>>();
var redactConfigurationBuilder = x.GetService<IConfigurationBuilder<RedactArgs>>();
var formatValidationConfigurationBuilder = x.GetService<IConfigurationBuilder<FormatValidationArgs>>();
var inputConfiguration = result.ActionArgs switch
{
ValidationArgs v => validationConfigurationBuilder.GetConfiguration(v).GetAwaiter().GetResult(),
GenerationArgs g => generationConfigurationBuilder.GetConfiguration(g).GetAwaiter().GetResult(),
RedactArgs r => redactConfigurationBuilder.GetConfiguration(r).GetAwaiter().GetResult(),
FormatValidationArgs f => formatValidationConfigurationBuilder.GetConfiguration(f).GetAwaiter().GetResult(),
_ => default
};

Expand Down
25 changes: 25 additions & 0 deletions test/Microsoft.Sbom.Api.Tests/Config/ConfigSanitizerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,31 @@ public void NoValueForBuildDropPathForRedaction_Succeeds()
configSanitizer.SanitizeConfig(config);
}

[TestMethod]
public void NoValueForBuildDropPathForValidateFormat_Succeeds()
{
var config = GetConfigurationBaseObject();
config.ManifestToolAction = ManifestToolActions.ValidateFormat;
config.BuildDropPath = null;
config.SbomPath = new ConfigurationSetting<string>
{
Source = SettingSource.Default,
Value = "any non empty value"
};

configSanitizer.SanitizeConfig(config);
}

[TestMethod]
public void NoValueForSbomPathForValidateFormat_Throws()
{
var config = GetConfigurationBaseObject();
config.ManifestToolAction = ManifestToolActions.ValidateFormat;
config.SbomPath = null;

Assert.ThrowsException<ValidationArgException>(() => configSanitizer.SanitizeConfig(config));
}

[TestMethod]
public void NoValueForManifestInfoForValidation_SetsDefaultValue()
{
Expand Down

0 comments on commit 1d832e0

Please sign in to comment.