save dev progress

This commit is contained in:
StellaOps Bot
2025-12-26 00:32:35 +02:00
parent aa70af062e
commit ed3079543c
142 changed files with 23771 additions and 232 deletions

View File

@@ -0,0 +1,192 @@
// -----------------------------------------------------------------------------
// GraphRootIntegration.cs
// Sprint: SPRINT_8100_0012_0003_graph_root_attestation
// Task: GROOT-8100-013
// Description: Implementation bridging Scanner RichGraph to GraphRootAttestor.
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Attestor.Envelope;
using StellaOps.Attestor.GraphRoot;
using StellaOps.Attestor.GraphRoot.Models;
namespace StellaOps.Scanner.Reachability.Attestation;
/// <summary>
/// Implementation of GraphRoot attestation integration for Scanner.
/// Extracts node/edge IDs from RichGraph and invokes IGraphRootAttestor.
/// </summary>
public sealed class GraphRootIntegration : IGraphRootIntegration
{
private readonly IGraphRootAttestor _attestor;
private readonly GraphRootIntegrationOptions _options;
private readonly ILogger<GraphRootIntegration> _logger;
public GraphRootIntegration(
IGraphRootAttestor attestor,
IOptions<GraphRootIntegrationOptions> options,
ILogger<GraphRootIntegration> logger)
{
_attestor = attestor ?? throw new ArgumentNullException(nameof(attestor));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<GraphRootIntegrationResult?> AttestAsync(
GraphRootIntegrationInput input,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(input);
if (!_options.Enabled)
{
_logger.LogDebug("GraphRoot attestation is disabled");
return null;
}
var richGraph = input.RichGraph;
// Extract node and edge IDs from RichGraph
var nodeIds = ExtractNodeIds(richGraph);
var edgeIds = ExtractEdgeIds(richGraph);
_logger.LogDebug(
"Creating GraphRoot attestation for RichGraph with {NodeCount} nodes and {EdgeCount} edges",
nodeIds.Count,
edgeIds.Count);
// Build attestation request
var request = new GraphRootAttestationRequest
{
GraphType = GraphType.ReachabilityGraph,
NodeIds = nodeIds,
EdgeIds = edgeIds,
PolicyDigest = input.PolicyDigest,
FeedsDigest = input.FeedsDigest,
ToolchainDigest = input.ToolchainDigest,
ParamsDigest = input.ParamsDigest,
ArtifactDigest = input.SubjectDigest,
EvidenceIds = ExtractEvidenceIds(richGraph),
PublishToRekor = _options.PublishToRekor,
SigningKeyId = _options.SigningKeyId
};
try
{
var result = await _attestor.AttestAsync(request, cancellationToken).ConfigureAwait(false);
// Generate deterministic attestation ID from root hash
var attestationId = ComputeAttestationId(result.RootHash, input.GraphHash);
// Serialize envelope to JSON
var serializationResult = DsseEnvelopeSerializer.Serialize(result.Envelope, new DsseEnvelopeSerializationOptions
{
EmitCompactJson = true,
EmitExpandedJson = false
});
_logger.LogInformation(
"Created GraphRoot attestation: root={RootHash}, id={AttestationId}, nodes={NodeCount}, edges={EdgeCount}",
result.RootHash,
attestationId,
result.NodeCount,
result.EdgeCount);
return new GraphRootIntegrationResult(
RootHash: result.RootHash,
AttestationId: attestationId,
EnvelopeBytes: serializationResult.CompactJson ?? [],
RekorLogIndex: ParseRekorLogIndex(result.RekorLogIndex));
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create GraphRoot attestation");
throw;
}
}
private static IReadOnlyList<string> ExtractNodeIds(RichGraph graph)
{
// Extract node IDs from RichGraph nodes
// Each node has an Id field that is deterministic
return graph.Nodes
.Where(n => !string.IsNullOrEmpty(n.Id))
.Select(n => n.Id)
.Distinct()
.OrderBy(id => id, StringComparer.Ordinal)
.ToList();
}
private static IReadOnlyList<string> ExtractEdgeIds(RichGraph graph)
{
// Extract edge IDs from RichGraph edges
// Edge ID is deterministic from From->To->Kind
return graph.Edges
.Select(e => $"{e.From}->{e.To}:{e.Kind}")
.Distinct()
.OrderBy(id => id, StringComparer.Ordinal)
.ToList();
}
private static IReadOnlyList<string> ExtractEvidenceIds(RichGraph graph)
{
// Collect all evidence IDs from nodes and edges
var evidenceIds = new HashSet<string>(StringComparer.Ordinal);
foreach (var node in graph.Nodes)
{
if (node.Evidence is not null)
{
foreach (var evidence in node.Evidence)
{
if (!string.IsNullOrEmpty(evidence))
{
evidenceIds.Add(evidence);
}
}
}
}
foreach (var edge in graph.Edges)
{
if (edge.Evidence is not null)
{
foreach (var evidence in edge.Evidence)
{
if (!string.IsNullOrEmpty(evidence))
{
evidenceIds.Add(evidence);
}
}
}
}
return evidenceIds.OrderBy(id => id, StringComparer.Ordinal).ToList();
}
private static string ComputeAttestationId(string rootHash, string graphHash)
{
// Combine root hash and graph hash for unique attestation ID
var combined = $"{rootHash}:{graphHash}";
var bytes = Encoding.UTF8.GetBytes(combined);
var hash = SHA256.HashData(bytes);
return $"groot:{Convert.ToHexString(hash[..16]).ToLowerInvariant()}";
}
private static long? ParseRekorLogIndex(string? rekorLogIndex)
{
if (string.IsNullOrEmpty(rekorLogIndex))
{
return null;
}
return long.TryParse(rekorLogIndex, out var index) ? index : null;
}
}

View File

@@ -0,0 +1,46 @@
// -----------------------------------------------------------------------------
// GraphRootIntegrationServiceCollectionExtensions.cs
// Sprint: SPRINT_8100_0012_0003_graph_root_attestation
// Task: GROOT-8100-013
// Description: DI registration for GraphRoot integration in Scanner.
// -----------------------------------------------------------------------------
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Attestor.GraphRoot;
namespace StellaOps.Scanner.Reachability.Attestation;
/// <summary>
/// Extension methods for registering GraphRoot integration services.
/// </summary>
public static class GraphRootIntegrationServiceCollectionExtensions
{
/// <summary>
/// Adds GraphRoot attestation integration services to the service collection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddGraphRootIntegration(
this IServiceCollection services,
Action<GraphRootIntegrationOptions>? configure = null)
{
ArgumentNullException.ThrowIfNull(services);
// Register GraphRootAttestor dependencies if not already registered
services.TryAddSingleton<IMerkleRootComputer, Sha256MerkleRootComputer>();
// Register the integration service
services.TryAddSingleton<IGraphRootIntegration, GraphRootIntegration>();
// Configure options
if (configure is not null)
{
services.Configure(configure);
}
return services;
}
}

View File

@@ -0,0 +1,81 @@
// -----------------------------------------------------------------------------
// IGraphRootIntegration.cs
// Sprint: SPRINT_8100_0012_0003_graph_root_attestation
// Task: GROOT-8100-013
// Description: Integration service for GraphRootAttestor in Scanner pipeline.
// -----------------------------------------------------------------------------
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Attestor.GraphRoot.Models;
namespace StellaOps.Scanner.Reachability.Attestation;
/// <summary>
/// Options for GraphRoot attestation integration.
/// </summary>
public sealed class GraphRootIntegrationOptions
{
/// <summary>
/// Whether GraphRoot attestation is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Whether to publish to Rekor transparency log.
/// </summary>
public bool PublishToRekor { get; set; } = false;
/// <summary>
/// Signing key ID to use for attestations.
/// </summary>
public string? SigningKeyId { get; set; }
}
/// <summary>
/// Result of GraphRoot attestation integration.
/// </summary>
/// <param name="RootHash">Merkle root hash of the graph.</param>
/// <param name="AttestationId">Unique attestation identifier.</param>
/// <param name="EnvelopeBytes">DSSE envelope bytes.</param>
/// <param name="RekorLogIndex">Rekor log index if published.</param>
public sealed record GraphRootIntegrationResult(
string RootHash,
string AttestationId,
byte[] EnvelopeBytes,
long? RekorLogIndex);
/// <summary>
/// Input for GraphRoot attestation from RichGraph.
/// </summary>
/// <param name="RichGraph">The rich graph to attest.</param>
/// <param name="GraphHash">Content-addressed hash of the graph.</param>
/// <param name="SubjectDigest">Subject artifact digest (container image, etc.).</param>
/// <param name="PolicyDigest">Policy bundle digest used during computation.</param>
/// <param name="FeedsDigest">Feed snapshot digest.</param>
/// <param name="ToolchainDigest">Toolchain version digest.</param>
/// <param name="ParamsDigest">Evaluation parameters digest.</param>
public sealed record GraphRootIntegrationInput(
RichGraph RichGraph,
string GraphHash,
string SubjectDigest,
string PolicyDigest,
string FeedsDigest,
string ToolchainDigest,
string ParamsDigest);
/// <summary>
/// Integration service that bridges Scanner RichGraph to GraphRootAttestor.
/// </summary>
public interface IGraphRootIntegration
{
/// <summary>
/// Creates a GraphRoot attestation from a RichGraph.
/// </summary>
/// <param name="input">GraphRoot input derived from RichGraph.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>GraphRoot attestation result.</returns>
Task<GraphRootIntegrationResult?> AttestAsync(
GraphRootIntegrationInput input,
CancellationToken cancellationToken = default);
}

View File

@@ -20,6 +20,7 @@
<ProjectReference Include="..\..\..\Attestor\StellaOps.Attestor\StellaOps.Attestor.Core\StellaOps.Attestor.Core.csproj" />
<ProjectReference Include="..\..\..\Attestor\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj" />
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj" />
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.GraphRoot\StellaOps.Attestor.GraphRoot.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Replay.Core\StellaOps.Replay.Core.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
</ItemGroup>