- Introduced `VexStatusChipComponent` to display VEX status with color coding and tooltips. - Implemented integration tests for reachability drift detection, covering various scenarios including drift detection, determinism, and error handling. - Enhanced `ScannerToSignalsReachabilityTests` with a null implementation of `ICallGraphSyncService` for better test isolation. - Updated project references to include the new Reachability Drift library.
367 lines
9.2 KiB
C#
367 lines
9.2 KiB
C#
// -----------------------------------------------------------------------------
|
|
// AttestationChain.cs
|
|
// Sprint: SPRINT_3801_0001_0003_chain_verification (CHAIN-002)
|
|
// Description: Models for attestation chain verification.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Scanner.WebService.Contracts;
|
|
|
|
/// <summary>
|
|
/// Represents a chain of attestations for a finding.
|
|
/// </summary>
|
|
public sealed record AttestationChain
|
|
{
|
|
/// <summary>
|
|
/// Content-addressed chain identifier.
|
|
/// </summary>
|
|
[JsonPropertyName("chain_id")]
|
|
public required string ChainId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The scan ID this chain belongs to.
|
|
/// </summary>
|
|
[JsonPropertyName("scan_id")]
|
|
public required string ScanId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The finding ID (e.g., CVE identifier) this chain is for.
|
|
/// </summary>
|
|
[JsonPropertyName("finding_id")]
|
|
public required string FindingId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The root digest (typically the scan/image digest).
|
|
/// </summary>
|
|
[JsonPropertyName("root_digest")]
|
|
public required string RootDigest { get; init; }
|
|
|
|
/// <summary>
|
|
/// The attestations in this chain, ordered from root to leaf.
|
|
/// </summary>
|
|
[JsonPropertyName("attestations")]
|
|
public required ImmutableList<ChainAttestation> Attestations { get; init; }
|
|
|
|
/// <summary>
|
|
/// Whether the entire chain is verified.
|
|
/// </summary>
|
|
[JsonPropertyName("verified")]
|
|
public required bool Verified { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the chain was verified.
|
|
/// </summary>
|
|
[JsonPropertyName("verified_at")]
|
|
public required DateTimeOffset VerifiedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// The chain status.
|
|
/// </summary>
|
|
[JsonPropertyName("chain_status")]
|
|
public required ChainStatus Status { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the earliest attestation in the chain expires.
|
|
/// </summary>
|
|
[JsonPropertyName("expires_at")]
|
|
public DateTimeOffset? ExpiresAt { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a single attestation in the chain.
|
|
/// </summary>
|
|
public sealed record ChainAttestation
|
|
{
|
|
/// <summary>
|
|
/// The type of attestation (e.g., "richgraph", "policy_decision", "human_approval").
|
|
/// </summary>
|
|
[JsonPropertyName("type")]
|
|
public required AttestationType Type { get; init; }
|
|
|
|
/// <summary>
|
|
/// The attestation ID.
|
|
/// </summary>
|
|
[JsonPropertyName("attestation_id")]
|
|
public required string AttestationId { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the attestation was created.
|
|
/// </summary>
|
|
[JsonPropertyName("created_at")]
|
|
public required DateTimeOffset CreatedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the attestation expires.
|
|
/// </summary>
|
|
[JsonPropertyName("expires_at")]
|
|
public required DateTimeOffset ExpiresAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Whether the attestation signature verified.
|
|
/// </summary>
|
|
[JsonPropertyName("verified")]
|
|
public required bool Verified { get; init; }
|
|
|
|
/// <summary>
|
|
/// The verification status of this attestation.
|
|
/// </summary>
|
|
[JsonPropertyName("verification_status")]
|
|
public required AttestationVerificationStatus VerificationStatus { get; init; }
|
|
|
|
/// <summary>
|
|
/// The subject digest this attestation covers.
|
|
/// </summary>
|
|
[JsonPropertyName("subject_digest")]
|
|
public required string SubjectDigest { get; init; }
|
|
|
|
/// <summary>
|
|
/// The predicate type URI.
|
|
/// </summary>
|
|
[JsonPropertyName("predicate_type")]
|
|
public required string PredicateType { get; init; }
|
|
|
|
/// <summary>
|
|
/// Optional error message if verification failed.
|
|
/// </summary>
|
|
[JsonPropertyName("error")]
|
|
public string? Error { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The type of attestation.
|
|
/// </summary>
|
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
public enum AttestationType
|
|
{
|
|
/// <summary>
|
|
/// RichGraph computation attestation.
|
|
/// </summary>
|
|
RichGraph,
|
|
|
|
/// <summary>
|
|
/// Policy decision attestation.
|
|
/// </summary>
|
|
PolicyDecision,
|
|
|
|
/// <summary>
|
|
/// Human approval attestation.
|
|
/// </summary>
|
|
HumanApproval,
|
|
|
|
/// <summary>
|
|
/// SBOM generation attestation.
|
|
/// </summary>
|
|
Sbom,
|
|
|
|
/// <summary>
|
|
/// Vulnerability scan attestation.
|
|
/// </summary>
|
|
VulnerabilityScan
|
|
}
|
|
|
|
/// <summary>
|
|
/// The verification status of an attestation.
|
|
/// </summary>
|
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
public enum AttestationVerificationStatus
|
|
{
|
|
/// <summary>
|
|
/// Verification succeeded.
|
|
/// </summary>
|
|
Valid,
|
|
|
|
/// <summary>
|
|
/// Attestation has expired.
|
|
/// </summary>
|
|
Expired,
|
|
|
|
/// <summary>
|
|
/// Signature verification failed.
|
|
/// </summary>
|
|
InvalidSignature,
|
|
|
|
/// <summary>
|
|
/// Attestation not found.
|
|
/// </summary>
|
|
NotFound,
|
|
|
|
/// <summary>
|
|
/// Chain link broken (digest mismatch).
|
|
/// </summary>
|
|
ChainBroken,
|
|
|
|
/// <summary>
|
|
/// Attestation has been revoked.
|
|
/// </summary>
|
|
Revoked,
|
|
|
|
/// <summary>
|
|
/// Verification pending.
|
|
/// </summary>
|
|
Pending
|
|
}
|
|
|
|
/// <summary>
|
|
/// The overall status of the attestation chain.
|
|
/// </summary>
|
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
public enum ChainStatus
|
|
{
|
|
/// <summary>
|
|
/// All attestations present and valid.
|
|
/// </summary>
|
|
Complete,
|
|
|
|
/// <summary>
|
|
/// Some attestations missing but core valid.
|
|
/// </summary>
|
|
Partial,
|
|
|
|
/// <summary>
|
|
/// One or more attestations past TTL.
|
|
/// </summary>
|
|
Expired,
|
|
|
|
/// <summary>
|
|
/// Signature verification failed.
|
|
/// </summary>
|
|
Invalid,
|
|
|
|
/// <summary>
|
|
/// Chain link missing or digest mismatch.
|
|
/// </summary>
|
|
Broken,
|
|
|
|
/// <summary>
|
|
/// Chain is empty (no attestations).
|
|
/// </summary>
|
|
Empty
|
|
}
|
|
|
|
/// <summary>
|
|
/// Input for chain verification.
|
|
/// </summary>
|
|
public sealed record ChainVerificationInput
|
|
{
|
|
/// <summary>
|
|
/// The scan ID to verify chain for.
|
|
/// </summary>
|
|
public required Domain.ScanId ScanId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The finding ID to verify chain for.
|
|
/// </summary>
|
|
public required string FindingId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The expected root digest.
|
|
/// </summary>
|
|
public required string RootDigest { get; init; }
|
|
|
|
/// <summary>
|
|
/// Optional: specific attestation types to verify.
|
|
/// If null, verifies all available attestations.
|
|
/// </summary>
|
|
public IReadOnlyList<AttestationType>? RequiredTypes { get; init; }
|
|
|
|
/// <summary>
|
|
/// Whether to require human approval in the chain.
|
|
/// </summary>
|
|
public bool RequireHumanApproval { get; init; }
|
|
|
|
/// <summary>
|
|
/// Grace period for expired attestations (default: 0).
|
|
/// </summary>
|
|
public TimeSpan ExpirationGracePeriod { get; init; } = TimeSpan.Zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of chain verification.
|
|
/// </summary>
|
|
public sealed record ChainVerificationResult
|
|
{
|
|
/// <summary>
|
|
/// Whether verification succeeded.
|
|
/// </summary>
|
|
public required bool Success { get; init; }
|
|
|
|
/// <summary>
|
|
/// The verified chain.
|
|
/// </summary>
|
|
public AttestationChain? Chain { get; init; }
|
|
|
|
/// <summary>
|
|
/// Error message if verification failed.
|
|
/// </summary>
|
|
public string? Error { get; init; }
|
|
|
|
/// <summary>
|
|
/// Detailed verification results per attestation.
|
|
/// </summary>
|
|
public IReadOnlyList<AttestationVerificationDetail>? Details { get; init; }
|
|
|
|
/// <summary>
|
|
/// Creates a successful result.
|
|
/// </summary>
|
|
public static ChainVerificationResult Succeeded(
|
|
AttestationChain chain,
|
|
IReadOnlyList<AttestationVerificationDetail>? details = null)
|
|
=> new()
|
|
{
|
|
Success = true,
|
|
Chain = chain,
|
|
Details = details
|
|
};
|
|
|
|
/// <summary>
|
|
/// Creates a failed result.
|
|
/// </summary>
|
|
public static ChainVerificationResult Failed(string error, AttestationChain? chain = null)
|
|
=> new()
|
|
{
|
|
Success = false,
|
|
Chain = chain,
|
|
Error = error
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detailed verification result for a single attestation.
|
|
/// </summary>
|
|
public sealed record AttestationVerificationDetail
|
|
{
|
|
/// <summary>
|
|
/// The attestation type.
|
|
/// </summary>
|
|
public required AttestationType Type { get; init; }
|
|
|
|
/// <summary>
|
|
/// The attestation ID.
|
|
/// </summary>
|
|
public required string AttestationId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The verification status.
|
|
/// </summary>
|
|
public required AttestationVerificationStatus Status { get; init; }
|
|
|
|
/// <summary>
|
|
/// Whether the attestation was verified successfully.
|
|
/// </summary>
|
|
public required bool Verified { get; init; }
|
|
|
|
/// <summary>
|
|
/// Time taken for verification.
|
|
/// </summary>
|
|
public TimeSpan? VerificationTime { get; init; }
|
|
|
|
/// <summary>
|
|
/// Error message if verification failed.
|
|
/// </summary>
|
|
public string? Error { get; init; }
|
|
}
|