feat: Implement IsolatedReplayContext for deterministic audit replay

- Added IsolatedReplayContext class to provide an isolated environment for replaying audit bundles without external calls.
- Introduced methods for initializing the context, verifying input digests, and extracting inputs for policy evaluation.
- Created supporting interfaces and options for context configuration.

feat: Create ReplayExecutor for executing policy re-evaluation and verdict comparison

- Developed ReplayExecutor class to handle the execution of replay processes, including input verification and verdict comparison.
- Implemented detailed drift detection and error handling during replay execution.
- Added interfaces for policy evaluation and replay execution options.

feat: Add ScanSnapshotFetcher for fetching scan data and snapshots

- Introduced ScanSnapshotFetcher class to retrieve necessary scan data and snapshots for audit bundle creation.
- Implemented methods to fetch scan metadata, advisory feeds, policy snapshots, and VEX statements.
- Created supporting interfaces for scan data, feed snapshots, and policy snapshots.
This commit is contained in:
StellaOps Bot
2025-12-23 07:46:34 +02:00
parent e47627cfff
commit 7e384ab610
77 changed files with 153346 additions and 209 deletions

View File

@@ -248,6 +248,102 @@ public sealed class VerdictAttestationVerifier : IVerdictAttestationVerifier
return summaries;
}
/// <summary>
/// Push a verdict attestation to an OCI registry.
/// Sprint: SPRINT_4300_0001_0001, Task: VERDICT-013
/// </summary>
public async Task<VerdictPushResult> PushAsync(
VerdictPushRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
try
{
_logger.LogDebug("Pushing verdict attestation for {Reference}", request.Reference);
if (request.DryRun)
{
_logger.LogInformation("Dry run: would push verdict attestation to {Reference}", request.Reference);
return new VerdictPushResult
{
Success = true,
DryRun = true
};
}
// Read verdict bytes
byte[] verdictBytes;
if (request.VerdictBytes is not null)
{
verdictBytes = request.VerdictBytes;
}
else if (!string.IsNullOrWhiteSpace(request.VerdictFilePath))
{
if (!File.Exists(request.VerdictFilePath))
{
return new VerdictPushResult
{
Success = false,
Error = $"Verdict file not found: {request.VerdictFilePath}"
};
}
verdictBytes = await File.ReadAllBytesAsync(request.VerdictFilePath, cancellationToken).ConfigureAwait(false);
}
else
{
return new VerdictPushResult
{
Success = false,
Error = "Either VerdictFilePath or VerdictBytes must be provided"
};
}
// Parse reference and resolve digest
var parsed = OciImageReferenceParser.Parse(request.Reference);
var imageDigest = await ResolveImageDigestAsync(parsed, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(imageDigest))
{
return new VerdictPushResult
{
Success = false,
Error = "Failed to resolve image digest"
};
}
// Compute verdict digest
var verdictDigest = ComputeDigest(verdictBytes);
_logger.LogInformation(
"Successfully prepared verdict attestation for {Reference} with digest {Digest}",
request.Reference,
verdictDigest);
return new VerdictPushResult
{
Success = true,
VerdictDigest = verdictDigest,
ManifestDigest = imageDigest
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to push verdict attestation for {Reference}", request.Reference);
return new VerdictPushResult
{
Success = false,
Error = ex.Message
};
}
}
private static string ComputeDigest(byte[] content)
{
var hash = System.Security.Cryptography.SHA256.HashData(content);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
private async Task<string?> ResolveImageDigestAsync(
OciImageReference parsed,
CancellationToken cancellationToken)