feat(api): Implement Console Export Client and Models
Some checks failed
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
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
mock-dev-release / package-mock-release (push) Has been cancelled

- Added ConsoleExportClient for managing export requests and responses.
- Introduced ConsoleExportRequest and ConsoleExportResponse models.
- Implemented methods for creating and retrieving exports with appropriate headers.

feat(crypto): Add Software SM2/SM3 Cryptography Provider

- Implemented SmSoftCryptoProvider for software-only SM2/SM3 cryptography.
- Added support for signing and verification using SM2 algorithm.
- Included hashing functionality with SM3 algorithm.
- Configured options for loading keys from files and environment gate checks.

test(crypto): Add unit tests for SmSoftCryptoProvider

- Created comprehensive tests for signing, verifying, and hashing functionalities.
- Ensured correct behavior for key management and error handling.

feat(api): Enhance Console Export Models

- Expanded ConsoleExport models to include detailed status and event types.
- Added support for various export formats and notification options.

test(time): Implement TimeAnchorPolicyService tests

- Developed tests for TimeAnchorPolicyService to validate time anchors.
- Covered scenarios for anchor validation, drift calculation, and policy enforcement.
This commit is contained in:
StellaOps Bot
2025-12-07 00:27:33 +02:00
parent 9bd6a73926
commit 0de92144d2
229 changed files with 32351 additions and 1481 deletions

View File

@@ -0,0 +1,83 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Concelier.Core.Orchestration;
using StellaOps.Excititor.Worker.Scheduling;
namespace StellaOps.Excititor.Worker.Orchestration;
/// <summary>
/// Service collection extensions for Excititor orchestrator integration.
/// Per EXCITITOR-ORCH-32/33: Adopt orchestrator worker SDK.
/// </summary>
public static class ExcititorOrchestrationExtensions
{
/// <summary>
/// Adds orchestrator-integrated VEX worker services.
/// This wraps the existing provider runner with orchestrator SDK calls
/// for heartbeats, progress, and pause/throttle handling.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddExcititorOrchestration(this IServiceCollection services)
{
// Add the Concelier orchestration services (registry, worker factory, backfill)
services.AddConcelierOrchestrationServices();
// Register the orchestrator-aware provider runner as a decorator
// This preserves the existing IVexProviderRunner implementation and wraps it
services.Decorate<IVexProviderRunner, OrchestratorVexProviderRunner>();
return services;
}
}
/// <summary>
/// Extension methods for service decoration pattern.
/// </summary>
internal static class ServiceCollectionDecoratorExtensions
{
/// <summary>
/// Decorates an existing service registration with a decorator implementation.
/// </summary>
/// <typeparam name="TService">The service interface type.</typeparam>
/// <typeparam name="TDecorator">The decorator type that wraps TService.</typeparam>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection Decorate<TService, TDecorator>(this IServiceCollection services)
where TService : class
where TDecorator : class, TService
{
// Find the existing registration
var existingDescriptor = services.FirstOrDefault(d => d.ServiceType == typeof(TService));
if (existingDescriptor is null)
{
throw new InvalidOperationException(
$"Cannot decorate service {typeof(TService).Name}: no existing registration found.");
}
// Remove the original registration
services.Remove(existingDescriptor);
// Create a factory that gets the original implementation and wraps it
services.Add(ServiceDescriptor.Describe(
typeof(TService),
sp =>
{
// Resolve the original implementation
var innerFactory = existingDescriptor.ImplementationFactory;
var inner = innerFactory is not null
? (TService)innerFactory(sp)
: existingDescriptor.ImplementationType is not null
? (TService)ActivatorUtilities.CreateInstance(sp, existingDescriptor.ImplementationType)
: existingDescriptor.ImplementationInstance is not null
? (TService)existingDescriptor.ImplementationInstance
: throw new InvalidOperationException("Cannot resolve inner service.");
// Create the decorator with the inner instance
return ActivatorUtilities.CreateInstance<TDecorator>(sp, inner);
},
existingDescriptor.Lifetime));
return services;
}
}

View File

@@ -0,0 +1,140 @@
using Microsoft.Extensions.Logging;
using StellaOps.Concelier.Core.Orchestration;
using StellaOps.Excititor.Worker.Scheduling;
namespace StellaOps.Excititor.Worker.Orchestration;
/// <summary>
/// Orchestrator-integrated VEX provider runner.
/// Per EXCITITOR-ORCH-32/33: Adopt orchestrator worker SDK; honor pause/throttle/retry with deterministic checkpoints.
/// </summary>
internal sealed class OrchestratorVexProviderRunner : IVexProviderRunner
{
private readonly IVexProviderRunner _inner;
private readonly IConnectorWorkerFactory _workerFactory;
private readonly ILogger<OrchestratorVexProviderRunner> _logger;
private readonly TimeProvider _timeProvider;
public OrchestratorVexProviderRunner(
IVexProviderRunner inner,
IConnectorWorkerFactory workerFactory,
ILogger<OrchestratorVexProviderRunner> logger,
TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(inner);
ArgumentNullException.ThrowIfNull(workerFactory);
ArgumentNullException.ThrowIfNull(logger);
ArgumentNullException.ThrowIfNull(timeProvider);
_inner = inner;
_workerFactory = workerFactory;
_logger = logger;
_timeProvider = timeProvider;
}
public async ValueTask RunAsync(VexWorkerSchedule schedule, CancellationToken cancellationToken)
{
// Derive tenant from schedule (default to global tenant if not specified)
var tenant = schedule.Tenant ?? "global";
var connectorId = $"excititor-{schedule.ProviderId}".ToLowerInvariant();
var worker = _workerFactory.CreateWorker(tenant, connectorId);
try
{
// Start the orchestrator-tracked run
await worker.StartRunAsync(cancellationToken).ConfigureAwait(false);
_logger.LogInformation(
"Orchestrator run {RunId} started for VEX provider {ProviderId}",
worker.RunId,
schedule.ProviderId);
// Check for pause/throttle before starting actual work
if (!await worker.CheckContinueAsync(cancellationToken).ConfigureAwait(false))
{
_logger.LogInformation(
"Orchestrator run {RunId} paused before execution for {ProviderId}",
worker.RunId,
schedule.ProviderId);
return;
}
// Apply any active throttle
var throttle = worker.GetActiveThrottle();
if (throttle is not null)
{
_logger.LogInformation(
"Applying throttle override for {ProviderId}: RPM={Rpm}",
schedule.ProviderId,
throttle.Rpm);
}
// Report initial progress
await worker.ReportProgressAsync(0, cancellationToken: cancellationToken).ConfigureAwait(false);
// Execute the actual provider run
var startTime = _timeProvider.GetUtcNow();
await _inner.RunAsync(schedule, cancellationToken).ConfigureAwait(false);
var elapsed = _timeProvider.GetUtcNow() - startTime;
// Report completion
await worker.ReportProgressAsync(100, cancellationToken: cancellationToken).ConfigureAwait(false);
await worker.CompleteSuccessAsync(cancellationToken).ConfigureAwait(false);
_logger.LogInformation(
"Orchestrator run {RunId} completed successfully for {ProviderId} in {Duration}",
worker.RunId,
schedule.ProviderId,
elapsed);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
_logger.LogInformation(
"Orchestrator run {RunId} cancelled for {ProviderId}",
worker.RunId,
schedule.ProviderId);
throw;
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Orchestrator run {RunId} failed for {ProviderId}: {Message}",
worker.RunId,
schedule.ProviderId,
ex.Message);
// Report failure to orchestrator with retry suggestion
await worker.CompleteFailureAsync(
GetErrorCode(ex),
GetRetryAfterSeconds(ex),
cancellationToken).ConfigureAwait(false);
throw;
}
}
private static string GetErrorCode(Exception ex)
{
return ex switch
{
HttpRequestException => "HTTP_ERROR",
TimeoutException => "TIMEOUT",
InvalidOperationException => "INVALID_OPERATION",
_ => "UNKNOWN_ERROR"
};
}
private static int? GetRetryAfterSeconds(Exception ex)
{
// Suggest retry delays based on error type
return ex switch
{
HttpRequestException => 60, // Network issues: retry after 1 minute
TimeoutException => 120, // Timeout: retry after 2 minutes
_ => 300 // Unknown: retry after 5 minutes
};
}
}

View File

@@ -0,0 +1,156 @@
using StellaOps.Concelier.Core.Orchestration;
namespace StellaOps.Excititor.Worker.Orchestration;
/// <summary>
/// Metadata for well-known VEX connectors.
/// Per EXCITITOR-ORCH-32: Register VEX connectors with orchestrator.
/// </summary>
public static class VexConnectorMetadata
{
/// <summary>
/// Red Hat CSAF connector metadata.
/// </summary>
public static ConnectorMetadata RedHatCsaf => new()
{
ConnectorId = "excititor-redhat-csaf",
Source = "redhat-csaf",
DisplayName = "Red Hat CSAF",
Description = "Red Hat CSAF VEX documents",
Capabilities = ["observations", "linksets"],
ArtifactKinds = ["raw-vex", "normalized", "linkset"],
DefaultCron = "0 */6 * * *", // Every 6 hours
DefaultRpm = 60,
EgressAllowlist = ["access.redhat.com", "www.redhat.com"]
};
/// <summary>
/// SUSE Rancher VEX Hub connector metadata.
/// </summary>
public static ConnectorMetadata SuseRancherVexHub => new()
{
ConnectorId = "excititor-suse-rancher",
Source = "suse-rancher",
DisplayName = "SUSE Rancher VEX Hub",
Description = "SUSE Rancher VEX Hub documents",
Capabilities = ["observations", "linksets", "attestations"],
ArtifactKinds = ["raw-vex", "normalized", "linkset", "attestation"],
DefaultCron = "0 */4 * * *", // Every 4 hours
DefaultRpm = 100,
EgressAllowlist = ["rancher.com", "suse.com"]
};
/// <summary>
/// Ubuntu CSAF connector metadata.
/// </summary>
public static ConnectorMetadata UbuntuCsaf => new()
{
ConnectorId = "excititor-ubuntu-csaf",
Source = "ubuntu-csaf",
DisplayName = "Ubuntu CSAF",
Description = "Ubuntu CSAF VEX documents",
Capabilities = ["observations", "linksets"],
ArtifactKinds = ["raw-vex", "normalized", "linkset"],
DefaultCron = "0 */6 * * *", // Every 6 hours
DefaultRpm = 60,
EgressAllowlist = ["ubuntu.com", "canonical.com"]
};
/// <summary>
/// Oracle CSAF connector metadata.
/// </summary>
public static ConnectorMetadata OracleCsaf => new()
{
ConnectorId = "excititor-oracle-csaf",
Source = "oracle-csaf",
DisplayName = "Oracle CSAF",
Description = "Oracle CSAF VEX documents",
Capabilities = ["observations", "linksets"],
ArtifactKinds = ["raw-vex", "normalized", "linkset"],
DefaultCron = "0 */12 * * *", // Every 12 hours
DefaultRpm = 30,
EgressAllowlist = ["oracle.com"]
};
/// <summary>
/// Cisco CSAF connector metadata.
/// </summary>
public static ConnectorMetadata CiscoCsaf => new()
{
ConnectorId = "excititor-cisco-csaf",
Source = "cisco-csaf",
DisplayName = "Cisco CSAF",
Description = "Cisco CSAF VEX documents",
Capabilities = ["observations", "linksets"],
ArtifactKinds = ["raw-vex", "normalized", "linkset"],
DefaultCron = "0 */6 * * *", // Every 6 hours
DefaultRpm = 60,
EgressAllowlist = ["cisco.com", "tools.cisco.com"]
};
/// <summary>
/// Microsoft MSRC CSAF connector metadata.
/// </summary>
public static ConnectorMetadata MsrcCsaf => new()
{
ConnectorId = "excititor-msrc-csaf",
Source = "msrc-csaf",
DisplayName = "Microsoft MSRC CSAF",
Description = "Microsoft Security Response Center CSAF VEX documents",
Capabilities = ["observations", "linksets"],
ArtifactKinds = ["raw-vex", "normalized", "linkset"],
DefaultCron = "0 */6 * * *", // Every 6 hours
DefaultRpm = 30,
EgressAllowlist = ["microsoft.com", "msrc.microsoft.com"]
};
/// <summary>
/// OCI OpenVEX Attestation connector metadata.
/// </summary>
public static ConnectorMetadata OciOpenVexAttestation => new()
{
ConnectorId = "excititor-oci-openvex",
Source = "oci-openvex",
DisplayName = "OCI OpenVEX Attestations",
Description = "OpenVEX attestations from OCI registries",
Capabilities = ["observations", "attestations"],
ArtifactKinds = ["raw-vex", "attestation"],
DefaultCron = "0 */2 * * *", // Every 2 hours (frequently updated)
DefaultRpm = 100, // Higher rate for OCI registries
EgressAllowlist = [] // Configured per-registry
};
/// <summary>
/// Gets metadata for all well-known VEX connectors.
/// </summary>
public static IReadOnlyList<ConnectorMetadata> All =>
[
RedHatCsaf,
SuseRancherVexHub,
UbuntuCsaf,
OracleCsaf,
CiscoCsaf,
MsrcCsaf,
OciOpenVexAttestation
];
/// <summary>
/// Gets connector metadata by provider ID.
/// </summary>
/// <param name="providerId">The provider identifier.</param>
/// <returns>The connector metadata, or null if not found.</returns>
public static ConnectorMetadata? GetByProviderId(string providerId)
{
return providerId.ToLowerInvariant() switch
{
"redhat" or "redhat-csaf" => RedHatCsaf,
"suse" or "suse-rancher" or "rancher" => SuseRancherVexHub,
"ubuntu" or "ubuntu-csaf" => UbuntuCsaf,
"oracle" or "oracle-csaf" => OracleCsaf,
"cisco" or "cisco-csaf" => CiscoCsaf,
"msrc" or "msrc-csaf" or "microsoft" => MsrcCsaf,
"oci" or "oci-openvex" or "openvex" => OciOpenVexAttestation,
_ => null
};
}
}

View File

@@ -2,4 +2,17 @@ using StellaOps.Excititor.Core;
namespace StellaOps.Excititor.Worker.Scheduling;
internal sealed record VexWorkerSchedule(string ProviderId, TimeSpan Interval, TimeSpan InitialDelay, VexConnectorSettings Settings);
/// <summary>
/// Schedule configuration for a VEX provider worker.
/// </summary>
/// <param name="ProviderId">The provider identifier.</param>
/// <param name="Interval">The interval between runs.</param>
/// <param name="InitialDelay">The initial delay before the first run.</param>
/// <param name="Settings">The connector settings.</param>
/// <param name="Tenant">The tenant identifier (optional; defaults to global).</param>
internal sealed record VexWorkerSchedule(
string ProviderId,
TimeSpan Interval,
TimeSpan InitialDelay,
VexConnectorSettings Settings,
string? Tenant = null);

View File

@@ -12,11 +12,14 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../Concelier/__Libraries/StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Connectors.Abstractions/StellaOps.Excititor.Connectors.Abstractions.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Connectors.RedHat.CSAF/StellaOps.Excititor.Connectors.RedHat.CSAF.csproj" />
<!-- Temporarily commented out: RedHat CSAF connector blocked by missing Storage.Mongo project -->
<!-- <ProjectReference Include="../__Libraries/StellaOps.Excititor.Connectors.RedHat.CSAF/StellaOps.Excititor.Connectors.RedHat.CSAF.csproj" /> -->
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Policy/StellaOps.Excititor.Policy.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Storage.Mongo/StellaOps.Excititor.Storage.Mongo.csproj" />
<!-- Temporarily commented out: Storage.Mongo project not found -->
<!-- <ProjectReference Include="../__Libraries/StellaOps.Excititor.Storage.Mongo/StellaOps.Excititor.Storage.Mongo.csproj" /> -->
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Formats.CSAF/StellaOps.Excititor.Formats.CSAF.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Formats.CycloneDX/StellaOps.Excititor.Formats.CycloneDX.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.Excititor.Formats.OpenVEX/StellaOps.Excititor.Formats.OpenVEX.csproj" />