feat: add Attestation Chain and Triage Evidence API clients and models

- 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.
This commit is contained in:
master
2025-12-18 13:15:13 +02:00
parent 7d5250238c
commit 00d2c99af9
118 changed files with 13463 additions and 151 deletions

View File

@@ -0,0 +1,118 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Signals.Models;
using StellaOps.Signals.Persistence;
namespace StellaOps.Signals.Services;
/// <summary>
/// Synchronizes canonical callgraph documents to relational tables.
/// </summary>
internal sealed class CallGraphSyncService : ICallGraphSyncService
{
private readonly ICallGraphProjectionRepository _projectionRepository;
private readonly ILogger<CallGraphSyncService> _logger;
private readonly TimeProvider _timeProvider;
public CallGraphSyncService(
ICallGraphProjectionRepository projectionRepository,
TimeProvider timeProvider,
ILogger<CallGraphSyncService> logger)
{
_projectionRepository = projectionRepository ?? throw new ArgumentNullException(nameof(projectionRepository));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public async Task<CallGraphSyncResult> SyncAsync(
Guid scanId,
string artifactDigest,
CallgraphDocument document,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(document);
ArgumentException.ThrowIfNullOrWhiteSpace(artifactDigest);
var stopwatch = Stopwatch.StartNew();
_logger.LogInformation(
"Starting callgraph projection for scan {ScanId}, artifact {ArtifactDigest}, nodes={NodeCount}, edges={EdgeCount}",
scanId, artifactDigest, document.Nodes.Count, document.Edges.Count);
try
{
// Step 1: Upsert scan record
await _projectionRepository.UpsertScanAsync(
scanId,
artifactDigest,
document.GraphHash,
cancellationToken: cancellationToken).ConfigureAwait(false);
// Step 2: Project nodes in stable order
var nodesProjected = await _projectionRepository.UpsertNodesAsync(
scanId,
document.Nodes,
cancellationToken).ConfigureAwait(false);
// Step 3: Project edges in stable order
var edgesProjected = await _projectionRepository.UpsertEdgesAsync(
scanId,
document.Edges,
cancellationToken).ConfigureAwait(false);
// Step 4: Project entrypoints in stable order
var entrypointsProjected = 0;
if (document.Entrypoints is { Count: > 0 })
{
entrypointsProjected = await _projectionRepository.UpsertEntrypointsAsync(
scanId,
document.Entrypoints,
cancellationToken).ConfigureAwait(false);
}
// Step 5: Mark scan as completed
await _projectionRepository.CompleteScanAsync(scanId, cancellationToken).ConfigureAwait(false);
stopwatch.Stop();
_logger.LogInformation(
"Completed callgraph projection for scan {ScanId}: nodes={NodesProjected}, edges={EdgesProjected}, entrypoints={EntrypointsProjected}, duration={DurationMs}ms",
scanId, nodesProjected, edgesProjected, entrypointsProjected, stopwatch.ElapsedMilliseconds);
return new CallGraphSyncResult(
ScanId: scanId,
NodesProjected: nodesProjected,
EdgesProjected: edgesProjected,
EntrypointsProjected: entrypointsProjected,
WasUpdated: nodesProjected > 0 || edgesProjected > 0,
DurationMs: stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(
ex,
"Failed callgraph projection for scan {ScanId} after {DurationMs}ms: {ErrorMessage}",
scanId, stopwatch.ElapsedMilliseconds, ex.Message);
await _projectionRepository.FailScanAsync(scanId, ex.Message, cancellationToken).ConfigureAwait(false);
throw;
}
}
/// <inheritdoc />
public async Task DeleteByScanAsync(Guid scanId, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Deleting callgraph projection for scan {ScanId}", scanId);
await _projectionRepository.DeleteScanAsync(scanId, cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Deleted callgraph projection for scan {ScanId}", scanId);
}
}