Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate singular SBOM based on manifestInfo parameter #959

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
37 changes: 31 additions & 6 deletions src/Microsoft.Sbom.Api/Config/ConfigSanitizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
//using System.Linq;
using System.Reflection;
using Microsoft.Sbom.Api.Hashing;
using Microsoft.Sbom.Api.Utils;
Expand Down Expand Up @@ -72,6 +73,9 @@ public IConfiguration SanitizeConfig(IConfiguration configuration)
configuration.NamespaceUriBase = GetNamespaceBaseUri(configuration, logger);
}

// Set default ManifestInfo for generation in case user doesn't provide a value.
configuration.ManifestInfo = GetDefaultManifestInfoForGenerationAction(configuration);

// Set default ManifestInfo for validation in case user doesn't provide a value.
configuration.ManifestInfo = GetDefaultManifestInfoForValidationAction(configuration);

Expand Down Expand Up @@ -138,15 +142,36 @@ private ConfigurationSetting<IList<ManifestInfo>> GetDefaultManifestInfoForValid
}

return new ConfigurationSetting<IList<ManifestInfo>>
{
Source = SettingSource.Default,
Value = new List<ManifestInfo>()
{
Source = SettingSource.Default,
Value = new List<ManifestInfo>()
{
defaultManifestInfo
}
};
defaultManifestInfo
}
};
}

private ConfigurationSetting<IList<ManifestInfo>> GetDefaultManifestInfoForGenerationAction(IConfiguration configuration)
{
if (configuration.ManifestToolAction != ManifestToolActions.Generate)
{
return configuration.ManifestInfo;
}

if (configuration.ManifestInfo?.Value != null && configuration.ManifestInfo.Value.Count != 0)
{
return configuration.ManifestInfo;
}

// Use default ManifestInfo for generation if none is given.
var defaultManifestInfo = assemblyConfig.DefaultManifestInfoForGenerationAction;
return new ConfigurationSetting<IList<ManifestInfo>>
{
Source = SettingSource.Default,
Value = new List<ManifestInfo> { defaultManifestInfo }
};
}

private void ValidateBuildDropPathConfiguration(IConfiguration configuration)
{
if (configuration.ManifestToolAction == ManifestToolActions.Generate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal interface IJsonSerializationStrategy

public void AddToExternalDocRefsSupportingConfig(IList<ISbomConfig> elementsSupportingConfigs, ISbomConfig config);

public void AddHeadersToSbom(ISbomConfigProvider sbomConfigs)
public void AddHeadersToSbom(ISbomConfigProvider sbomConfigs, ISbomConfig config)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,9 @@ public void AddToExternalDocRefsSupportingConfig(IList<ISbomConfig> elementsSupp
}
}

public void AddHeadersToSbom(ISbomConfigProvider sbomConfigs)
public void AddHeadersToSbom(ISbomConfigProvider sbomConfigs, ISbomConfig config)
{
sbomConfigs.ApplyToEachConfig(config =>
config.JsonSerializer.WriteJsonString(
config.MetadataBuilder.GetHeaderJsonString(sbomConfigs)));
config.JsonSerializer?.WriteJsonString(config.MetadataBuilder.GetHeaderJsonString(sbomConfigs));
}

public async Task<List<FileValidationResult>> WriteJsonObjectsToSbomAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ public virtual bool TryGetManifestConfig(out ISbomConfig sbomConfig)
{
sbomConfig = CreateSbomConfig();

// For generation the default behavior is to always return true
// as we generate all the current formats of SBOM. Only override if the -mi
// argument is specified.
// For generation the default behavior is to return the SPDX 2.2 SBOM.
// Only override if the -mi argument is specified.
if (configuration.ManifestToolAction == ManifestToolActions.Generate)
{
if (configuration.ManifestInfo?.Value != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ public IAsyncDisposable StartJsonSerializationAsync()
return this;
}

public IAsyncDisposable StartJsonSerializationAsync(ISbomConfig configuration)
{
configuration.StartJsonSerialization();
return this;
}

/// <summary>
/// Helper method to operate an action on each included configs.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.Sbom.Api/Utils/AssemblyConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public class AssemblyConfig : IAssemblyConfig
/// <inheritdoc/>
public ManifestInfo DefaultManifestInfoForValidationAction => DefaultManifestInfoForValidationActionValue.Value;

/// <inheritdoc>
public ManifestInfo DefaultManifestInfoForGenerationAction => DefaultManifestInfoForGenerationActionValue.Value;

public string DefaultPackageSupplier => PackageSupplier.Value;

/// <inheritdoc/>
Expand All @@ -30,6 +33,9 @@ private static readonly Lazy<string> DefaultSbomBaseNamespaceUri
private static readonly Lazy<ManifestInfo> DefaultManifestInfoForValidationActionValue
= GetCustomAttributeValue<DefaultManifestInfoArgForValidationAttribute, ManifestInfo>(a => a?.ManifestInfo);

private static readonly Lazy<ManifestInfo> DefaultManifestInfoForGenerationActionValue
= GetCustomAttributeValue<DefaultManifestInfoArgForGenerationAttribute, ManifestInfo>(a => a?.ManifestInfo);

private static readonly Lazy<string> PackageSupplier = GetCustomAttributeValue<PackageSupplierAttribute, string>(a => a?.PackageSupplier);

private static readonly Lazy<string> AssemblyDirectoryValue = new Lazy<string>(() =>
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.Sbom.Api/Utils/IAssemblyConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public interface IAssemblyConfig
/// </summary>
public ManifestInfo DefaultManifestInfoForValidationAction { get; }

/// <summary>
/// Gets the default value to use for ManifestInfo for generation action in case the user doesn't provide a
/// value.
/// </summary>
public ManifestInfo DefaultManifestInfoForGenerationAction { get; }

/// <summary>
/// Gets the directory where the current executing assembly is located.
/// </summary>
Expand Down
27 changes: 13 additions & 14 deletions src/Microsoft.Sbom.Api/Workflows/SbomGenerationWorkflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,15 @@ public virtual async Task<bool> RunAsync()
log.Information("Manifest directory path was explicitly defined. Will not attempt to delete any existing _manifest directory.");
}

await using (sbomConfigs.StartJsonSerializationAsync())
{
sbomConfigs.ApplyToEachConfig(config => config.JsonSerializer.StartJsonObject());

var manifestInfos = sbomConfigs.GetManifestInfos();
var manifestInfos = configuration.ManifestInfo.Value;

// Use the WriteJsonObjectsToSbomAsync method based on the SPDX version in manifest info
foreach (var manifestInfo in manifestInfos)
// Use the WriteJsonObjectsToSbomAsync method based on the SPDX version in manifest info
foreach (var manifestInfo in manifestInfos)
{
var config = sbomConfigs.Get(manifestInfo);
await using (sbomConfigs.StartJsonSerializationAsync(config))
{
var config = sbomConfigs.Get(manifestInfo);
config.JsonSerializer?.StartJsonObject();

// Get the appropriate strategy
var serializationStrategy = JsonSerializationStrategyFactory.GetStrategy(manifestInfo.Version);
Expand All @@ -117,16 +116,16 @@ public virtual async Task<bool> RunAsync()
ConfigureAwait(false);

// Write headers
serializationStrategy.AddHeadersToSbom(sbomConfigs);
serializationStrategy.AddHeadersToSbom(sbomConfigs, config);

// Finalize JSON
config.JsonSerializer?.FinalizeJsonObject();
}

// Finalize JSON
sbomConfigs.ApplyToEachConfig(config => config.JsonSerializer.FinalizeJsonObject());
// Generate SHA256 for manifest json
GenerateHashForManifestJson(config.ManifestJsonFilePath);
}

// Generate SHA256 for manifest json
sbomConfigs.ApplyToEachConfig(config => GenerateHashForManifestJson(config.ManifestJsonFilePath));

return !validErrors.Any();
}
catch (Exception e)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.Sbom.Extensions.Entities;

namespace Microsoft.Sbom.Common.Config.Attributes;

[AttributeUsage(AttributeTargets.Assembly)]
[SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "The properties are exposed via a property bag")]
public sealed class DefaultManifestInfoArgForGenerationAttribute : Attribute
{
/// <summary>
/// Gets or sets the default value of the ManifestInfo to use in case of validation action
/// where the user hasn't provided any parameter value.
/// </summary>
public ManifestInfo ManifestInfo { get; set; }

public DefaultManifestInfoArgForGenerationAttribute(string name, string version)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException($"The default value for '{nameof(name)}' cannot be null or empty.", nameof(name));
}

if (string.IsNullOrEmpty(version))
{
throw new ArgumentException($"The default value for '{nameof(version)}' cannot be null or empty.", nameof(version));
}

ManifestInfo = new ManifestInfo
{
Name = name,
Version = version
};
}
}
3 changes: 3 additions & 0 deletions src/Microsoft.Sbom.Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ public static class Constants
public const int MaxLicenseFetchTimeoutInSeconds = 86400;

public const LogEventLevel DefaultLogLevel = LogEventLevel.Warning;

public const string DefaultManifestInfoName = "SPDX";
public const string DefaultManifestInfoVersion = "2.2";
}
7 changes: 7 additions & 0 deletions src/Microsoft.Sbom.Common/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Sbom.Common;
using Microsoft.Sbom.Common.Config.Attributes;

[assembly: DefaultManifestInfoArgForGeneration(Constants.DefaultManifestInfoName, Constants.DefaultManifestInfoVersion)]
7 changes: 7 additions & 0 deletions src/Microsoft.Sbom.Extensions/ISbomConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ public interface ISbomConfigProvider : IDisposable, IAsyncDisposable, IInternalM
/// <returns></returns>
IAsyncDisposable StartJsonSerializationAsync();

/// <summary>
/// Starts the JSON serialization of the specified ISbomConfig object asynchronously.
/// This returns a <see cref="IAsyncDisposable"/> object that is used to clean up the JSON streams.
/// </summary>
/// <returns></returns>
IAsyncDisposable StartJsonSerializationAsync(ISbomConfig configuration);

/// <summary>
/// Helper method to operate an action on each included configs.
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion src/Microsoft.Sbom.Tool/ValidationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Sbom.Api;
using Microsoft.Sbom.Api.Exceptions;
using Microsoft.Sbom.Api.Output.Telemetry;
using Microsoft.Sbom.Api.Utils;
using Microsoft.Sbom.Api.Workflows;
using IConfiguration = Microsoft.Sbom.Common.Config.IConfiguration;

Expand Down Expand Up @@ -40,7 +42,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
bool result;
try
{
if (configuration.ManifestInfo.Value.Contains(Api.Utils.Constants.SPDX22ManifestInfo))
if (configuration.ManifestInfo.Value.Any(Constants.SupportedSpdxManifests.Contains))
{
result = await parserValidationWorkflow.RunAsync();
}
Expand Down
38 changes: 38 additions & 0 deletions test/Microsoft.Sbom.Api.Tests/Config/ConfigSanitizerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ public void NoValueForManifestInfoForValidation_Throws()
Assert.ThrowsException<ValidationArgException>(() => configSanitizer.SanitizeConfig(config));
}

[TestMethod]
public void SetValueForManifestInfoForGeneration_Succeeds()
{
var config = GetConfigurationBaseObject();
config.ManifestToolAction = ManifestToolActions.Generate;
configSanitizer.SanitizeConfig(config);

mockAssemblyConfig.Verify();
}

[TestMethod]
public void NoValueForManifestInfoForGeneration_Succeeds()
{
var config = GetConfigurationBaseObject();
config.ManifestToolAction = ManifestToolActions.Generate;
config.ManifestInfo.Value.Clear();
configSanitizer.SanitizeConfig(config);

mockAssemblyConfig.Verify();
}

[TestMethod]
public void NoValueForBuildDropPathForRedaction_Succeeds()
{
Expand Down Expand Up @@ -163,6 +184,23 @@ public void NoValueForManifestInfoForValidation_SetsDefaultValue()
mockAssemblyConfig.VerifyGet(a => a.DefaultManifestInfoForValidationAction);
}

[TestMethod]
public void NoValueForManifestInfoForGeneration_SetsDefaultValue()
{
var config = GetConfigurationBaseObject();
config.ManifestToolAction = ManifestToolActions.Generate;
config.ManifestInfo.Value.Clear();
mockAssemblyConfig.SetupGet(a => a.DefaultManifestInfoForGenerationAction).Returns(Constants.TestManifestInfo);

var sanitizedConfig = configSanitizer.SanitizeConfig(config);

Assert.IsNotNull(sanitizedConfig.ManifestInfo.Value);
Assert.AreEqual(1, sanitizedConfig.ManifestInfo.Value.Count);
Assert.AreEqual(Constants.TestManifestInfo, sanitizedConfig.ManifestInfo.Value.First());

mockAssemblyConfig.VerifyGet(a => a.DefaultManifestInfoForGenerationAction);
}

[TestMethod]
public void ForGenerateActionIgnoresEmptyAlgorithmName_Succeeds()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ protected void Init()
fileSystemUtilsMock = new Mock<IFileSystemUtils>();
mockAssemblyConfig = new Mock<IAssemblyConfig>();
mockAssemblyConfig.SetupGet(a => a.DefaultManifestInfoForValidationAction).Returns(Constants.TestManifestInfo);
mockAssemblyConfig.SetupGet(a => a.DefaultManifestInfoForGenerationAction).Returns(Constants.TestManifestInfo);

configValidators = new ConfigValidator[]
{
Expand All @@ -36,7 +37,8 @@ protected void Init()
new FileExistsValidator(fileSystemUtilsMock.Object, mockAssemblyConfig.Object),
new DirectoryExistsValidator(fileSystemUtilsMock.Object, mockAssemblyConfig.Object),
new DirectoryPathIsWritableValidator(fileSystemUtilsMock.Object, mockAssemblyConfig.Object),
new UriValidator(mockAssemblyConfig.Object)
new UriValidator(mockAssemblyConfig.Object),
new ManifestInfoValidator(mockAssemblyConfig.Object)
};

var hashAlgorithmProvider = new HashAlgorithmProvider(new IAlgorithmNames[] { new AlgorithmNames() });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ private class PinnedIAssemblyConfig : IAssemblyConfig

public ManifestInfo DefaultManifestInfoForValidationAction => throw new NotImplementedException();

public ManifestInfo DefaultManifestInfoForGenerationAction => throw new NotImplementedException();

public string AssemblyDirectory => throw new NotImplementedException();

public string DefaultPackageSupplier => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ public async Task ManifestGenerationWorkflowTests_Succeeds(string spdxVersionFor
configurationMock.SetupGet(c => c.ManifestToolAction).Returns(ManifestToolActions.Generate);
configurationMock.SetupGet(c => c.BuildComponentPath).Returns(new ConfigurationSetting<string> { Value = "/root" });
configurationMock.SetupGet(c => c.FollowSymlinks).Returns(new ConfigurationSetting<bool> { Value = true });
configurationMock.SetupGet(x => x.ManifestInfo).Returns(new ConfigurationSetting<IList<ManifestInfo>> { Value = new List<ManifestInfo> { manifestInfoPerSpdxVersion[spdxVersionForGenerator] }, Source = SettingSource.CommandLine });

// Added config settings necessary for 3.0 SBOM generation
if (spdxVersionForGenerator == "3.0")
Expand Down Expand Up @@ -452,7 +453,10 @@ public async Task ManifestGenerationWorkflowTests_SBOMDir_NotDefault_NotDeleted(
ManifestJsonDirPath = "/root/_manifest",
ManifestJsonFilePath = "/root/_manifest/manifest.json"
};

IList<ManifestInfo> testManifestInfo = new List<ManifestInfo> { Constants.TestManifestInfo };
configurationMock.SetupGet(x => x.ManifestDirPath).Returns(new ConfigurationSetting<string> { Value = PathUtils.Join("/root", "_manifest"), Source = SettingSource.CommandLine });
configurationMock.SetupGet(x => x.ManifestInfo).Returns(new ConfigurationSetting<IList<ManifestInfo>> { Value = testManifestInfo, Source = SettingSource.CommandLine });
fileSystemMock.Setup(f => f.DirectoryExists(It.IsAny<string>())).Returns(true);
fileSystemMock.Setup(f => f.DeleteDir(It.IsAny<string>(), true)).Verifiable();

Expand All @@ -472,7 +476,7 @@ public async Task ManifestGenerationWorkflowTests_SBOMDir_NotDefault_NotDeleted(
externalDocumentReferenceGeneratorMock.Setup(f => f.GenerateAsync()).ReturnsAsync(generationResultWithFailure);

var sbomConfigsMock = new Mock<ISbomConfigProvider>();
sbomConfigsMock.Setup(f => f.GetManifestInfos()).Returns(new List<ManifestInfo> { Constants.TestManifestInfo });
sbomConfigsMock.Setup(f => f.Get(It.IsAny<ManifestInfo>())).Returns(sbomConfig);

var workflow = new SbomGenerationWorkflow(
configurationMock.Object,
Expand Down
Loading