save work

This commit is contained in:
StellaOps Bot
2025-12-19 09:40:41 +02:00
parent 2eafe98d44
commit 43882078a4
44 changed files with 3044 additions and 492 deletions

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Scanner.Analyzers.Native.Index;
using StellaOps.Scanner.Emit.Composition;
using StellaOps.Scanner.Emit.Native;
using Xunit;
namespace StellaOps.Scanner.Emit.Tests.Native;
public sealed class NativeBinarySbomIntegrationTests
{
[Fact]
public async Task Compose_EmitsNativeBinariesAsFileComponents_WithBuildIdPurlAndLayerTracking()
{
var index = new FakeBuildIdIndex();
index.AddEntry("gnu-build-id:abc123", new BuildIdLookupResult(
BuildId: "gnu-build-id:abc123",
Purl: "pkg:deb/debian/libc6@2.31",
Version: "2.31",
SourceDistro: "debian",
Confidence: BuildIdConfidence.Exact,
IndexedAt: new DateTimeOffset(2025, 12, 19, 0, 0, 0, TimeSpan.Zero)));
var emitter = new NativeComponentEmitter(index, NullLogger<NativeComponentEmitter>.Instance);
var mapper = new NativeComponentMapper(emitter);
const string layer1 = "sha256:layer1";
const string layer2 = "sha256:layer2";
var resolvedBinary = new NativeBinaryMetadata
{
Format = "elf",
FilePath = "/usr/lib/libc.so.6",
BuildId = "GNU-BUILD-ID:ABC123",
Architecture = "x86_64",
Platform = "linux",
};
var unresolvedBinary = new NativeBinaryMetadata
{
Format = "elf",
FilePath = "/usr/lib/libssl.so.3",
BuildId = "gnu-build-id:def456",
Architecture = "x86_64",
Platform = "linux",
};
var mappingLayer1 = await mapper.MapLayerAsync(layer1, new[] { resolvedBinary, unresolvedBinary });
var mappingLayer2 = await mapper.MapLayerAsync(layer2, new[] { resolvedBinary });
var request = SbomCompositionRequest.Create(
new ImageArtifactDescriptor
{
ImageDigest = "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
ImageReference = "registry.example.com/app/service:1.2.3",
Repository = "registry.example.com/app/service",
Tag = "1.2.3",
Architecture = "amd64",
},
new[] { mappingLayer1.ToFragment(), mappingLayer2.ToFragment() },
new DateTimeOffset(2025, 12, 19, 12, 0, 0, TimeSpan.Zero),
generatorName: "StellaOps.Scanner",
generatorVersion: "0.10.0");
var composer = new CycloneDxComposer();
var result = composer.Compose(request);
using var document = JsonDocument.Parse(result.Inventory.JsonBytes);
var components = document.RootElement.GetProperty("components").EnumerateArray().ToArray();
Assert.Equal(2, components.Length);
var resolvedPurl = mappingLayer1.Components.Single(component => component.IndexMatch).Purl;
var unresolvedPurl = mappingLayer1.Components.Single(component => !component.IndexMatch).Purl;
var resolvedComponent = components.Single(component => string.Equals(component.GetProperty("purl").GetString(), resolvedPurl, StringComparison.Ordinal));
Assert.Equal("file", resolvedComponent.GetProperty("type").GetString());
Assert.Equal(resolvedPurl, resolvedComponent.GetProperty("bom-ref").GetString());
var resolvedProperties = resolvedComponent
.GetProperty("properties")
.EnumerateArray()
.ToDictionary(
property => property.GetProperty("name").GetString()!,
property => property.GetProperty("value").GetString()!,
StringComparer.Ordinal);
Assert.Equal("gnu-build-id:abc123", resolvedProperties["stellaops:buildId"]);
Assert.Equal("elf", resolvedProperties["stellaops:binary.format"]);
Assert.Equal(layer1, resolvedProperties["stellaops:firstLayerDigest"]);
Assert.Equal(layer2, resolvedProperties["stellaops:lastLayerDigest"]);
Assert.Equal($"{layer1},{layer2}", resolvedProperties["stellaops:layerDigests"]);
var unresolvedComponent = components.Single(component => string.Equals(component.GetProperty("purl").GetString(), unresolvedPurl, StringComparison.Ordinal));
Assert.Equal("file", unresolvedComponent.GetProperty("type").GetString());
Assert.StartsWith("pkg:generic/libssl.so.3@unknown", unresolvedPurl, StringComparison.Ordinal);
Assert.Contains("build-id=gnu-build-id%3Adef456", unresolvedPurl, StringComparison.Ordinal);
}
private sealed class FakeBuildIdIndex : IBuildIdIndex
{
private readonly Dictionary<string, BuildIdLookupResult> _entries = new(StringComparer.OrdinalIgnoreCase);
public int Count => _entries.Count;
public bool IsLoaded => true;
public void AddEntry(string buildId, BuildIdLookupResult result)
{
_entries[buildId] = result;
}
public Task<BuildIdLookupResult?> LookupAsync(string buildId, CancellationToken cancellationToken = default)
{
_entries.TryGetValue(buildId, out var result);
return Task.FromResult(result);
}
public Task<IReadOnlyList<BuildIdLookupResult>> BatchLookupAsync(IEnumerable<string> buildIds, CancellationToken cancellationToken = default)
{
var results = buildIds
.Select(id => _entries.TryGetValue(id, out var result) ? result : null)
.Where(result => result is not null)
.Select(result => result!)
.ToList();
return Task.FromResult<IReadOnlyList<BuildIdLookupResult>>(results);
}
public Task LoadAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
}
}