feat: add Attestation Chain and Triage Evidence API clients and models
- Implemented Attestation Chain API client with methods for verifying, fetching, and managing attestation chains. - Created models for Attestation Chain, including DSSE envelope structures and verification results. - Developed Triage Evidence API client for fetching finding evidence, including methods for evidence retrieval by CVE and component. - Added models for Triage Evidence, encapsulating evidence responses, entry points, boundary proofs, and VEX evidence. - Introduced mock implementations for both API clients to facilitate testing and development.
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StellaOps.Scanner.Triage.Entities;
|
||||
|
||||
namespace StellaOps.Scanner.Triage;
|
||||
|
||||
/// <summary>
|
||||
/// Entity Framework Core DbContext for the Triage schema.
|
||||
/// </summary>
|
||||
public sealed class TriageDbContext : DbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TriageDbContext"/> class.
|
||||
/// </summary>
|
||||
public TriageDbContext(DbContextOptions<TriageDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triage findings (cases).
|
||||
/// </summary>
|
||||
public DbSet<TriageFinding> Findings => Set<TriageFinding>();
|
||||
|
||||
/// <summary>
|
||||
/// Effective VEX records.
|
||||
/// </summary>
|
||||
public DbSet<TriageEffectiveVex> EffectiveVex => Set<TriageEffectiveVex>();
|
||||
|
||||
/// <summary>
|
||||
/// Reachability analysis results.
|
||||
/// </summary>
|
||||
public DbSet<TriageReachabilityResult> ReachabilityResults => Set<TriageReachabilityResult>();
|
||||
|
||||
/// <summary>
|
||||
/// Risk/lattice evaluation results.
|
||||
/// </summary>
|
||||
public DbSet<TriageRiskResult> RiskResults => Set<TriageRiskResult>();
|
||||
|
||||
/// <summary>
|
||||
/// Triage decisions.
|
||||
/// </summary>
|
||||
public DbSet<TriageDecision> Decisions => Set<TriageDecision>();
|
||||
|
||||
/// <summary>
|
||||
/// Evidence artifacts.
|
||||
/// </summary>
|
||||
public DbSet<TriageEvidenceArtifact> EvidenceArtifacts => Set<TriageEvidenceArtifact>();
|
||||
|
||||
/// <summary>
|
||||
/// Snapshots for Smart-Diff.
|
||||
/// </summary>
|
||||
public DbSet<TriageSnapshot> Snapshots => Set<TriageSnapshot>();
|
||||
|
||||
/// <summary>
|
||||
/// Current case view (read-only).
|
||||
/// </summary>
|
||||
public DbSet<TriageCaseCurrent> CurrentCases => Set<TriageCaseCurrent>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// Configure PostgreSQL enums
|
||||
modelBuilder.HasPostgresEnum<TriageLane>("triage_lane");
|
||||
modelBuilder.HasPostgresEnum<TriageVerdict>("triage_verdict");
|
||||
modelBuilder.HasPostgresEnum<TriageReachability>("triage_reachability");
|
||||
modelBuilder.HasPostgresEnum<TriageVexStatus>("triage_vex_status");
|
||||
modelBuilder.HasPostgresEnum<TriageDecisionKind>("triage_decision_kind");
|
||||
modelBuilder.HasPostgresEnum<TriageSnapshotTrigger>("triage_snapshot_trigger");
|
||||
modelBuilder.HasPostgresEnum<TriageEvidenceType>("triage_evidence_type");
|
||||
|
||||
// Configure TriageFinding
|
||||
modelBuilder.Entity<TriageFinding>(entity =>
|
||||
{
|
||||
entity.ToTable("triage_finding");
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.HasIndex(e => e.LastSeenAt)
|
||||
.IsDescending()
|
||||
.HasDatabaseName("ix_triage_finding_last_seen");
|
||||
|
||||
entity.HasIndex(e => e.AssetLabel)
|
||||
.HasDatabaseName("ix_triage_finding_asset_label");
|
||||
|
||||
entity.HasIndex(e => e.Purl)
|
||||
.HasDatabaseName("ix_triage_finding_purl");
|
||||
|
||||
entity.HasIndex(e => e.CveId)
|
||||
.HasDatabaseName("ix_triage_finding_cve");
|
||||
|
||||
entity.HasIndex(e => new { e.AssetId, e.EnvironmentId, e.Purl, e.CveId, e.RuleId })
|
||||
.IsUnique();
|
||||
});
|
||||
|
||||
// Configure TriageEffectiveVex
|
||||
modelBuilder.Entity<TriageEffectiveVex>(entity =>
|
||||
{
|
||||
entity.ToTable("triage_effective_vex");
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.CollectedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_effective_vex_finding");
|
||||
|
||||
entity.HasOne(e => e.Finding)
|
||||
.WithMany(f => f.EffectiveVexRecords)
|
||||
.HasForeignKey(e => e.FindingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure TriageReachabilityResult
|
||||
modelBuilder.Entity<TriageReachabilityResult>(entity =>
|
||||
{
|
||||
entity.ToTable("triage_reachability_result");
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.ComputedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_reachability_finding");
|
||||
|
||||
entity.HasOne(e => e.Finding)
|
||||
.WithMany(f => f.ReachabilityResults)
|
||||
.HasForeignKey(e => e.FindingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure TriageRiskResult
|
||||
modelBuilder.Entity<TriageRiskResult>(entity =>
|
||||
{
|
||||
entity.ToTable("triage_risk_result");
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.ComputedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_risk_finding");
|
||||
|
||||
entity.HasIndex(e => new { e.Lane, e.ComputedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_risk_lane");
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.PolicyId, e.PolicyVersion, e.InputsHash })
|
||||
.IsUnique();
|
||||
|
||||
entity.HasOne(e => e.Finding)
|
||||
.WithMany(f => f.RiskResults)
|
||||
.HasForeignKey(e => e.FindingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure TriageDecision
|
||||
modelBuilder.Entity<TriageDecision>(entity =>
|
||||
{
|
||||
entity.ToTable("triage_decision");
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.CreatedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_decision_finding");
|
||||
|
||||
entity.HasIndex(e => new { e.Kind, e.CreatedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_decision_kind");
|
||||
|
||||
entity.HasIndex(e => e.FindingId)
|
||||
.HasFilter("revoked_at IS NULL")
|
||||
.HasDatabaseName("ix_triage_decision_active");
|
||||
|
||||
entity.HasOne(e => e.Finding)
|
||||
.WithMany(f => f.Decisions)
|
||||
.HasForeignKey(e => e.FindingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure TriageEvidenceArtifact
|
||||
modelBuilder.Entity<TriageEvidenceArtifact>(entity =>
|
||||
{
|
||||
entity.ToTable("triage_evidence_artifact");
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.CreatedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_evidence_finding");
|
||||
|
||||
entity.HasIndex(e => new { e.Type, e.CreatedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_evidence_type");
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.Type, e.ContentHash })
|
||||
.IsUnique();
|
||||
|
||||
entity.HasOne(e => e.Finding)
|
||||
.WithMany(f => f.EvidenceArtifacts)
|
||||
.HasForeignKey(e => e.FindingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure TriageSnapshot
|
||||
modelBuilder.Entity<TriageSnapshot>(entity =>
|
||||
{
|
||||
entity.ToTable("triage_snapshot");
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.CreatedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_snapshot_finding");
|
||||
|
||||
entity.HasIndex(e => new { e.Trigger, e.CreatedAt })
|
||||
.IsDescending(false, true)
|
||||
.HasDatabaseName("ix_triage_snapshot_trigger");
|
||||
|
||||
entity.HasIndex(e => new { e.FindingId, e.ToInputsHash, e.CreatedAt })
|
||||
.IsUnique();
|
||||
|
||||
entity.HasOne(e => e.Finding)
|
||||
.WithMany(f => f.Snapshots)
|
||||
.HasForeignKey(e => e.FindingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Configure the read-only view
|
||||
modelBuilder.Entity<TriageCaseCurrent>(entity =>
|
||||
{
|
||||
entity.ToView("v_triage_case_current");
|
||||
entity.HasNoKey();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user