sprints work
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user