finish off sprint advisories and sprints
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Copyright (c) 2025 StellaOps
|
||||
// Sprint: SPRINT_20260122_039_Scanner_runtime_linkage_verification
|
||||
// Task: RLV-011 - Bundle Integration: function_map Artifact Type
|
||||
|
||||
using StellaOps.AirGap.Bundle.Models;
|
||||
using StellaOps.AirGap.Bundle.Services;
|
||||
|
||||
namespace StellaOps.AirGap.Bundle.FunctionMap;
|
||||
|
||||
/// <summary>
|
||||
/// Integration constants and helpers for function_map artifacts in StellaBundle.
|
||||
/// Provides standardized artifact type strings, media types, and factory methods
|
||||
/// for building function-map bundle configurations.
|
||||
/// </summary>
|
||||
public static class FunctionMapBundleIntegration
|
||||
{
|
||||
/// <summary>
|
||||
/// Artifact type strings for bundle manifest entries.
|
||||
/// </summary>
|
||||
public static class ArtifactTypes
|
||||
{
|
||||
/// <summary>Function map predicate JSON.</summary>
|
||||
public const string FunctionMap = "function-map";
|
||||
|
||||
/// <summary>DSSE-signed function map statement.</summary>
|
||||
public const string FunctionMapDsse = "function-map.dsse";
|
||||
|
||||
/// <summary>Runtime observations data (NDJSON).</summary>
|
||||
public const string Observations = "observations";
|
||||
|
||||
/// <summary>Verification report JSON.</summary>
|
||||
public const string VerificationReport = "verification-report";
|
||||
|
||||
/// <summary>DSSE-signed verification report.</summary>
|
||||
public const string VerificationReportDsse = "verification-report.dsse";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Media types for function-map artifacts.
|
||||
/// </summary>
|
||||
public static class MediaTypes
|
||||
{
|
||||
/// <summary>Function map predicate media type.</summary>
|
||||
public const string FunctionMap = "application/vnd.stella.function-map+json";
|
||||
|
||||
/// <summary>DSSE-signed function map envelope.</summary>
|
||||
public const string FunctionMapDsse = "application/vnd.dsse+json";
|
||||
|
||||
/// <summary>Runtime observations NDJSON.</summary>
|
||||
public const string Observations = "application/x-ndjson";
|
||||
|
||||
/// <summary>Verification report media type.</summary>
|
||||
public const string VerificationReport = "application/vnd.stella.verification-report+json";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default relative paths within a bundle.
|
||||
/// </summary>
|
||||
public static class BundlePaths
|
||||
{
|
||||
/// <summary>Directory for function maps.</summary>
|
||||
public const string FunctionMapsDir = "function-maps";
|
||||
|
||||
/// <summary>Directory for observations.</summary>
|
||||
public const string ObservationsDir = "observations";
|
||||
|
||||
/// <summary>Directory for verification reports.</summary>
|
||||
public const string VerificationDir = "verification";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bundle artifact build config for a function map predicate file.
|
||||
/// </summary>
|
||||
/// <param name="sourcePath">Path to the function map JSON file on disk.</param>
|
||||
/// <param name="serviceName">Service name for the function map (used in bundle path).</param>
|
||||
/// <returns>A configured <see cref="BundleArtifactBuildConfig"/>.</returns>
|
||||
public static BundleArtifactBuildConfig CreateFunctionMapConfig(string sourcePath, string serviceName)
|
||||
{
|
||||
var fileName = $"{SanitizeName(serviceName)}-function-map.json";
|
||||
return new BundleArtifactBuildConfig
|
||||
{
|
||||
Type = ArtifactTypes.FunctionMap,
|
||||
ContentType = MediaTypes.FunctionMap,
|
||||
SourcePath = sourcePath,
|
||||
RelativePath = $"{BundlePaths.FunctionMapsDir}/{fileName}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bundle artifact build config for a DSSE-signed function map.
|
||||
/// </summary>
|
||||
/// <param name="sourcePath">Path to the DSSE envelope JSON file on disk.</param>
|
||||
/// <param name="serviceName">Service name for the function map (used in bundle path).</param>
|
||||
/// <returns>A configured <see cref="BundleArtifactBuildConfig"/>.</returns>
|
||||
public static BundleArtifactBuildConfig CreateFunctionMapDsseConfig(string sourcePath, string serviceName)
|
||||
{
|
||||
var fileName = $"{SanitizeName(serviceName)}-function-map.dsse.json";
|
||||
return new BundleArtifactBuildConfig
|
||||
{
|
||||
Type = ArtifactTypes.FunctionMapDsse,
|
||||
ContentType = MediaTypes.FunctionMapDsse,
|
||||
SourcePath = sourcePath,
|
||||
RelativePath = $"{BundlePaths.FunctionMapsDir}/{fileName}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bundle artifact build config for a runtime observations file.
|
||||
/// </summary>
|
||||
/// <param name="sourcePath">Path to the NDJSON observations file on disk.</param>
|
||||
/// <param name="dateLabel">Date label for the observations file (e.g., "2026-01-22").</param>
|
||||
/// <returns>A configured <see cref="BundleArtifactBuildConfig"/>.</returns>
|
||||
public static BundleArtifactBuildConfig CreateObservationsConfig(string sourcePath, string dateLabel)
|
||||
{
|
||||
var fileName = $"observations-{SanitizeName(dateLabel)}.ndjson";
|
||||
return new BundleArtifactBuildConfig
|
||||
{
|
||||
Type = ArtifactTypes.Observations,
|
||||
ContentType = MediaTypes.Observations,
|
||||
SourcePath = sourcePath,
|
||||
RelativePath = $"{BundlePaths.ObservationsDir}/{fileName}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bundle artifact build config for a verification report.
|
||||
/// </summary>
|
||||
/// <param name="sourcePath">Path to the verification report JSON file on disk.</param>
|
||||
/// <returns>A configured <see cref="BundleArtifactBuildConfig"/>.</returns>
|
||||
public static BundleArtifactBuildConfig CreateVerificationReportConfig(string sourcePath)
|
||||
{
|
||||
return new BundleArtifactBuildConfig
|
||||
{
|
||||
Type = ArtifactTypes.VerificationReport,
|
||||
ContentType = MediaTypes.VerificationReport,
|
||||
SourcePath = sourcePath,
|
||||
RelativePath = $"{BundlePaths.VerificationDir}/verification-report.json"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bundle artifact build config for a DSSE-signed verification report.
|
||||
/// </summary>
|
||||
/// <param name="sourcePath">Path to the DSSE envelope JSON file on disk.</param>
|
||||
/// <returns>A configured <see cref="BundleArtifactBuildConfig"/>.</returns>
|
||||
public static BundleArtifactBuildConfig CreateVerificationReportDsseConfig(string sourcePath)
|
||||
{
|
||||
return new BundleArtifactBuildConfig
|
||||
{
|
||||
Type = ArtifactTypes.VerificationReportDsse,
|
||||
ContentType = MediaTypes.FunctionMapDsse,
|
||||
SourcePath = sourcePath,
|
||||
RelativePath = $"{BundlePaths.VerificationDir}/verification-report.dsse.json"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bundle artifact build config from in-memory function map content.
|
||||
/// </summary>
|
||||
/// <param name="content">Function map predicate JSON bytes.</param>
|
||||
/// <param name="serviceName">Service name for the function map.</param>
|
||||
/// <returns>A configured <see cref="BundleArtifactBuildConfig"/>.</returns>
|
||||
public static BundleArtifactBuildConfig CreateFunctionMapFromContent(byte[] content, string serviceName)
|
||||
{
|
||||
var fileName = $"{SanitizeName(serviceName)}-function-map.json";
|
||||
return new BundleArtifactBuildConfig
|
||||
{
|
||||
Type = ArtifactTypes.FunctionMap,
|
||||
ContentType = MediaTypes.FunctionMap,
|
||||
Content = content,
|
||||
RelativePath = $"{BundlePaths.FunctionMapsDir}/{fileName}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bundle artifact build config from in-memory observations content.
|
||||
/// </summary>
|
||||
/// <param name="content">Observations NDJSON bytes.</param>
|
||||
/// <param name="dateLabel">Date label for the observations file.</param>
|
||||
/// <returns>A configured <see cref="BundleArtifactBuildConfig"/>.</returns>
|
||||
public static BundleArtifactBuildConfig CreateObservationsFromContent(byte[] content, string dateLabel)
|
||||
{
|
||||
var fileName = $"observations-{SanitizeName(dateLabel)}.ndjson";
|
||||
return new BundleArtifactBuildConfig
|
||||
{
|
||||
Type = ArtifactTypes.Observations,
|
||||
ContentType = MediaTypes.Observations,
|
||||
Content = content,
|
||||
RelativePath = $"{BundlePaths.ObservationsDir}/{fileName}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given artifact type string represents a function-map related artifact.
|
||||
/// </summary>
|
||||
public static bool IsFunctionMapArtifact(string? artifactType)
|
||||
{
|
||||
return artifactType is ArtifactTypes.FunctionMap
|
||||
or ArtifactTypes.FunctionMapDsse
|
||||
or ArtifactTypes.Observations
|
||||
or ArtifactTypes.VerificationReport
|
||||
or ArtifactTypes.VerificationReportDsse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given artifact type is a DSSE-signed artifact that should be verified.
|
||||
/// </summary>
|
||||
public static bool IsDsseArtifact(string? artifactType)
|
||||
{
|
||||
return artifactType is ArtifactTypes.FunctionMapDsse
|
||||
or ArtifactTypes.VerificationReportDsse;
|
||||
}
|
||||
|
||||
private static string SanitizeName(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
var buffer = new char[value.Length];
|
||||
var index = 0;
|
||||
foreach (var ch in value)
|
||||
{
|
||||
if (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == '.')
|
||||
{
|
||||
buffer[index++] = ch;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[index++] = '-';
|
||||
}
|
||||
}
|
||||
|
||||
var cleaned = new string(buffer, 0, index).Trim('-');
|
||||
return string.IsNullOrWhiteSpace(cleaned) ? "unknown" : cleaned;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BundleExportMode.cs
|
||||
// Sprint: SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-04)
|
||||
// Description: Two-tier bundle export mode enum
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace StellaOps.AirGap.Bundle.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Controls how much content is included in an exported evidence bundle.
|
||||
/// </summary>
|
||||
public enum BundleExportMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Include only metadata, predicates, proofs, and SBOMs. No binary blobs.
|
||||
/// Typical size: ~50KB.
|
||||
/// </summary>
|
||||
Light,
|
||||
|
||||
/// <summary>
|
||||
/// Include everything in Light mode plus all binary blobs referenced in predicates.
|
||||
/// Typical size: 50MB+.
|
||||
/// </summary>
|
||||
Full
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for controlling bundle export behavior.
|
||||
/// </summary>
|
||||
public sealed record BundleBuilderOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Export mode (Light = metadata only, Full = metadata + binary blobs).
|
||||
/// </summary>
|
||||
public BundleExportMode Mode { get; init; } = BundleExportMode.Light;
|
||||
|
||||
/// <summary>
|
||||
/// Skip blobs larger than this threshold in Full mode (null = no limit).
|
||||
/// </summary>
|
||||
public long? MaxBlobSizeBytes { get; init; }
|
||||
}
|
||||
@@ -138,6 +138,22 @@ public enum BundleArtifactType
|
||||
[JsonPropertyName("rekor.checkpoint")]
|
||||
RekorCheckpoint,
|
||||
|
||||
/// <summary>Function map predicate (runtime→static linkage).</summary>
|
||||
[JsonPropertyName("function-map")]
|
||||
FunctionMap,
|
||||
|
||||
/// <summary>DSSE-signed function map statement.</summary>
|
||||
[JsonPropertyName("function-map.dsse")]
|
||||
FunctionMapDsse,
|
||||
|
||||
/// <summary>Runtime observations data (NDJSON).</summary>
|
||||
[JsonPropertyName("observations")]
|
||||
Observations,
|
||||
|
||||
/// <summary>Verification report (function map verification result).</summary>
|
||||
[JsonPropertyName("verification-report")]
|
||||
VerificationReport,
|
||||
|
||||
/// <summary>Other/generic artifact.</summary>
|
||||
[JsonPropertyName("other")]
|
||||
Other
|
||||
|
||||
@@ -25,6 +25,12 @@ public sealed record BundleManifest
|
||||
public long TotalSizeBytes { get; init; }
|
||||
public string? BundleDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Export mode indicator: "light" or "full".
|
||||
/// Sprint: SPRINT_20260122_040 (040-04)
|
||||
/// </summary>
|
||||
public string? ExportMode { get; init; }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// v2.0.0 Additions - Sprint: SPRINT_20260118_018 (TASK-018-001)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -70,6 +70,11 @@ public sealed class BundleValidationOptions
|
||||
/// Whether to validate crypto provider entries if present.
|
||||
/// </summary>
|
||||
public bool ValidateCryptoProviders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to validate artifact digests (function maps, observations, verification reports).
|
||||
/// </summary>
|
||||
public bool ValidateArtifacts { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -207,6 +207,7 @@ public sealed class BundleBuilder : IBundleBuilder
|
||||
timestampSizeBytes +
|
||||
artifactsSizeBytes;
|
||||
|
||||
var exportMode = request.ExportOptions?.Mode ?? BundleExportMode.Light;
|
||||
var manifest = new BundleManifest
|
||||
{
|
||||
BundleId = _guidProvider.NewGuid().ToString(),
|
||||
@@ -221,6 +222,7 @@ public sealed class BundleBuilder : IBundleBuilder
|
||||
RuleBundles = ruleBundles.ToImmutableArray(),
|
||||
Timestamps = timestamps.ToImmutableArray(),
|
||||
Artifacts = artifacts.ToImmutableArray(),
|
||||
ExportMode = exportMode.ToString().ToLowerInvariant(),
|
||||
TotalSizeBytes = totalSize
|
||||
};
|
||||
|
||||
@@ -564,7 +566,8 @@ public sealed record BundleBuildRequest(
|
||||
IReadOnlyList<TimestampBuildConfig>? Timestamps = null,
|
||||
IReadOnlyList<BundleArtifactBuildConfig>? Artifacts = null,
|
||||
bool StrictInlineArtifacts = false,
|
||||
ICollection<string>? WarningSink = null);
|
||||
ICollection<string>? WarningSink = null,
|
||||
BundleBuilderOptions? ExportOptions = null);
|
||||
|
||||
public abstract record BundleComponentSource(string SourcePath, string RelativePath);
|
||||
|
||||
|
||||
@@ -104,6 +104,40 @@ public sealed class BundleValidator : IBundleValidator
|
||||
}
|
||||
}
|
||||
|
||||
// Validate artifact digests (function maps, observations, verification reports)
|
||||
if (_options.ValidateArtifacts && manifest.Artifacts.Length > 0)
|
||||
{
|
||||
foreach (var artifact in manifest.Artifacts)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(artifact.Path))
|
||||
{
|
||||
continue; // Inline artifact without path
|
||||
}
|
||||
|
||||
if (!PathValidation.IsSafeRelativePath(artifact.Path))
|
||||
{
|
||||
errors.Add(new BundleValidationError("Artifacts",
|
||||
$"Artifact '{artifact.Type}' has unsafe relative path: {artifact.Path}"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artifact.Digest))
|
||||
{
|
||||
warnings.Add(new BundleValidationWarning("Artifacts",
|
||||
$"Artifact '{artifact.Type}' at '{artifact.Path}' has no digest"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var filePath = PathValidation.SafeCombine(bundlePath, artifact.Path);
|
||||
var result = await VerifyFileDigestAsync(filePath, NormalizeDigest(artifact.Digest), ct).ConfigureAwait(false);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
errors.Add(new BundleValidationError("Artifacts",
|
||||
$"Artifact '{artifact.Type}' at '{artifact.Path}' digest mismatch: expected {artifact.Digest}, got {result.ActualDigest}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check bundle expiration
|
||||
if (manifest.ExpiresAt.HasValue && manifest.ExpiresAt.Value < now)
|
||||
{
|
||||
@@ -159,6 +193,14 @@ public sealed class BundleValidator : IBundleValidator
|
||||
return (string.Equals(actualDigest, expectedDigest, StringComparison.OrdinalIgnoreCase), actualDigest);
|
||||
}
|
||||
|
||||
private static string NormalizeDigest(string digest)
|
||||
{
|
||||
// Strip "sha256:" prefix if present for comparison with raw hex
|
||||
return digest.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase)
|
||||
? digest[7..]
|
||||
: digest;
|
||||
}
|
||||
|
||||
private static string ComputeBundleDigest(BundleManifest manifest)
|
||||
{
|
||||
var withoutDigest = manifest with { BundleDigest = null };
|
||||
|
||||
Reference in New Issue
Block a user