sprints work

This commit is contained in:
StellaOps Bot
2025-12-25 12:19:12 +02:00
parent 223843f1d1
commit 2a06f780cf
224 changed files with 41796 additions and 1515 deletions

View File

@@ -0,0 +1,148 @@
// -----------------------------------------------------------------------------
// ProofSpineBuilderExtensions.cs
// Sprint: SPRINT_8100_0012_0003 - Graph Root Attestation Service
// Task: GROOT-8100-012 - Extend ProofSpineBuilder with BuildWithAttestationAsync()
// Description: Extensions for ProofSpineBuilder to emit graph root attestations
// -----------------------------------------------------------------------------
using StellaOps.Attestor.GraphRoot;
using StellaOps.Attestor.GraphRoot.Models;
using StellaOps.Replay.Core;
using AttestorEnvelope = StellaOps.Attestor.Envelope.DsseEnvelope;
using AttestorSignature = StellaOps.Attestor.Envelope.DsseSignature;
namespace StellaOps.Scanner.ProofSpine;
/// <summary>
/// Extension methods for <see cref="ProofSpineBuilder"/> to support graph root attestation.
/// </summary>
public static class ProofSpineBuilderExtensions
{
/// <summary>
/// Builds the proof spine and creates a graph root attestation.
/// </summary>
/// <param name="builder">The proof spine builder.</param>
/// <param name="attestor">The graph root attestor service.</param>
/// <param name="request">The attestation request configuration.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A proof spine with attached graph root attestation.</returns>
public static async Task<ProofSpine> BuildWithAttestationAsync(
this ProofSpineBuilder builder,
IGraphRootAttestor attestor,
ProofSpineAttestationRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(attestor);
ArgumentNullException.ThrowIfNull(request);
// Build the spine first
var spine = await builder.BuildAsync(cancellationToken).ConfigureAwait(false);
// Create attestation request from spine data
var attestRequest = new GraphRootAttestationRequest
{
GraphType = GraphType.ProofSpine,
NodeIds = spine.Segments.Select(s => s.SegmentId).ToList(),
EdgeIds = BuildEdgeIds(spine.Segments),
PolicyDigest = request.PolicyDigest,
FeedsDigest = request.FeedsDigest,
ToolchainDigest = request.ToolchainDigest,
ParamsDigest = request.ParamsDigest,
ArtifactDigest = request.ArtifactDigest ?? spine.ArtifactId,
EvidenceIds = request.EvidenceIds,
PublishToRekor = request.PublishToRekor,
SigningKeyId = request.SigningKeyId
};
// Create the attestation
var attestResult = await attestor.AttestAsync(attestRequest, cancellationToken)
.ConfigureAwait(false);
// Convert Attestor envelope to Replay.Core envelope
var replayEnvelope = ConvertToReplayEnvelope(attestResult.Envelope);
// Return spine with attestation attached
return spine with
{
GraphRootAttestationId = attestResult.RootHash,
GraphRootEnvelope = replayEnvelope
};
}
/// <summary>
/// Converts an Attestor.Envelope.DsseEnvelope to Replay.Core.DsseEnvelope.
/// </summary>
private static DsseEnvelope ConvertToReplayEnvelope(AttestorEnvelope envelope)
{
var base64Payload = Convert.ToBase64String(envelope.Payload.Span);
var signatures = envelope.Signatures
.Select(s => new DsseSignature(s.KeyId ?? string.Empty, s.Signature))
.ToList();
return new DsseEnvelope(envelope.PayloadType, base64Payload, signatures);
}
/// <summary>
/// Builds edge IDs from segment chain (each segment links to the previous).
/// </summary>
private static IReadOnlyList<string> BuildEdgeIds(IReadOnlyList<ProofSegment> segments)
{
var edges = new List<string>(segments.Count - 1);
for (var i = 1; i < segments.Count; i++)
{
var prevSegment = segments[i - 1];
var currSegment = segments[i];
edges.Add($"{prevSegment.SegmentId}->{currSegment.SegmentId}");
}
return edges;
}
}
/// <summary>
/// Configuration for proof spine attestation.
/// </summary>
public sealed record ProofSpineAttestationRequest
{
/// <summary>
/// Digest of the policy profile used for evaluation.
/// </summary>
public required string PolicyDigest { get; init; }
/// <summary>
/// Digest of the advisory/vulnerability feeds snapshot.
/// </summary>
public required string FeedsDigest { get; init; }
/// <summary>
/// Digest of the toolchain (scanner, analyzer versions).
/// </summary>
public required string ToolchainDigest { get; init; }
/// <summary>
/// Digest of the evaluation parameters.
/// </summary>
public required string ParamsDigest { get; init; }
/// <summary>
/// Optional: Override artifact digest (defaults to spine's ArtifactId).
/// </summary>
public string? ArtifactDigest { get; init; }
/// <summary>
/// Evidence IDs linked to this proof spine.
/// </summary>
public IReadOnlyList<string> EvidenceIds { get; init; } = [];
/// <summary>
/// Whether to publish the attestation to Rekor transparency log.
/// </summary>
public bool PublishToRekor { get; init; }
/// <summary>
/// Optional: Specific signing key ID to use.
/// </summary>
public string? SigningKeyId { get; init; }
}

View File

@@ -5,6 +5,19 @@ namespace StellaOps.Scanner.ProofSpine;
/// <summary>
/// Represents a complete verifiable decision chain from SBOM to VEX verdict.
/// </summary>
/// <param name="SpineId">Content-addressed ID of this proof spine.</param>
/// <param name="ArtifactId">The artifact (container image, package) this spine evaluates.</param>
/// <param name="VulnerabilityId">The vulnerability ID being evaluated.</param>
/// <param name="PolicyProfileId">The policy profile used for evaluation.</param>
/// <param name="Segments">Ordered list of evidence segments in the proof chain.</param>
/// <param name="Verdict">Final verdict (affected, not_affected, fixed, under_investigation).</param>
/// <param name="VerdictReason">Human-readable explanation of the verdict.</param>
/// <param name="RootHash">Merkle root hash of all segment hashes.</param>
/// <param name="ScanRunId">ID of the scan run that produced this spine.</param>
/// <param name="CreatedAt">When this spine was created.</param>
/// <param name="SupersededBySpineId">If superseded, the ID of the newer spine.</param>
/// <param name="GraphRootAttestationId">Optional: Content-addressed ID of the graph root attestation.</param>
/// <param name="GraphRootEnvelope">Optional: DSSE envelope containing the graph root attestation.</param>
public sealed record ProofSpine(
string SpineId,
string ArtifactId,
@@ -16,7 +29,9 @@ public sealed record ProofSpine(
string RootHash,
string ScanRunId,
DateTimeOffset CreatedAt,
string? SupersededBySpineId);
string? SupersededBySpineId,
string? GraphRootAttestationId = null,
DsseEnvelope? GraphRootEnvelope = null);
/// <summary>
/// A single evidence segment in the proof chain.

View File

@@ -13,5 +13,6 @@
<ItemGroup>
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Replay.Core/StellaOps.Replay.Core.csproj" />
<ProjectReference Include="../../../Attestor/__Libraries/StellaOps.Attestor.GraphRoot/StellaOps.Attestor.GraphRoot.csproj" />
</ItemGroup>
</Project>