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
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user