- Implemented Attestation Chain API client with methods for verifying, fetching, and managing attestation chains. - Created models for Attestation Chain, including DSSE envelope structures and verification results. - Developed Triage Evidence API client for fetching finding evidence, including methods for evidence retrieval by CVE and component. - Added models for Triage Evidence, encapsulating evidence responses, entry points, boundary proofs, and VEX evidence. - Introduced mock implementations for both API clients to facilitate testing and development.
156 lines
4.9 KiB
C#
156 lines
4.9 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Scanner.Analyzers.Native.Index;
|
|
|
|
namespace StellaOps.Scanner.Emit.Native;
|
|
|
|
/// <summary>
|
|
/// Emits native binary components for SBOM generation.
|
|
/// Uses the Build-ID index to resolve PURLs when possible.
|
|
/// </summary>
|
|
public sealed class NativeComponentEmitter : INativeComponentEmitter
|
|
{
|
|
private readonly IBuildIdIndex _buildIdIndex;
|
|
private readonly NativePurlBuilder _purlBuilder;
|
|
private readonly ILogger<NativeComponentEmitter> _logger;
|
|
|
|
/// <summary>
|
|
/// Creates a new native component emitter.
|
|
/// </summary>
|
|
public NativeComponentEmitter(
|
|
IBuildIdIndex buildIdIndex,
|
|
ILogger<NativeComponentEmitter> logger)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(buildIdIndex);
|
|
ArgumentNullException.ThrowIfNull(logger);
|
|
|
|
_buildIdIndex = buildIdIndex;
|
|
_purlBuilder = new NativePurlBuilder();
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<NativeComponentEmitResult> EmitAsync(
|
|
NativeBinaryMetadata metadata,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(metadata);
|
|
|
|
// Try to resolve via Build-ID index
|
|
BuildIdLookupResult? lookupResult = null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(metadata.BuildId))
|
|
{
|
|
lookupResult = await _buildIdIndex.LookupAsync(metadata.BuildId, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
string purl;
|
|
string? version = null;
|
|
bool indexMatch = false;
|
|
|
|
if (lookupResult is not null)
|
|
{
|
|
// Index match - use the resolved PURL
|
|
purl = _purlBuilder.FromIndexResult(lookupResult);
|
|
version = lookupResult.Version;
|
|
indexMatch = true;
|
|
|
|
_logger.LogDebug(
|
|
"Resolved binary {FilePath} via Build-ID index: {Purl}",
|
|
metadata.FilePath,
|
|
purl);
|
|
}
|
|
else
|
|
{
|
|
// No match - generate generic PURL
|
|
purl = _purlBuilder.FromUnresolvedBinary(metadata);
|
|
version = metadata.ProductVersion ?? metadata.FileVersion;
|
|
|
|
_logger.LogDebug(
|
|
"Unresolved binary {FilePath}, generated generic PURL: {Purl}",
|
|
metadata.FilePath,
|
|
purl);
|
|
}
|
|
|
|
var name = Path.GetFileName(metadata.FilePath);
|
|
|
|
return new NativeComponentEmitResult(
|
|
Purl: purl,
|
|
Name: name,
|
|
Version: version,
|
|
Metadata: metadata,
|
|
IndexMatch: indexMatch,
|
|
LookupResult: lookupResult);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IReadOnlyList<NativeComponentEmitResult>> EmitBatchAsync(
|
|
IEnumerable<NativeBinaryMetadata> metadataList,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(metadataList);
|
|
|
|
var metadataArray = metadataList.ToArray();
|
|
if (metadataArray.Length == 0)
|
|
{
|
|
return Array.Empty<NativeComponentEmitResult>();
|
|
}
|
|
|
|
// Batch lookup for all Build-IDs
|
|
var buildIds = metadataArray
|
|
.Where(m => !string.IsNullOrWhiteSpace(m.BuildId))
|
|
.Select(m => m.BuildId!)
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray();
|
|
|
|
var lookupResults = await _buildIdIndex.BatchLookupAsync(buildIds, cancellationToken).ConfigureAwait(false);
|
|
var lookupMap = lookupResults.ToDictionary(
|
|
r => r.BuildId,
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
_logger.LogDebug(
|
|
"Batch lookup: {Total} binaries, {Resolved} resolved via index",
|
|
metadataArray.Length,
|
|
lookupMap.Count);
|
|
|
|
// Emit components
|
|
var results = new List<NativeComponentEmitResult>(metadataArray.Length);
|
|
|
|
foreach (var metadata in metadataArray)
|
|
{
|
|
BuildIdLookupResult? lookupResult = null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(metadata.BuildId) &&
|
|
lookupMap.TryGetValue(metadata.BuildId, out var result))
|
|
{
|
|
lookupResult = result;
|
|
}
|
|
|
|
string purl;
|
|
string? version = null;
|
|
bool indexMatch = false;
|
|
|
|
if (lookupResult is not null)
|
|
{
|
|
purl = _purlBuilder.FromIndexResult(lookupResult);
|
|
version = lookupResult.Version;
|
|
indexMatch = true;
|
|
}
|
|
else
|
|
{
|
|
purl = _purlBuilder.FromUnresolvedBinary(metadata);
|
|
version = metadata.ProductVersion ?? metadata.FileVersion;
|
|
}
|
|
|
|
results.Add(new NativeComponentEmitResult(
|
|
Purl: purl,
|
|
Name: Path.GetFileName(metadata.FilePath),
|
|
Version: version,
|
|
Metadata: metadata,
|
|
IndexMatch: indexMatch,
|
|
LookupResult: lookupResult));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
}
|