release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.Feedser.BinaryAnalysis;
|
||||
using StellaOps.Feedser.BinaryAnalysis.Models;
|
||||
using StellaOps.Scanner.PatchVerification.Models;
|
||||
using StellaOps.Scanner.PatchVerification.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.PatchVerification.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class PatchVerificationOrchestratorTests
|
||||
{
|
||||
private readonly Mock<IBinaryFingerprinter> _mockFingerprinter;
|
||||
private readonly InMemoryPatchSignatureStore _signatureStore;
|
||||
private readonly PatchVerificationOrchestrator _orchestrator;
|
||||
|
||||
public PatchVerificationOrchestratorTests()
|
||||
{
|
||||
_mockFingerprinter = new Mock<IBinaryFingerprinter>();
|
||||
_mockFingerprinter.Setup(f => f.Method).Returns(FingerprintMethod.SectionHash);
|
||||
|
||||
_signatureStore = new InMemoryPatchSignatureStore();
|
||||
|
||||
_orchestrator = new PatchVerificationOrchestrator(
|
||||
[_mockFingerprinter.Object],
|
||||
_signatureStore,
|
||||
TimeProvider.System,
|
||||
NullLogger<PatchVerificationOrchestrator>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_NoPatchData_ReturnsNoPatchDataStatus()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(["CVE-2024-001"]);
|
||||
|
||||
// Act
|
||||
var result = await _orchestrator.VerifyAsync(context);
|
||||
|
||||
// Assert
|
||||
result.NoPatchDataCves.Should().Contain("CVE-2024-001");
|
||||
result.PatchedCves.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WithPatchData_MatchFound_ReturnsVerified()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/libtest.so");
|
||||
|
||||
_mockFingerprinter
|
||||
.Setup(f => f.MatchAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BinaryFingerprint>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new FingerprintMatchResult
|
||||
{
|
||||
IsMatch = true,
|
||||
Similarity = 0.95,
|
||||
Confidence = 0.90,
|
||||
Method = FingerprintMethod.SectionHash
|
||||
});
|
||||
|
||||
var context = CreateContext(
|
||||
["CVE-2024-001"],
|
||||
new Dictionary<string, string> { ["/lib/libtest.so"] = "/tmp/extracted/lib/libtest.so" });
|
||||
|
||||
// Act
|
||||
var result = await _orchestrator.VerifyAsync(context);
|
||||
|
||||
// Assert
|
||||
result.PatchedCves.Should().Contain("CVE-2024-001");
|
||||
result.Evidence.Should().HaveCount(1);
|
||||
result.Evidence[0].Status.Should().Be(PatchVerificationStatus.Verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WithPatchData_NoMatch_ReturnsNotPatched()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/libtest.so");
|
||||
|
||||
_mockFingerprinter
|
||||
.Setup(f => f.MatchAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BinaryFingerprint>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new FingerprintMatchResult
|
||||
{
|
||||
IsMatch = false,
|
||||
Similarity = 0.20,
|
||||
Confidence = 0.90,
|
||||
Method = FingerprintMethod.SectionHash
|
||||
});
|
||||
|
||||
var context = CreateContext(
|
||||
["CVE-2024-001"],
|
||||
new Dictionary<string, string> { ["/lib/libtest.so"] = "/tmp/extracted/lib/libtest.so" });
|
||||
|
||||
// Act
|
||||
var result = await _orchestrator.VerifyAsync(context);
|
||||
|
||||
// Assert
|
||||
result.UnpatchedCves.Should().Contain("CVE-2024-001");
|
||||
result.Evidence[0].Status.Should().Be(PatchVerificationStatus.NotPatched);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_LowConfidence_ReturnsInconclusive()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/libtest.so");
|
||||
|
||||
_mockFingerprinter
|
||||
.Setup(f => f.MatchAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BinaryFingerprint>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new FingerprintMatchResult
|
||||
{
|
||||
IsMatch = true,
|
||||
Similarity = 0.60, // Below threshold
|
||||
Confidence = 0.50, // Below threshold
|
||||
Method = FingerprintMethod.SectionHash
|
||||
});
|
||||
|
||||
var context = CreateContext(
|
||||
["CVE-2024-001"],
|
||||
new Dictionary<string, string> { ["/lib/libtest.so"] = "/tmp/extracted/lib/libtest.so" });
|
||||
|
||||
// Act
|
||||
var result = await _orchestrator.VerifyAsync(context);
|
||||
|
||||
// Assert
|
||||
result.InconclusiveCves.Should().Contain("CVE-2024-001");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_MultipleCves_ProcessesAll()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/a.so");
|
||||
await SetupPatchSignature("CVE-2024-002", "/lib/b.so");
|
||||
|
||||
_mockFingerprinter
|
||||
.Setup(f => f.MatchAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BinaryFingerprint>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new FingerprintMatchResult
|
||||
{
|
||||
IsMatch = true,
|
||||
Similarity = 0.95,
|
||||
Confidence = 0.90,
|
||||
Method = FingerprintMethod.SectionHash
|
||||
});
|
||||
|
||||
var context = CreateContext(
|
||||
["CVE-2024-001", "CVE-2024-002", "CVE-2024-003"],
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["/lib/a.so"] = "/tmp/a.so",
|
||||
["/lib/b.so"] = "/tmp/b.so"
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await _orchestrator.VerifyAsync(context);
|
||||
|
||||
// Assert
|
||||
result.PatchedCves.Should().HaveCount(2);
|
||||
result.NoPatchDataCves.Should().Contain("CVE-2024-003");
|
||||
result.TotalCvesProcessed.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ContinueOnError_DoesNotAbort()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/a.so");
|
||||
await SetupPatchSignature("CVE-2024-002", "/lib/b.so");
|
||||
|
||||
var callCount = 0;
|
||||
_mockFingerprinter
|
||||
.Setup(f => f.MatchAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BinaryFingerprint>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(() =>
|
||||
{
|
||||
callCount++;
|
||||
if (callCount == 1)
|
||||
{
|
||||
throw new InvalidOperationException("Simulated error");
|
||||
}
|
||||
return new FingerprintMatchResult
|
||||
{
|
||||
IsMatch = true,
|
||||
Similarity = 0.95,
|
||||
Confidence = 0.90,
|
||||
Method = FingerprintMethod.SectionHash
|
||||
};
|
||||
});
|
||||
|
||||
var context = CreateContext(
|
||||
["CVE-2024-001", "CVE-2024-002"],
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["/lib/a.so"] = "/tmp/a.so",
|
||||
["/lib/b.so"] = "/tmp/b.so"
|
||||
},
|
||||
new PatchVerificationOptions { ContinueOnError = true });
|
||||
|
||||
// Act
|
||||
var result = await _orchestrator.VerifyAsync(context);
|
||||
|
||||
// Assert
|
||||
result.InconclusiveCves.Should().Contain("CVE-2024-001");
|
||||
result.PatchedCves.Should().Contain("CVE-2024-002");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HasPatchDataAsync_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/test.so");
|
||||
|
||||
// Act & Assert
|
||||
(await _orchestrator.HasPatchDataAsync("CVE-2024-001")).Should().BeTrue();
|
||||
(await _orchestrator.HasPatchDataAsync("CVE-2024-999")).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCvesWithPatchDataAsync_FiltersCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/a.so");
|
||||
await SetupPatchSignature("CVE-2024-003", "/lib/c.so");
|
||||
|
||||
// Act
|
||||
var result = await _orchestrator.GetCvesWithPatchDataAsync(
|
||||
["CVE-2024-001", "CVE-2024-002", "CVE-2024-003"]);
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(2);
|
||||
result.Should().Contain("CVE-2024-001");
|
||||
result.Should().Contain("CVE-2024-003");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifySingleAsync_NoPatchData_ReturnsNoPatchDataEvidence()
|
||||
{
|
||||
// Act
|
||||
var evidence = await _orchestrator.VerifySingleAsync(
|
||||
"CVE-2024-999",
|
||||
"/lib/test.so",
|
||||
"pkg:rpm/test@1.0.0");
|
||||
|
||||
// Assert
|
||||
evidence.Status.Should().Be(PatchVerificationStatus.NoPatchData);
|
||||
evidence.CveId.Should().Be("CVE-2024-999");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifySingleAsync_WithPatchData_ReturnsVerificationResult()
|
||||
{
|
||||
// Arrange
|
||||
await SetupPatchSignature("CVE-2024-001", "/lib/libtest.so");
|
||||
|
||||
_mockFingerprinter
|
||||
.Setup(f => f.MatchAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BinaryFingerprint>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new FingerprintMatchResult
|
||||
{
|
||||
IsMatch = true,
|
||||
Similarity = 0.95,
|
||||
Confidence = 0.90,
|
||||
Method = FingerprintMethod.SectionHash
|
||||
});
|
||||
|
||||
// Act
|
||||
var evidence = await _orchestrator.VerifySingleAsync(
|
||||
"CVE-2024-001",
|
||||
"/tmp/lib/libtest.so",
|
||||
"pkg:rpm/test@1.0.0");
|
||||
|
||||
// Assert
|
||||
evidence.Status.Should().Be(PatchVerificationStatus.Verified);
|
||||
evidence.Similarity.Should().Be(0.95);
|
||||
evidence.Confidence.Should().Be(0.90);
|
||||
}
|
||||
|
||||
private async Task SetupPatchSignature(string cveId, string binaryPath)
|
||||
{
|
||||
await _signatureStore.StoreAsync(new PatchSignatureEntry
|
||||
{
|
||||
EntryId = Guid.NewGuid().ToString("N"),
|
||||
CveId = cveId,
|
||||
Purl = "pkg:rpm/test@1.0.0",
|
||||
BinaryPath = binaryPath,
|
||||
PatchedFingerprint = new BinaryFingerprint
|
||||
{
|
||||
FingerprintId = $"fp:test:{Guid.NewGuid():N}",
|
||||
CveId = cveId,
|
||||
Method = FingerprintMethod.SectionHash,
|
||||
FingerprintValue = "abc123",
|
||||
TargetBinary = binaryPath,
|
||||
Metadata = new FingerprintMetadata
|
||||
{
|
||||
Architecture = "x86_64",
|
||||
Format = "ELF",
|
||||
HasDebugSymbols = true
|
||||
},
|
||||
ExtractedAt = DateTimeOffset.UtcNow,
|
||||
ExtractorVersion = "1.0.0"
|
||||
},
|
||||
IssuerId = "test-vendor",
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
private static PatchVerificationContext CreateContext(
|
||||
IEnumerable<string> cveIds,
|
||||
Dictionary<string, string>? binaryPaths = null,
|
||||
PatchVerificationOptions? options = null)
|
||||
{
|
||||
return new PatchVerificationContext
|
||||
{
|
||||
ScanId = "test-scan-001",
|
||||
TenantId = "test-tenant",
|
||||
ImageDigest = "sha256:abc123",
|
||||
ArtifactPurl = "pkg:oci/test@sha256:abc123",
|
||||
CveIds = cveIds.ToList(),
|
||||
BinaryPaths = binaryPaths ?? new Dictionary<string, string>(),
|
||||
Options = options ?? new PatchVerificationOptions()
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user