|
|
|
|
@@ -0,0 +1,486 @@
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// OpaEvidenceModels.cs
|
|
|
|
|
// Sprint: SPRINT_0129_001_Policy_supply_chain_evidence_input
|
|
|
|
|
// Task: TASK-001 - Enrich OPA Policy Input with Supply Chain Evidence
|
|
|
|
|
// Description: Model types for supply chain evidence passed to OPA policies
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
|
using System.Text.Json.Serialization;
|
|
|
|
|
|
|
|
|
|
namespace StellaOps.Policy.Gates.Opa;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Artifact descriptor for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaArtifactDescriptor
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Artifact digest (e.g., "sha256:abc123...").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("digest")]
|
|
|
|
|
public required string Digest { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Media type of the artifact (e.g., "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("mediaType")]
|
|
|
|
|
public string? MediaType { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Optional artifact reference (e.g., "registry.example.com/app:v1.0.0").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("reference")]
|
|
|
|
|
public string? Reference { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Optional repository name.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("repository")]
|
|
|
|
|
public string? Repository { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Optional tag.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("tag")]
|
|
|
|
|
public string? Tag { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SBOM reference for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaSbomReference
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SBOM content hash (SHA-256, lowercase hex).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("digest")]
|
|
|
|
|
public required string Digest { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SBOM format (e.g., "cyclonedx-1.7", "spdx-3.0.1").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("format")]
|
|
|
|
|
public required string Format { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Spec version within the format.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("specVersion")]
|
|
|
|
|
public string? SpecVersion { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Number of components in the SBOM.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("componentCount")]
|
|
|
|
|
public int? ComponentCount { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// When the SBOM was generated (ISO 8601).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("generatedAt")]
|
|
|
|
|
public string? GeneratedAt { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Optional inline SBOM content (JSON object).
|
|
|
|
|
/// Only included when explicitly requested for deep policy inspection.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("content")]
|
|
|
|
|
public object? Content { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attestation reference for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaAttestationReference
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attestation bundle digest (SHA-256 of DSSE envelope).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("digest")]
|
|
|
|
|
public required string Digest { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Predicate type URI (e.g., "https://slsa.dev/provenance/v1").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("predicateType")]
|
|
|
|
|
public required string PredicateType { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subject digests this attestation covers.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("subjects")]
|
|
|
|
|
public IReadOnlyList<string>? Subjects { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Key ID used to sign this attestation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("keyId")]
|
|
|
|
|
public string? KeyId { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether the signature has been verified.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("signatureVerified")]
|
|
|
|
|
public bool? SignatureVerified { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Rekor log index if submitted to transparency log.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("rekorLogIndex")]
|
|
|
|
|
public long? RekorLogIndex { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// When the attestation was created (ISO 8601).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("createdAt")]
|
|
|
|
|
public string? CreatedAt { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Optional inline DSSE envelope (for deep policy inspection).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("envelope")]
|
|
|
|
|
public OpaAttestationEnvelope? Envelope { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Optional inline in-toto statement (for deep policy inspection).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("statement")]
|
|
|
|
|
public OpaAttestationStatement? Statement { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// DSSE envelope structure for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaAttestationEnvelope
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Payload type (e.g., "application/vnd.in-toto+json").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("payloadType")]
|
|
|
|
|
public required string PayloadType { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Base64-encoded payload.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("payload")]
|
|
|
|
|
public required string Payload { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Signatures on the envelope.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("signatures")]
|
|
|
|
|
public required IReadOnlyList<OpaAttestationSignature> Signatures { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// DSSE signature for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaAttestationSignature
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Key ID for signature verification.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("keyid")]
|
|
|
|
|
public string? KeyId { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Base64-encoded signature.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("sig")]
|
|
|
|
|
public required string Sig { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// In-toto statement structure for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaAttestationStatement
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Statement type (should be "https://in-toto.io/Statement/v1").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("_type")]
|
|
|
|
|
public required string Type { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subjects being attested.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("subject")]
|
|
|
|
|
public required IReadOnlyList<OpaAttestationSubject> Subject { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Predicate type URI.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("predicateType")]
|
|
|
|
|
public required string PredicateType { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Predicate content (type depends on predicateType).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("predicate")]
|
|
|
|
|
public required object Predicate { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attestation subject for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaAttestationSubject
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subject name (e.g., artifact reference).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("name")]
|
|
|
|
|
public required string Name { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subject digests.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("digest")]
|
|
|
|
|
public required IReadOnlyDictionary<string, string> Digest { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Rekor receipt for OPA input (simplified view).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaRekorReceipt
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Log ID identifying the Rekor instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("logId")]
|
|
|
|
|
public required string LogId { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Entry UUID.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("uuid")]
|
|
|
|
|
public required string Uuid { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Log index (position in the log).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("logIndex")]
|
|
|
|
|
public required long LogIndex { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Unix timestamp when entry was integrated.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("integratedTime")]
|
|
|
|
|
public required long IntegratedTime { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Entry kind (e.g., "dsse", "intoto").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("entryKind")]
|
|
|
|
|
public string? EntryKind { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Inclusion proof data.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("inclusionProof")]
|
|
|
|
|
public OpaRekorInclusionProof? InclusionProof { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether the receipt has been verified.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("verified")]
|
|
|
|
|
public bool? Verified { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Rekor inclusion proof for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaRekorInclusionProof
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tree size at time of proof.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("treeSize")]
|
|
|
|
|
public required long TreeSize { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Root hash (lowercase hex).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("rootHash")]
|
|
|
|
|
public required string RootHash { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Leaf hash (lowercase hex).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("leafHash")]
|
|
|
|
|
public required string LeafHash { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Proof hashes from leaf to root.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("hashes")]
|
|
|
|
|
public required IReadOnlyList<string> Hashes { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// VEX merge decision for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaVexMergeDecision
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Algorithm used for merging (e.g., "trust-weighted-lattice-v1").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("algorithm")]
|
|
|
|
|
public required string Algorithm { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Input VEX documents that were merged.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("inputs")]
|
|
|
|
|
public required IReadOnlyList<OpaVexMergeInput> Inputs { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Per-vulnerability decisions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("decisions")]
|
|
|
|
|
public required IReadOnlyList<OpaVexDecision> Decisions { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether there were conflicts during merge.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("hadConflicts")]
|
|
|
|
|
public bool HadConflicts { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Merge digest for integrity verification.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("digest")]
|
|
|
|
|
public string? Digest { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// VEX merge input source for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaVexMergeInput
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Source identifier (e.g., issuer name or URL).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("source")]
|
|
|
|
|
public required string Source { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Document digest for integrity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("digest")]
|
|
|
|
|
public required string Digest { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Trust tier of this source.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("trustTier")]
|
|
|
|
|
public string? TrustTier { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Trust weight (0.0-1.0).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("trustWeight")]
|
|
|
|
|
public double? TrustWeight { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Per-vulnerability VEX decision for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaVexDecision
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Vulnerability ID (e.g., "CVE-2024-1234").
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("vuln")]
|
|
|
|
|
public required string Vuln { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Resolved status.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("status")]
|
|
|
|
|
public required string Status { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Justification for status.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("justification")]
|
|
|
|
|
public string? Justification { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Impact statement if not_affected.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("impactStatement")]
|
|
|
|
|
public string? ImpactStatement { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Indices into inputs[] array showing which sources contributed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("sources")]
|
|
|
|
|
public IReadOnlyList<int>? Sources { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Complete supply chain evidence bundle for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaSupplyChainEvidence
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Artifact being evaluated.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("artifact")]
|
|
|
|
|
public OpaArtifactDescriptor? Artifact { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SBOM reference (and optionally content).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("sbom")]
|
|
|
|
|
public OpaSbomReference? Sbom { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attestations covering the artifact.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("attestations")]
|
|
|
|
|
public IReadOnlyList<OpaAttestationReference>? Attestations { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Transparency log receipts.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("transparency")]
|
|
|
|
|
public OpaTransparencyEvidence? Transparency { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// VEX data and merge decisions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("vex")]
|
|
|
|
|
public OpaVexEvidence? Vex { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Transparency log evidence for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaTransparencyEvidence
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Rekor receipts.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("rekor")]
|
|
|
|
|
public IReadOnlyList<OpaRekorReceipt>? Rekor { get; init; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// VEX evidence for OPA input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed record OpaVexEvidence
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Raw OpenVEX document (if available).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("openvex")]
|
|
|
|
|
public object? OpenVex { get; init; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Merge decision result.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[JsonPropertyName("mergeDecision")]
|
|
|
|
|
public OpaVexMergeDecision? MergeDecision { get; init; }
|
|
|
|
|
}
|