doctor enhancements, setup, enhancements, ui functionality and design consolidation and , test projects fixes , product advisory attestation/rekor and delta verfications enhancements

This commit is contained in:
master
2026-01-19 09:02:59 +02:00
parent 8c4bf54aed
commit 17419ba7c4
809 changed files with 170738 additions and 12244 deletions

View File

@@ -0,0 +1,234 @@
// -----------------------------------------------------------------------------
// BundleFormatV2.cs
// Sprint: SPRINT_20260118_018_AirGap_router_integration
// Task: TASK-018-001 - Complete Air-Gap Bundle Format
// Description: Air-gap bundle format v2.0.0 matching advisory specification
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.AirGap.Bundle.Models;
/// <summary>
/// Air-gap bundle manifest v2.0.0 per advisory specification.
/// </summary>
public sealed record BundleManifestV2
{
/// <summary>Schema version.</summary>
[JsonPropertyName("schemaVersion")]
public string SchemaVersion { get; init; } = "2.0.0";
/// <summary>Bundle information.</summary>
[JsonPropertyName("bundle")]
public required BundleInfoV2 Bundle { get; init; }
/// <summary>Verification configuration.</summary>
[JsonPropertyName("verify")]
public BundleVerifySection? Verify { get; init; }
/// <summary>Bundle metadata.</summary>
[JsonPropertyName("metadata")]
public BundleMetadata? Metadata { get; init; }
}
/// <summary>
/// Bundle information.
/// </summary>
public sealed record BundleInfoV2
{
/// <summary>Primary image reference.</summary>
[JsonPropertyName("image")]
public required string Image { get; init; }
/// <summary>Image digest.</summary>
[JsonPropertyName("digest")]
public string? Digest { get; init; }
/// <summary>Bundle artifacts.</summary>
[JsonPropertyName("artifacts")]
public required ImmutableArray<BundleArtifact> Artifacts { get; init; }
/// <summary>OCI referrer manifest.</summary>
[JsonPropertyName("referrers")]
public OciReferrerIndex? Referrers { get; init; }
}
/// <summary>
/// Bundle artifact entry.
/// </summary>
public sealed record BundleArtifact
{
/// <summary>Path within bundle.</summary>
[JsonPropertyName("path")]
public required string Path { get; init; }
/// <summary>Artifact type.</summary>
[JsonPropertyName("type")]
public BundleArtifactType Type { get; init; }
/// <summary>Content digest (sha256).</summary>
[JsonPropertyName("digest")]
public string? Digest { get; init; }
/// <summary>Media type.</summary>
[JsonPropertyName("mediaType")]
public string? MediaType { get; init; }
/// <summary>Size in bytes.</summary>
[JsonPropertyName("size")]
public long Size { get; init; }
}
/// <summary>
/// Bundle artifact type.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum BundleArtifactType
{
/// <summary>SBOM document.</summary>
[JsonPropertyName("sbom")]
Sbom,
/// <summary>DSSE-signed SBOM statement.</summary>
[JsonPropertyName("sbom.dsse")]
SbomDsse,
/// <summary>VEX document.</summary>
[JsonPropertyName("vex")]
Vex,
/// <summary>DSSE-signed VEX statement.</summary>
[JsonPropertyName("vex.dsse")]
VexDsse,
/// <summary>Rekor inclusion proof.</summary>
[JsonPropertyName("rekor.proof")]
RekorProof,
/// <summary>OCI referrers index.</summary>
[JsonPropertyName("oci.referrers")]
OciReferrers,
/// <summary>Policy snapshot.</summary>
[JsonPropertyName("policy")]
Policy,
/// <summary>Feed snapshot.</summary>
[JsonPropertyName("feed")]
Feed,
/// <summary>Rekor checkpoint.</summary>
[JsonPropertyName("rekor.checkpoint")]
RekorCheckpoint,
/// <summary>Other/generic artifact.</summary>
[JsonPropertyName("other")]
Other
}
/// <summary>
/// Bundle verification section.
/// </summary>
public sealed record BundleVerifySection
{
/// <summary>Trusted signing keys.</summary>
[JsonPropertyName("keys")]
public ImmutableArray<string> Keys { get; init; } = [];
/// <summary>Verification expectations.</summary>
[JsonPropertyName("expectations")]
public VerifyExpectations? Expectations { get; init; }
/// <summary>Certificate roots for verification.</summary>
[JsonPropertyName("certificateRoots")]
public ImmutableArray<string> CertificateRoots { get; init; } = [];
}
/// <summary>
/// Verification expectations.
/// </summary>
public sealed record VerifyExpectations
{
/// <summary>Expected payload types.</summary>
[JsonPropertyName("payloadTypes")]
public ImmutableArray<string> PayloadTypes { get; init; } = [];
/// <summary>Whether Rekor inclusion is required.</summary>
[JsonPropertyName("rekorRequired")]
public bool RekorRequired { get; init; }
/// <summary>Expected issuers.</summary>
[JsonPropertyName("issuers")]
public ImmutableArray<string> Issuers { get; init; } = [];
/// <summary>Minimum signature count.</summary>
[JsonPropertyName("minSignatures")]
public int MinSignatures { get; init; } = 1;
}
/// <summary>
/// OCI referrer index.
/// </summary>
public sealed record OciReferrerIndex
{
/// <summary>Referrer descriptors.</summary>
[JsonPropertyName("manifests")]
public ImmutableArray<OciReferrerDescriptor> Manifests { get; init; } = [];
}
/// <summary>
/// OCI referrer descriptor.
/// </summary>
public sealed record OciReferrerDescriptor
{
/// <summary>Media type.</summary>
[JsonPropertyName("mediaType")]
public required string MediaType { get; init; }
/// <summary>Digest.</summary>
[JsonPropertyName("digest")]
public required string Digest { get; init; }
/// <summary>Artifact type.</summary>
[JsonPropertyName("artifactType")]
public string? ArtifactType { get; init; }
/// <summary>Size.</summary>
[JsonPropertyName("size")]
public long Size { get; init; }
/// <summary>Annotations.</summary>
[JsonPropertyName("annotations")]
public IReadOnlyDictionary<string, string>? Annotations { get; init; }
}
/// <summary>
/// Bundle metadata.
/// </summary>
public sealed record BundleMetadata
{
/// <summary>When bundle was created.</summary>
[JsonPropertyName("createdAt")]
public DateTimeOffset CreatedAt { get; init; }
/// <summary>Bundle creator.</summary>
[JsonPropertyName("createdBy")]
public string? CreatedBy { get; init; }
/// <summary>Bundle description.</summary>
[JsonPropertyName("description")]
public string? Description { get; init; }
/// <summary>Source environment.</summary>
[JsonPropertyName("sourceEnvironment")]
public string? SourceEnvironment { get; init; }
/// <summary>Target environment.</summary>
[JsonPropertyName("targetEnvironment")]
public string? TargetEnvironment { get; init; }
/// <summary>Additional labels.</summary>
[JsonPropertyName("labels")]
public IReadOnlyDictionary<string, string>? Labels { get; init; }
}

View File

@@ -5,11 +5,12 @@ namespace StellaOps.AirGap.Bundle.Models;
/// <summary>
/// Manifest for an offline bundle, inventorying all components with content digests.
/// Used for integrity verification and completeness checking in air-gapped environments.
/// Sprint: SPRINT_20260118_018 (TASK-018-001) - Updated to v2.0.0
/// </summary>
public sealed record BundleManifest
{
public required string BundleId { get; init; }
public string SchemaVersion { get; init; } = "1.0.0";
public string SchemaVersion { get; init; } = "2.0.0";
public required string Name { get; init; }
public required string Version { get; init; }
public required DateTimeOffset CreatedAt { get; init; }
@@ -23,6 +24,103 @@ public sealed record BundleManifest
public ImmutableArray<RuleBundleComponent> RuleBundles { get; init; } = [];
public long TotalSizeBytes { get; init; }
public string? BundleDigest { get; init; }
// -------------------------------------------------------------------------
// v2.0.0 Additions - Sprint: SPRINT_20260118_018 (TASK-018-001)
// -------------------------------------------------------------------------
/// <summary>
/// Image reference this bundle is for (advisory-specified format).
/// Example: "registry.example.com/app@sha256:..."
/// </summary>
public string? Image { get; init; }
/// <summary>
/// List of artifacts in the bundle with path and type information.
/// </summary>
public ImmutableArray<BundleArtifact> Artifacts { get; init; } = [];
/// <summary>
/// Verification section with keys and expectations.
/// </summary>
public BundleVerifySection? Verify { get; init; }
}
/// <summary>
/// Artifact entry in a bundle (v2.0.0).
/// Sprint: SPRINT_20260118_018 (TASK-018-001)
/// </summary>
public sealed record BundleArtifact(
/// <summary>Relative path within the bundle.</summary>
string Path,
/// <summary>Artifact type: sbom, vex, dsse, rekor-proof, oci-referrers, etc.</summary>
string Type,
/// <summary>Content type (MIME).</summary>
string? ContentType,
/// <summary>SHA-256 digest of the artifact.</summary>
string? Digest,
/// <summary>Size in bytes.</summary>
long? SizeBytes);
/// <summary>
/// Verification section for bundle validation (v2.0.0).
/// Sprint: SPRINT_20260118_018 (TASK-018-001)
/// </summary>
public sealed record BundleVerifySection
{
/// <summary>
/// Trusted signing keys for verification.
/// Formats: kms://..., file://..., sigstore://...
/// </summary>
public ImmutableArray<string> Keys { get; init; } = [];
/// <summary>
/// Verification expectations.
/// </summary>
public BundleVerifyExpectations? Expectations { get; init; }
/// <summary>
/// Optional: path to trust root certificate.
/// </summary>
public string? TrustRoot { get; init; }
/// <summary>
/// Optional: Rekor checkpoint for offline proof verification.
/// </summary>
public string? RekorCheckpointPath { get; init; }
}
/// <summary>
/// Verification expectations (v2.0.0).
/// Sprint: SPRINT_20260118_018 (TASK-018-001)
/// </summary>
public sealed record BundleVerifyExpectations
{
/// <summary>
/// Expected payload types in DSSE envelopes.
/// Example: ["application/vnd.cyclonedx+json;version=1.6", "application/vnd.openvex+json"]
/// </summary>
public ImmutableArray<string> PayloadTypes { get; init; } = [];
/// <summary>
/// Whether Rekor proof is required for verification.
/// </summary>
public bool RekorRequired { get; init; } = true;
/// <summary>
/// Minimum number of signatures required.
/// </summary>
public int MinSignatures { get; init; } = 1;
/// <summary>
/// Required artifact types that must be present.
/// </summary>
public ImmutableArray<string> RequiredArtifacts { get; init; } = [];
/// <summary>
/// Whether all artifacts must pass checksum verification.
/// </summary>
public bool VerifyChecksums { get; init; } = true;
}
public sealed record FeedComponent(

View File

@@ -96,4 +96,160 @@ public class BundleManifestTests
TotalSizeBytes = 30
};
}
// -------------------------------------------------------------------------
// v2.0.0 Tests - Sprint: SPRINT_20260118_018 (TASK-018-001)
// -------------------------------------------------------------------------
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestV2_DefaultSchemaVersion_Is200()
{
var manifest = new BundleManifest
{
BundleId = "test",
Name = "test",
Version = "1.0.0",
CreatedAt = DateTimeOffset.UtcNow,
Feeds = [],
Policies = [],
CryptoMaterials = []
};
manifest.SchemaVersion.Should().Be("2.0.0");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestV2_WithImage_SetsImageReference()
{
var manifest = new BundleManifest
{
BundleId = "test",
Name = "test",
Version = "1.0.0",
CreatedAt = DateTimeOffset.UtcNow,
Feeds = [],
Policies = [],
CryptoMaterials = [],
Image = "registry.example.com/app@sha256:abc123"
};
manifest.Image.Should().Be("registry.example.com/app@sha256:abc123");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestV2_WithArtifacts_ContainsExpectedEntries()
{
var manifest = new BundleManifest
{
BundleId = "test",
Name = "test",
Version = "1.0.0",
CreatedAt = DateTimeOffset.UtcNow,
Feeds = [],
Policies = [],
CryptoMaterials = [],
Image = "registry.example.com/app@sha256:abc123",
Artifacts =
[
new BundleArtifact("sbom.cdx.json", "sbom", "application/vnd.cyclonedx+json", "sha256:def", 1024),
new BundleArtifact("sbom.statement.dsse.json", "dsse", "application/vnd.dsse+json", "sha256:ghi", 512),
new BundleArtifact("vex.statement.dsse.json", "dsse", "application/vnd.dsse+json", "sha256:jkl", 256),
new BundleArtifact("rekor.proof.json", "rekor-proof", "application/json", "sha256:mno", 128),
new BundleArtifact("oci.referrers.json", "oci-referrers", "application/vnd.oci.image.index.v1+json", "sha256:pqr", 64)
]
};
manifest.Artifacts.Should().HaveCount(5);
manifest.Artifacts.Should().Contain(a => a.Path == "sbom.cdx.json");
manifest.Artifacts.Should().Contain(a => a.Type == "dsse");
manifest.Artifacts.Should().Contain(a => a.Type == "rekor-proof");
manifest.Artifacts.Should().Contain(a => a.Type == "oci-referrers");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestV2_WithVerifySection_ContainsKeysAndExpectations()
{
var manifest = new BundleManifest
{
BundleId = "test",
Name = "test",
Version = "1.0.0",
CreatedAt = DateTimeOffset.UtcNow,
Feeds = [],
Policies = [],
CryptoMaterials = [],
Image = "registry.example.com/app@sha256:abc123",
Verify = new BundleVerifySection
{
Keys = ["kms://projects/test/locations/global/keyRings/ring/cryptoKeys/key"],
TrustRoot = "trust-root.pem",
RekorCheckpointPath = "rekor-checkpoint.json",
Expectations = new BundleVerifyExpectations
{
PayloadTypes = ["application/vnd.cyclonedx+json;version=1.6", "application/vnd.openvex+json"],
RekorRequired = true,
MinSignatures = 1,
RequiredArtifacts = ["sbom.cdx.json", "sbom.statement.dsse.json"],
VerifyChecksums = true
}
}
};
manifest.Verify.Should().NotBeNull();
manifest.Verify!.Keys.Should().HaveCount(1);
manifest.Verify.Keys[0].Should().StartWith("kms://");
manifest.Verify.Expectations.Should().NotBeNull();
manifest.Verify.Expectations!.PayloadTypes.Should().HaveCount(2);
manifest.Verify.Expectations.RekorRequired.Should().BeTrue();
manifest.Verify.Expectations.RequiredArtifacts.Should().HaveCount(2);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestV2_Serialization_RoundTrip()
{
var manifest = CreateV2Manifest();
var json = BundleManifestSerializer.Serialize(manifest);
var deserialized = BundleManifestSerializer.Deserialize(json);
deserialized.SchemaVersion.Should().Be("2.0.0");
deserialized.Image.Should().Be(manifest.Image);
deserialized.Artifacts.Should().HaveCount(manifest.Artifacts.Length);
deserialized.Verify.Should().NotBeNull();
deserialized.Verify!.Keys.Should().BeEquivalentTo(manifest.Verify!.Keys);
}
private static BundleManifest CreateV2Manifest()
{
return new BundleManifest
{
BundleId = Guid.NewGuid().ToString(),
SchemaVersion = "2.0.0",
Name = "offline-bundle-v2",
Version = "1.0.0",
CreatedAt = DateTimeOffset.UtcNow,
Feeds = [],
Policies = [],
CryptoMaterials = [],
Image = "registry.example.com/app@sha256:abc123def456",
Artifacts =
[
new BundleArtifact("sbom.cdx.json", "sbom", "application/vnd.cyclonedx+json", "sha256:aaa", 1024),
new BundleArtifact("sbom.statement.dsse.json", "dsse", "application/vnd.dsse+json", "sha256:bbb", 512)
],
Verify = new BundleVerifySection
{
Keys = ["kms://example/key"],
Expectations = new BundleVerifyExpectations
{
PayloadTypes = ["application/vnd.cyclonedx+json;version=1.6"],
RekorRequired = true
}
}
};
}
}