Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
469 lines
13 KiB
C#
469 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Cli.Services.Models;
|
|
|
|
// CLI-PROMO-70-001: Promotion attestation models
|
|
|
|
/// <summary>
|
|
/// Request for assembling a promotion attestation.
|
|
/// </summary>
|
|
internal sealed class PromotionAssembleRequest
|
|
{
|
|
[JsonPropertyName("tenant")]
|
|
public string Tenant { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("image")]
|
|
public string Image { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("sbomPath")]
|
|
public string? SbomPath { get; init; }
|
|
|
|
[JsonPropertyName("vexPath")]
|
|
public string? VexPath { get; init; }
|
|
|
|
[JsonPropertyName("fromEnvironment")]
|
|
public string FromEnvironment { get; init; } = "staging";
|
|
|
|
[JsonPropertyName("toEnvironment")]
|
|
public string ToEnvironment { get; init; } = "prod";
|
|
|
|
[JsonPropertyName("actor")]
|
|
public string? Actor { get; init; }
|
|
|
|
[JsonPropertyName("pipeline")]
|
|
public string? Pipeline { get; init; }
|
|
|
|
[JsonPropertyName("ticket")]
|
|
public string? Ticket { get; init; }
|
|
|
|
[JsonPropertyName("notes")]
|
|
public string? Notes { get; init; }
|
|
|
|
[JsonPropertyName("skipRekor")]
|
|
public bool SkipRekor { get; init; }
|
|
|
|
[JsonPropertyName("outputPath")]
|
|
public string? OutputPath { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Promotion attestation predicate following stella.ops/promotion@v1 schema.
|
|
/// </summary>
|
|
internal sealed class PromotionPredicate
|
|
{
|
|
[JsonPropertyName("_type")]
|
|
public string Type { get; init; } = "stella.ops/promotion@v1";
|
|
|
|
[JsonPropertyName("subject")]
|
|
public IReadOnlyList<PromotionSubject> Subject { get; init; } = Array.Empty<PromotionSubject>();
|
|
|
|
[JsonPropertyName("materials")]
|
|
public IReadOnlyList<PromotionMaterial> Materials { get; init; } = Array.Empty<PromotionMaterial>();
|
|
|
|
[JsonPropertyName("promotion")]
|
|
public PromotionMetadata Promotion { get; init; } = new();
|
|
|
|
[JsonPropertyName("rekor")]
|
|
public PromotionRekorEntry? Rekor { get; init; }
|
|
|
|
[JsonPropertyName("attestation")]
|
|
public PromotionAttestationMetadata? Attestation { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subject in promotion attestation (image reference).
|
|
/// </summary>
|
|
internal sealed class PromotionSubject
|
|
{
|
|
[JsonPropertyName("name")]
|
|
public string Name { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("digest")]
|
|
public IReadOnlyDictionary<string, string> Digest { get; init; } = new Dictionary<string, string>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Material in promotion attestation (SBOM, VEX, etc.).
|
|
/// </summary>
|
|
internal sealed class PromotionMaterial
|
|
{
|
|
[JsonPropertyName("role")]
|
|
public string Role { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("algo")]
|
|
public string Algo { get; init; } = "sha256";
|
|
|
|
[JsonPropertyName("digest")]
|
|
public string Digest { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("format")]
|
|
public string? Format { get; init; }
|
|
|
|
[JsonPropertyName("uri")]
|
|
public string? Uri { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Promotion metadata.
|
|
/// </summary>
|
|
internal sealed class PromotionMetadata
|
|
{
|
|
[JsonPropertyName("from")]
|
|
public string From { get; init; } = "staging";
|
|
|
|
[JsonPropertyName("to")]
|
|
public string To { get; init; } = "prod";
|
|
|
|
[JsonPropertyName("actor")]
|
|
public string? Actor { get; init; }
|
|
|
|
[JsonPropertyName("timestamp")]
|
|
public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
|
|
|
|
[JsonPropertyName("pipeline")]
|
|
public string? Pipeline { get; init; }
|
|
|
|
[JsonPropertyName("ticket")]
|
|
public string? Ticket { get; init; }
|
|
|
|
[JsonPropertyName("notes")]
|
|
public string? Notes { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rekor entry in promotion attestation.
|
|
/// </summary>
|
|
internal sealed class PromotionRekorEntry
|
|
{
|
|
[JsonPropertyName("uuid")]
|
|
public string Uuid { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("logIndex")]
|
|
public long LogIndex { get; init; }
|
|
|
|
[JsonPropertyName("inclusionProof")]
|
|
public PromotionInclusionProof? InclusionProof { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Merkle inclusion proof.
|
|
/// </summary>
|
|
internal sealed class PromotionInclusionProof
|
|
{
|
|
[JsonPropertyName("rootHash")]
|
|
public string RootHash { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("hashes")]
|
|
public IReadOnlyList<string> Hashes { get; init; } = Array.Empty<string>();
|
|
|
|
[JsonPropertyName("treeSize")]
|
|
public long TreeSize { get; init; }
|
|
|
|
[JsonPropertyName("checkpoint")]
|
|
public PromotionCheckpoint? Checkpoint { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rekor checkpoint.
|
|
/// </summary>
|
|
internal sealed class PromotionCheckpoint
|
|
{
|
|
[JsonPropertyName("origin")]
|
|
public string Origin { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("size")]
|
|
public long Size { get; init; }
|
|
|
|
[JsonPropertyName("hash")]
|
|
public string Hash { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("signedNote")]
|
|
public string? SignedNote { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attestation metadata.
|
|
/// </summary>
|
|
internal sealed class PromotionAttestationMetadata
|
|
{
|
|
[JsonPropertyName("bundle_sha256")]
|
|
public string BundleSha256 { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("witness")]
|
|
public string? Witness { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of promotion assemble operation.
|
|
/// </summary>
|
|
internal sealed class PromotionAssembleResult
|
|
{
|
|
[JsonPropertyName("success")]
|
|
public bool Success { get; init; }
|
|
|
|
[JsonPropertyName("predicate")]
|
|
public PromotionPredicate? Predicate { get; init; }
|
|
|
|
[JsonPropertyName("outputPath")]
|
|
public string? OutputPath { get; init; }
|
|
|
|
[JsonPropertyName("imageDigest")]
|
|
public string ImageDigest { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("materials")]
|
|
public IReadOnlyList<PromotionMaterial> Materials { get; init; } = Array.Empty<PromotionMaterial>();
|
|
|
|
[JsonPropertyName("rekorEntry")]
|
|
public PromotionRekorEntry? RekorEntry { get; init; }
|
|
|
|
[JsonPropertyName("errors")]
|
|
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
|
|
|
[JsonPropertyName("warnings")]
|
|
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
|
}
|
|
|
|
// CLI-PROMO-70-002: Promotion attest/verify models
|
|
|
|
/// <summary>
|
|
/// Request for attesting a promotion predicate.
|
|
/// </summary>
|
|
internal sealed class PromotionAttestRequest
|
|
{
|
|
[JsonPropertyName("tenant")]
|
|
public string Tenant { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("predicatePath")]
|
|
public string? PredicatePath { get; init; }
|
|
|
|
[JsonPropertyName("predicate")]
|
|
public PromotionPredicate? Predicate { get; init; }
|
|
|
|
[JsonPropertyName("keyId")]
|
|
public string? KeyId { get; init; }
|
|
|
|
[JsonPropertyName("useKeyless")]
|
|
public bool UseKeyless { get; init; }
|
|
|
|
[JsonPropertyName("outputPath")]
|
|
public string? OutputPath { get; init; }
|
|
|
|
[JsonPropertyName("uploadToRekor")]
|
|
public bool UploadToRekor { get; init; } = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of promotion attest operation.
|
|
/// </summary>
|
|
internal sealed class PromotionAttestResult
|
|
{
|
|
[JsonPropertyName("success")]
|
|
public bool Success { get; init; }
|
|
|
|
[JsonPropertyName("bundlePath")]
|
|
public string? BundlePath { get; init; }
|
|
|
|
[JsonPropertyName("dsseEnvelope")]
|
|
public DsseEnvelope? DsseEnvelope { get; init; }
|
|
|
|
[JsonPropertyName("rekorEntry")]
|
|
public PromotionRekorEntry? RekorEntry { get; init; }
|
|
|
|
[JsonPropertyName("auditId")]
|
|
public string? AuditId { get; init; }
|
|
|
|
[JsonPropertyName("signerKeyId")]
|
|
public string? SignerKeyId { get; init; }
|
|
|
|
[JsonPropertyName("signedAt")]
|
|
public DateTimeOffset? SignedAt { get; init; }
|
|
|
|
[JsonPropertyName("errors")]
|
|
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
|
|
|
[JsonPropertyName("warnings")]
|
|
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// DSSE envelope for promotion attestation.
|
|
/// </summary>
|
|
internal sealed class DsseEnvelope
|
|
{
|
|
[JsonPropertyName("payloadType")]
|
|
public string PayloadType { get; init; } = "application/vnd.in-toto+json";
|
|
|
|
[JsonPropertyName("payload")]
|
|
public string Payload { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("signatures")]
|
|
public IReadOnlyList<DsseSignature> Signatures { get; init; } = Array.Empty<DsseSignature>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// DSSE signature.
|
|
/// </summary>
|
|
internal sealed class DsseSignature
|
|
{
|
|
[JsonPropertyName("keyid")]
|
|
public string KeyId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("sig")]
|
|
public string Sig { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("cert")]
|
|
public string? Cert { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request for verifying a promotion attestation.
|
|
/// </summary>
|
|
internal sealed class PromotionVerifyRequest
|
|
{
|
|
[JsonPropertyName("tenant")]
|
|
public string Tenant { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("bundlePath")]
|
|
public string? BundlePath { get; init; }
|
|
|
|
[JsonPropertyName("predicatePath")]
|
|
public string? PredicatePath { get; init; }
|
|
|
|
[JsonPropertyName("sbomPath")]
|
|
public string? SbomPath { get; init; }
|
|
|
|
[JsonPropertyName("vexPath")]
|
|
public string? VexPath { get; init; }
|
|
|
|
[JsonPropertyName("trustRootPath")]
|
|
public string? TrustRootPath { get; init; }
|
|
|
|
[JsonPropertyName("checkpointPath")]
|
|
public string? CheckpointPath { get; init; }
|
|
|
|
[JsonPropertyName("skipRekorVerification")]
|
|
public bool SkipRekorVerification { get; init; }
|
|
|
|
[JsonPropertyName("skipSignatureVerification")]
|
|
public bool SkipSignatureVerification { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of promotion verify operation.
|
|
/// </summary>
|
|
internal sealed class PromotionVerifyResult
|
|
{
|
|
[JsonPropertyName("success")]
|
|
public bool Success { get; init; }
|
|
|
|
[JsonPropertyName("verified")]
|
|
public bool Verified { get; init; }
|
|
|
|
[JsonPropertyName("signatureVerification")]
|
|
public PromotionSignatureVerification? SignatureVerification { get; init; }
|
|
|
|
[JsonPropertyName("materialVerification")]
|
|
public PromotionMaterialVerification? MaterialVerification { get; init; }
|
|
|
|
[JsonPropertyName("rekorVerification")]
|
|
public PromotionRekorVerification? RekorVerification { get; init; }
|
|
|
|
[JsonPropertyName("predicate")]
|
|
public PromotionPredicate? Predicate { get; init; }
|
|
|
|
[JsonPropertyName("errors")]
|
|
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
|
|
|
[JsonPropertyName("warnings")]
|
|
public IReadOnlyList<string> Warnings { get; init; } = Array.Empty<string>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signature verification result.
|
|
/// </summary>
|
|
internal sealed class PromotionSignatureVerification
|
|
{
|
|
[JsonPropertyName("verified")]
|
|
public bool Verified { get; init; }
|
|
|
|
[JsonPropertyName("keyId")]
|
|
public string? KeyId { get; init; }
|
|
|
|
[JsonPropertyName("algorithm")]
|
|
public string? Algorithm { get; init; }
|
|
|
|
[JsonPropertyName("certSubject")]
|
|
public string? CertSubject { get; init; }
|
|
|
|
[JsonPropertyName("certIssuer")]
|
|
public string? CertIssuer { get; init; }
|
|
|
|
[JsonPropertyName("validFrom")]
|
|
public DateTimeOffset? ValidFrom { get; init; }
|
|
|
|
[JsonPropertyName("validTo")]
|
|
public DateTimeOffset? ValidTo { get; init; }
|
|
|
|
[JsonPropertyName("error")]
|
|
public string? Error { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Material verification result.
|
|
/// </summary>
|
|
internal sealed class PromotionMaterialVerification
|
|
{
|
|
[JsonPropertyName("verified")]
|
|
public bool Verified { get; init; }
|
|
|
|
[JsonPropertyName("materials")]
|
|
public IReadOnlyList<PromotionMaterialVerificationEntry> Materials { get; init; } = Array.Empty<PromotionMaterialVerificationEntry>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Individual material verification entry.
|
|
/// </summary>
|
|
internal sealed class PromotionMaterialVerificationEntry
|
|
{
|
|
[JsonPropertyName("role")]
|
|
public string Role { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("verified")]
|
|
public bool Verified { get; init; }
|
|
|
|
[JsonPropertyName("expectedDigest")]
|
|
public string ExpectedDigest { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("actualDigest")]
|
|
public string? ActualDigest { get; init; }
|
|
|
|
[JsonPropertyName("error")]
|
|
public string? Error { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rekor verification result.
|
|
/// </summary>
|
|
internal sealed class PromotionRekorVerification
|
|
{
|
|
[JsonPropertyName("verified")]
|
|
public bool Verified { get; init; }
|
|
|
|
[JsonPropertyName("uuid")]
|
|
public string? Uuid { get; init; }
|
|
|
|
[JsonPropertyName("logIndex")]
|
|
public long? LogIndex { get; init; }
|
|
|
|
[JsonPropertyName("inclusionProofVerified")]
|
|
public bool InclusionProofVerified { get; init; }
|
|
|
|
[JsonPropertyName("checkpointVerified")]
|
|
public bool CheckpointVerified { get; init; }
|
|
|
|
[JsonPropertyName("error")]
|
|
public string? Error { get; init; }
|
|
}
|