feat: Add Scanner CI runner and related artifacts
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Airgap Sealed CI Smoke / sealed-smoke (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled

- Implemented `run-scanner-ci.sh` to build and run tests for the Scanner solution with a warmed NuGet cache.
- Created `excititor-vex-traces.json` dashboard for monitoring Excititor VEX observations.
- Added Docker Compose configuration for the OTLP span sink in `docker-compose.spansink.yml`.
- Configured OpenTelemetry collector in `otel-spansink.yaml` to receive and process traces.
- Developed `run-spansink.sh` script to run the OTLP span sink for Excititor traces.
- Introduced `FileSystemRiskBundleObjectStore` for storing risk bundle artifacts in the filesystem.
- Built `RiskBundleBuilder` for creating risk bundles with associated metadata and providers.
- Established `RiskBundleJob` to execute the risk bundle creation and storage process.
- Defined models for risk bundle inputs, entries, and manifests in `RiskBundleModels.cs`.
- Implemented signing functionality for risk bundle manifests with `HmacRiskBundleManifestSigner`.
- Created unit tests for `RiskBundleBuilder`, `RiskBundleJob`, and signing functionality to ensure correctness.
- Added filesystem artifact reader tests to validate manifest parsing and artifact listing.
- Included test manifests for egress scenarios in the task runner tests.
- Developed timeline query service tests to verify tenant and event ID handling.
This commit is contained in:
StellaOps Bot
2025-11-30 19:12:35 +02:00
parent 17d45a6d30
commit 71e9a56cfd
92 changed files with 2596 additions and 387 deletions

View File

@@ -0,0 +1,95 @@
using Microsoft.Extensions.Logging;
namespace StellaOps.ExportCenter.RiskBundles;
public sealed record RiskBundleJobRequest(
RiskBundleBuildRequest BuildRequest,
string StoragePrefix = "risk-bundles",
string BundleFileName = "risk-bundle.tar.gz");
public sealed record RiskBundleJobOutcome(
RiskBundleManifest Manifest,
RiskBundleStorageMetadata ManifestStorage,
RiskBundleStorageMetadata ManifestSignatureStorage,
RiskBundleStorageMetadata BundleStorage,
string ManifestJson,
string ManifestSignatureJson,
string RootHash);
public sealed class RiskBundleJob
{
private readonly RiskBundleBuilder _builder;
private readonly IRiskBundleManifestSigner _signer;
private readonly IRiskBundleObjectStore _objectStore;
private readonly ILogger<RiskBundleJob> _logger;
public RiskBundleJob(
RiskBundleBuilder builder,
IRiskBundleManifestSigner signer,
IRiskBundleObjectStore objectStore,
ILogger<RiskBundleJob> logger)
{
_builder = builder ?? throw new ArgumentNullException(nameof(builder));
_signer = signer ?? throw new ArgumentNullException(nameof(signer));
_objectStore = objectStore ?? throw new ArgumentNullException(nameof(objectStore));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<RiskBundleJobOutcome> ExecuteAsync(RiskBundleJobRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
cancellationToken.ThrowIfCancellationRequested();
var build = _builder.Build(request.BuildRequest, cancellationToken);
_logger.LogInformation("Risk bundle built with {ProviderCount} providers.", build.Manifest.Providers.Count);
var signature = await _signer.SignAsync(build.ManifestJson, cancellationToken).ConfigureAwait(false);
var signatureJson = System.Text.Json.JsonSerializer.Serialize(signature, new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
});
var manifestKey = Combine(request.StoragePrefix, request.BuildRequest.ManifestFileName);
var manifestSigKey = Combine(request.StoragePrefix, request.BuildRequest.ManifestDsseFileName);
var bundleKey = Combine(request.StoragePrefix, request.BundleFileName);
var manifestStorage = await _objectStore.StoreAsync(
new RiskBundleObjectStoreOptions(manifestKey, "application/json"),
new MemoryStream(System.Text.Encoding.UTF8.GetBytes(build.ManifestJson)),
cancellationToken).ConfigureAwait(false);
var signatureStorage = await _objectStore.StoreAsync(
new RiskBundleObjectStoreOptions(manifestSigKey, "application/json"),
new MemoryStream(System.Text.Encoding.UTF8.GetBytes(signatureJson)),
cancellationToken).ConfigureAwait(false);
var bundleStorage = await _objectStore.StoreAsync(
new RiskBundleObjectStoreOptions(bundleKey, "application/gzip"),
build.BundleStream,
cancellationToken).ConfigureAwait(false);
return new RiskBundleJobOutcome(
build.Manifest,
manifestStorage,
signatureStorage,
bundleStorage,
build.ManifestJson,
signatureJson,
build.RootHash);
}
private static string Combine(string prefix, string fileName)
{
if (string.IsNullOrWhiteSpace(prefix))
{
return fileName;
}
var sanitizedPrefix = prefix.Trim('/');
return string.IsNullOrWhiteSpace(sanitizedPrefix)
? fileName
: $"{sanitizedPrefix}/{fileName}";
}
}