Refactor code structure for improved readability and maintainability
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-06 21:48:12 +02:00
parent f6c22854a4
commit dd0067ea0b
105 changed files with 12662 additions and 427 deletions

View File

@@ -0,0 +1,15 @@
namespace StellaOps.TaskRunner.Core.AirGap;
/// <summary>
/// Provider for retrieving air-gap sealed mode status.
/// </summary>
public interface IAirGapStatusProvider
{
/// <summary>
/// Gets the current sealed mode status of the environment.
/// </summary>
/// <param name="tenantId">Optional tenant ID for multi-tenant environments.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The sealed mode status.</returns>
Task<SealedModeStatus> GetStatusAsync(string? tenantId = null, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,125 @@
using StellaOps.TaskRunner.Core.Events;
using StellaOps.TaskRunner.Core.TaskPacks;
namespace StellaOps.TaskRunner.Core.AirGap;
/// <summary>
/// Audit logger for sealed install enforcement decisions.
/// </summary>
public interface ISealedInstallAuditLogger
{
/// <summary>
/// Logs an enforcement decision.
/// </summary>
Task LogEnforcementAsync(
TaskPackManifest manifest,
SealedInstallEnforcementResult result,
string? tenantId = null,
string? runId = null,
string? actor = null,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Implementation of sealed install audit logger using timeline events.
/// </summary>
public sealed class SealedInstallAuditLogger : ISealedInstallAuditLogger
{
private readonly IPackRunTimelineEventEmitter _eventEmitter;
public SealedInstallAuditLogger(IPackRunTimelineEventEmitter eventEmitter)
{
_eventEmitter = eventEmitter ?? throw new ArgumentNullException(nameof(eventEmitter));
}
/// <inheritdoc />
public async Task LogEnforcementAsync(
TaskPackManifest manifest,
SealedInstallEnforcementResult result,
string? tenantId = null,
string? runId = null,
string? actor = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(manifest);
ArgumentNullException.ThrowIfNull(result);
var effectiveTenantId = tenantId ?? "default";
var effectiveRunId = runId ?? Guid.NewGuid().ToString("n");
var now = DateTimeOffset.UtcNow;
var eventType = result.Allowed
? PackRunEventTypes.SealedInstallAllowed
: PackRunEventTypes.SealedInstallDenied;
var severity = result.Allowed
? PackRunEventSeverity.Info
: PackRunEventSeverity.Warning;
var attributes = new Dictionary<string, string>(StringComparer.Ordinal)
{
["pack_name"] = manifest.Metadata.Name,
["pack_version"] = manifest.Metadata.Version,
["decision"] = result.Allowed ? "allowed" : "denied",
["sealed_install_required"] = manifest.Spec.SealedInstall.ToString().ToLowerInvariant()
};
if (!string.IsNullOrWhiteSpace(result.ErrorCode))
{
attributes["error_code"] = result.ErrorCode;
}
object payload;
if (result.Allowed)
{
payload = new
{
event_type = "sealed_install_enforcement",
pack_id = manifest.Metadata.Name,
pack_version = manifest.Metadata.Version,
decision = "allowed",
reason = result.Message
};
}
else
{
payload = new
{
event_type = "sealed_install_enforcement",
pack_id = manifest.Metadata.Name,
pack_version = manifest.Metadata.Version,
decision = "denied",
reason = result.ErrorCode,
message = result.Message,
violation = result.Violation is not null
? new
{
required_sealed = result.Violation.RequiredSealed,
actual_sealed = result.Violation.ActualSealed,
recommendation = result.Violation.Recommendation
}
: null,
requirement_violations = result.RequirementViolations?.Select(v => new
{
requirement = v.Requirement,
expected = v.Expected,
actual = v.Actual,
message = v.Message
}).ToList()
};
}
var timelineEvent = PackRunTimelineEvent.Create(
tenantId: effectiveTenantId,
eventType: eventType,
source: "StellaOps.TaskRunner.SealedInstallEnforcer",
occurredAt: now,
runId: effectiveRunId,
actor: actor,
severity: severity,
attributes: attributes,
payload: payload);
await _eventEmitter.EmitAsync(timelineEvent, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,22 @@
using StellaOps.TaskRunner.Core.TaskPacks;
namespace StellaOps.TaskRunner.Core.AirGap;
/// <summary>
/// Enforces sealed install requirements for task packs.
/// Per sealed-install-enforcement.md contract.
/// </summary>
public interface ISealedInstallEnforcer
{
/// <summary>
/// Enforces sealed install requirements for a task pack.
/// </summary>
/// <param name="manifest">The task pack manifest.</param>
/// <param name="tenantId">Optional tenant ID.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Enforcement result indicating whether execution is allowed.</returns>
Task<SealedInstallEnforcementResult> EnforceAsync(
TaskPackManifest manifest,
string? tenantId = null,
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,118 @@
namespace StellaOps.TaskRunner.Core.AirGap;
/// <summary>
/// Result of sealed install enforcement check.
/// Per sealed-install-enforcement.md contract.
/// </summary>
public sealed record SealedInstallEnforcementResult(
/// <summary>Whether execution is allowed.</summary>
bool Allowed,
/// <summary>Error code if denied.</summary>
string? ErrorCode,
/// <summary>Human-readable message.</summary>
string Message,
/// <summary>Detailed violation information.</summary>
SealedInstallViolation? Violation,
/// <summary>Requirement violations if any.</summary>
IReadOnlyList<RequirementViolation>? RequirementViolations)
{
/// <summary>
/// Creates an allowed result.
/// </summary>
public static SealedInstallEnforcementResult CreateAllowed(string message)
=> new(true, null, message, null, null);
/// <summary>
/// Creates a denied result.
/// </summary>
public static SealedInstallEnforcementResult CreateDenied(
string errorCode,
string message,
SealedInstallViolation? violation = null,
IReadOnlyList<RequirementViolation>? requirementViolations = null)
=> new(false, errorCode, message, violation, requirementViolations);
}
/// <summary>
/// Details about a sealed install violation.
/// </summary>
public sealed record SealedInstallViolation(
/// <summary>Pack ID that requires sealed install.</summary>
string PackId,
/// <summary>Pack version.</summary>
string? PackVersion,
/// <summary>Whether pack requires sealed install.</summary>
bool RequiredSealed,
/// <summary>Actual sealed status of environment.</summary>
bool ActualSealed,
/// <summary>Recommendation for resolving the violation.</summary>
string Recommendation);
/// <summary>
/// Details about a requirement violation.
/// </summary>
public sealed record RequirementViolation(
/// <summary>Name of the requirement that was violated.</summary>
string Requirement,
/// <summary>Expected value.</summary>
string Expected,
/// <summary>Actual value.</summary>
string Actual,
/// <summary>Human-readable message describing the violation.</summary>
string Message);
/// <summary>
/// Error codes for sealed install enforcement.
/// </summary>
public static class SealedInstallErrorCodes
{
/// <summary>Pack requires sealed but environment is not sealed.</summary>
public const string SealedInstallViolation = "SEALED_INSTALL_VIOLATION";
/// <summary>Sealed requirements not met.</summary>
public const string SealedRequirementsViolation = "SEALED_REQUIREMENTS_VIOLATION";
/// <summary>Bundle version below minimum required.</summary>
public const string BundleVersionViolation = "BUNDLE_VERSION_VIOLATION";
/// <summary>Advisory data too stale.</summary>
public const string AdvisoryStalenessViolation = "ADVISORY_STALENESS_VIOLATION";
/// <summary>Time anchor missing or invalid.</summary>
public const string TimeAnchorViolation = "TIME_ANCHOR_VIOLATION";
/// <summary>Bundle signature verification failed.</summary>
public const string SignatureVerificationViolation = "SIGNATURE_VERIFICATION_VIOLATION";
}
/// <summary>
/// CLI exit codes for sealed install enforcement.
/// </summary>
public static class SealedInstallExitCodes
{
/// <summary>Pack requires sealed but environment is not.</summary>
public const int SealedInstallViolation = 40;
/// <summary>Bundle version below minimum.</summary>
public const int BundleVersionViolation = 41;
/// <summary>Advisory data too stale.</summary>
public const int AdvisoryStalenessViolation = 42;
/// <summary>Time anchor missing or invalid.</summary>
public const int TimeAnchorViolation = 43;
/// <summary>Bundle signature verification failed.</summary>
public const int SignatureVerificationViolation = 44;
}

View File

@@ -0,0 +1,297 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.TaskRunner.Core.TaskPacks;
namespace StellaOps.TaskRunner.Core.AirGap;
/// <summary>
/// Enforces sealed install requirements for task packs.
/// Per sealed-install-enforcement.md contract.
/// </summary>
public sealed class SealedInstallEnforcer : ISealedInstallEnforcer
{
private readonly IAirGapStatusProvider _statusProvider;
private readonly IOptions<SealedInstallEnforcementOptions> _options;
private readonly ILogger<SealedInstallEnforcer> _logger;
public SealedInstallEnforcer(
IAirGapStatusProvider statusProvider,
IOptions<SealedInstallEnforcementOptions> options,
ILogger<SealedInstallEnforcer> logger)
{
_statusProvider = statusProvider ?? throw new ArgumentNullException(nameof(statusProvider));
_options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public async Task<SealedInstallEnforcementResult> EnforceAsync(
TaskPackManifest manifest,
string? tenantId = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(manifest);
var options = _options.Value;
// Check if enforcement is enabled
if (!options.Enabled)
{
_logger.LogDebug("Sealed install enforcement is disabled.");
return SealedInstallEnforcementResult.CreateAllowed("Enforcement disabled");
}
// Check for development bypass
if (options.BypassForDevelopment && IsDevelopmentEnvironment())
{
_logger.LogWarning("Sealed install enforcement bypassed for development environment.");
return SealedInstallEnforcementResult.CreateAllowed("Development bypass active");
}
// If pack doesn't require sealed install, allow
if (!manifest.Spec.SealedInstall)
{
_logger.LogDebug(
"Pack {PackName} v{PackVersion} does not require sealed install.",
manifest.Metadata.Name,
manifest.Metadata.Version);
return SealedInstallEnforcementResult.CreateAllowed("Pack does not require sealed install");
}
// Get environment sealed status
SealedModeStatus status;
try
{
status = await _statusProvider.GetStatusAsync(tenantId, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get air-gap status. Denying sealed install pack.");
return SealedInstallEnforcementResult.CreateDenied(
SealedInstallErrorCodes.SealedInstallViolation,
"Failed to verify sealed mode status",
new SealedInstallViolation(
manifest.Metadata.Name,
manifest.Metadata.Version,
RequiredSealed: true,
ActualSealed: false,
Recommendation: "Ensure the AirGap controller is accessible: stella airgap status"));
}
// Core check: environment must be sealed
if (!status.Sealed)
{
_logger.LogWarning(
"Sealed install violation: Pack {PackName} v{PackVersion} requires sealed environment but environment is {Mode}.",
manifest.Metadata.Name,
manifest.Metadata.Version,
status.Mode);
return SealedInstallEnforcementResult.CreateDenied(
SealedInstallErrorCodes.SealedInstallViolation,
"Pack requires sealed environment but environment is not sealed",
new SealedInstallViolation(
manifest.Metadata.Name,
manifest.Metadata.Version,
RequiredSealed: true,
ActualSealed: false,
Recommendation: "Activate sealed mode with: stella airgap seal"));
}
// Check sealed requirements if specified
var requirements = manifest.Spec.SealedRequirements ?? SealedRequirements.Default;
var violations = ValidateRequirements(requirements, status, options);
if (violations.Count > 0)
{
_logger.LogWarning(
"Sealed requirements violation for pack {PackName} v{PackVersion}: {ViolationCount} requirement(s) not met.",
manifest.Metadata.Name,
manifest.Metadata.Version,
violations.Count);
return SealedInstallEnforcementResult.CreateDenied(
SealedInstallErrorCodes.SealedRequirementsViolation,
"Sealed requirements not met",
violation: null,
requirementViolations: violations);
}
_logger.LogInformation(
"Sealed install requirements satisfied for pack {PackName} v{PackVersion}.",
manifest.Metadata.Name,
manifest.Metadata.Version);
return SealedInstallEnforcementResult.CreateAllowed("Sealed install requirements satisfied");
}
private List<RequirementViolation> ValidateRequirements(
SealedRequirements requirements,
SealedModeStatus status,
SealedInstallEnforcementOptions options)
{
var violations = new List<RequirementViolation>();
// Bundle version check
if (!string.IsNullOrWhiteSpace(requirements.MinBundleVersion) &&
!string.IsNullOrWhiteSpace(status.BundleVersion))
{
if (!IsVersionSatisfied(status.BundleVersion, requirements.MinBundleVersion))
{
violations.Add(new RequirementViolation(
Requirement: "min_bundle_version",
Expected: requirements.MinBundleVersion,
Actual: status.BundleVersion,
Message: $"Bundle version {status.BundleVersion} < required {requirements.MinBundleVersion}"));
}
}
// Advisory staleness check
var effectiveStaleness = status.AdvisoryStalenessHours;
var maxStaleness = requirements.MaxAdvisoryStalenessHours;
// Apply grace period if configured
if (options.StalenessGracePeriodHours > 0)
{
maxStaleness += options.StalenessGracePeriodHours;
}
if (effectiveStaleness > maxStaleness)
{
if (options.DenyOnStaleness)
{
violations.Add(new RequirementViolation(
Requirement: "max_advisory_staleness_hours",
Expected: requirements.MaxAdvisoryStalenessHours.ToString(),
Actual: effectiveStaleness.ToString(),
Message: $"Advisory data is {effectiveStaleness}h old, max allowed is {requirements.MaxAdvisoryStalenessHours}h"));
}
else if (effectiveStaleness > options.StalenessWarningThresholdHours)
{
_logger.LogWarning(
"Advisory data is {Staleness}h old, approaching max allowed {MaxStaleness}h.",
effectiveStaleness,
requirements.MaxAdvisoryStalenessHours);
}
}
// Time anchor check
if (requirements.RequireTimeAnchor)
{
if (status.TimeAnchor is null)
{
violations.Add(new RequirementViolation(
Requirement: "require_time_anchor",
Expected: "valid time anchor",
Actual: "missing",
Message: "Valid time anchor required but not present"));
}
else if (!status.TimeAnchor.Valid)
{
violations.Add(new RequirementViolation(
Requirement: "require_time_anchor",
Expected: "valid time anchor",
Actual: "invalid",
Message: "Time anchor present but invalid or expired"));
}
else if (status.TimeAnchor.ExpiresAt.HasValue &&
status.TimeAnchor.ExpiresAt.Value < DateTimeOffset.UtcNow)
{
violations.Add(new RequirementViolation(
Requirement: "require_time_anchor",
Expected: "non-expired time anchor",
Actual: $"expired at {status.TimeAnchor.ExpiresAt.Value:O}",
Message: "Time anchor has expired"));
}
}
return violations;
}
private static bool IsVersionSatisfied(string actual, string required)
{
// Try semantic version comparison
if (Version.TryParse(NormalizeVersion(actual), out var actualVersion) &&
Version.TryParse(NormalizeVersion(required), out var requiredVersion))
{
return actualVersion >= requiredVersion;
}
// Fall back to string comparison
return string.Compare(actual, required, StringComparison.OrdinalIgnoreCase) >= 0;
}
private static string NormalizeVersion(string version)
{
// Strip common prefixes like 'v' and suffixes like '-beta'
var normalized = version.TrimStart('v', 'V');
var dashIndex = normalized.IndexOf('-', StringComparison.Ordinal);
if (dashIndex > 0)
{
normalized = normalized[..dashIndex];
}
return normalized;
}
private static bool IsDevelopmentEnvironment()
{
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ??
Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
return string.Equals(env, "Development", StringComparison.OrdinalIgnoreCase);
}
}
/// <summary>
/// Configuration options for sealed install enforcement.
/// </summary>
public sealed class SealedInstallEnforcementOptions
{
/// <summary>
/// Whether enforcement is enabled.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Grace period for advisory staleness in hours.
/// </summary>
public int StalenessGracePeriodHours { get; set; } = 24;
/// <summary>
/// Warning threshold for staleness in hours.
/// </summary>
public int StalenessWarningThresholdHours { get; set; } = 120;
/// <summary>
/// Whether to deny on staleness violation (false = warn only).
/// </summary>
public bool DenyOnStaleness { get; set; } = true;
/// <summary>
/// Whether to use heuristic detection when AirGap controller is unavailable.
/// </summary>
public bool UseHeuristicDetection { get; set; } = true;
/// <summary>
/// Heuristic score threshold to consider environment sealed.
/// </summary>
public double HeuristicThreshold { get; set; } = 0.7;
/// <summary>
/// Bypass enforcement in development environments (DANGEROUS).
/// </summary>
public bool BypassForDevelopment { get; set; }
/// <summary>
/// Log all enforcement decisions.
/// </summary>
public bool LogAllDecisions { get; set; } = true;
/// <summary>
/// Audit retention in days.
/// </summary>
public int AuditRetentionDays { get; set; } = 365;
}

View File

@@ -0,0 +1,88 @@
namespace StellaOps.TaskRunner.Core.AirGap;
/// <summary>
/// Represents the sealed mode status of the air-gap environment.
/// Per sealed-install-enforcement.md contract.
/// </summary>
public sealed record SealedModeStatus(
/// <summary>Whether the environment is currently sealed.</summary>
bool Sealed,
/// <summary>Current mode (sealed, unsealed, transitioning).</summary>
string Mode,
/// <summary>When the environment was sealed.</summary>
DateTimeOffset? SealedAt,
/// <summary>Identity that sealed the environment.</summary>
string? SealedBy,
/// <summary>Air-gap bundle version currently installed.</summary>
string? BundleVersion,
/// <summary>Digest of the bundle.</summary>
string? BundleDigest,
/// <summary>When advisories were last updated.</summary>
DateTimeOffset? LastAdvisoryUpdate,
/// <summary>Hours since last advisory update.</summary>
int AdvisoryStalenessHours,
/// <summary>Time anchor information.</summary>
TimeAnchorInfo? TimeAnchor,
/// <summary>Whether egress is blocked.</summary>
bool EgressBlocked,
/// <summary>Network policy in effect.</summary>
string? NetworkPolicy)
{
/// <summary>
/// Creates an unsealed status (environment not in air-gap mode).
/// </summary>
public static SealedModeStatus Unsealed() => new(
Sealed: false,
Mode: "unsealed",
SealedAt: null,
SealedBy: null,
BundleVersion: null,
BundleDigest: null,
LastAdvisoryUpdate: null,
AdvisoryStalenessHours: 0,
TimeAnchor: null,
EgressBlocked: false,
NetworkPolicy: null);
/// <summary>
/// Creates a status indicating the provider is unavailable.
/// </summary>
public static SealedModeStatus Unavailable() => new(
Sealed: false,
Mode: "unavailable",
SealedAt: null,
SealedBy: null,
BundleVersion: null,
BundleDigest: null,
LastAdvisoryUpdate: null,
AdvisoryStalenessHours: 0,
TimeAnchor: null,
EgressBlocked: false,
NetworkPolicy: null);
}
/// <summary>
/// Time anchor information for sealed environments.
/// </summary>
public sealed record TimeAnchorInfo(
/// <summary>The anchor timestamp.</summary>
DateTimeOffset Timestamp,
/// <summary>Signature of the time anchor.</summary>
string? Signature,
/// <summary>Whether the time anchor is valid.</summary>
bool Valid,
/// <summary>When the time anchor expires.</summary>
DateTimeOffset? ExpiresAt);

View File

@@ -0,0 +1,39 @@
using System.Text.Json.Serialization;
namespace StellaOps.TaskRunner.Core.AirGap;
/// <summary>
/// Sealed install requirements specified in a task pack manifest.
/// Per sealed-install-enforcement.md contract.
/// </summary>
public sealed record SealedRequirements(
/// <summary>Minimum air-gap bundle version required.</summary>
[property: JsonPropertyName("min_bundle_version")]
string? MinBundleVersion,
/// <summary>Maximum age of advisory data in hours (default: 168).</summary>
[property: JsonPropertyName("max_advisory_staleness_hours")]
int MaxAdvisoryStalenessHours,
/// <summary>Whether a valid time anchor is required (default: true).</summary>
[property: JsonPropertyName("require_time_anchor")]
bool RequireTimeAnchor,
/// <summary>Maximum allowed offline duration in hours (default: 720).</summary>
[property: JsonPropertyName("allowed_offline_duration_hours")]
int AllowedOfflineDurationHours,
/// <summary>Whether bundle signature verification is required (default: true).</summary>
[property: JsonPropertyName("require_signature_verification")]
bool RequireSignatureVerification)
{
/// <summary>
/// Default sealed requirements.
/// </summary>
public static SealedRequirements Default => new(
MinBundleVersion: null,
MaxAdvisoryStalenessHours: 168,
RequireTimeAnchor: true,
AllowedOfflineDurationHours: 720,
RequireSignatureVerification: true);
}

View File

@@ -301,6 +301,18 @@ public static class PackRunEventTypes
/// <summary>Policy gate evaluated.</summary>
public const string PolicyEvaluated = "pack.policy.evaluated";
/// <summary>Sealed install enforcement performed.</summary>
public const string SealedInstallEnforcement = "pack.sealed_install.enforcement";
/// <summary>Sealed install enforcement denied execution.</summary>
public const string SealedInstallDenied = "pack.sealed_install.denied";
/// <summary>Sealed install enforcement allowed execution.</summary>
public const string SealedInstallAllowed = "pack.sealed_install.allowed";
/// <summary>Sealed install requirements warning.</summary>
public const string SealedInstallWarning = "pack.sealed_install.warning";
/// <summary>Checks if the event type is a pack run event.</summary>
public static bool IsPackRunEvent(string eventType) =>
eventType.StartsWith(Prefix, StringComparison.Ordinal);

View File

@@ -2,9 +2,9 @@ using System.Diagnostics.Metrics;
namespace StellaOps.TaskRunner.Core.Execution;
internal static class TaskRunnerTelemetry
public static class TaskRunnerTelemetry
{
internal const string MeterName = "stellaops.taskrunner";
public const string MeterName = "stellaops.taskrunner";
internal static readonly Meter Meter = new(MeterName);
internal static readonly Histogram<double> StepDurationMs =

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using StellaOps.TaskRunner.Core.AirGap;
namespace StellaOps.TaskRunner.Core.TaskPacks;
@@ -82,6 +83,18 @@ public sealed class TaskPackSpec
[JsonPropertyName("slo")]
public TaskPackSlo? Slo { get; init; }
/// <summary>
/// Whether this pack requires a sealed (air-gapped) environment.
/// </summary>
[JsonPropertyName("sealedInstall")]
public bool SealedInstall { get; init; }
/// <summary>
/// Specific requirements for sealed install mode.
/// </summary>
[JsonPropertyName("sealedRequirements")]
public SealedRequirements? SealedRequirements { get; init; }
}
public sealed class TaskPackInput