save dev progress
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user