up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Airgap Sealed CI Smoke / sealed-smoke (push) Has been cancelled
Console CI / console-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-30 22:36:03 +02:00
parent b39eb34226
commit 7df0677e34
31 changed files with 884 additions and 164 deletions

View File

@@ -1,8 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using StellaOps.ExportCenter.Core.DevPortalOffline;
using StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
using StellaOps.ExportCenter.Worker;
using StellaOps.ExportCenter.RiskBundles;
var builder = Host.CreateApplicationBuilder(args);
@@ -11,12 +13,27 @@ builder.Services.AddSingleton(TimeProvider.System);
builder.Services.Configure<DevPortalOfflineWorkerOptions>(builder.Configuration.GetSection("DevPortalOffline"));
builder.Services.Configure<DevPortalOfflineManifestSigningOptions>(builder.Configuration.GetSection("DevPortalOffline:Signing"));
builder.Services.Configure<DevPortalOfflineStorageOptions>(builder.Configuration.GetSection("DevPortalOffline:Storage"));
builder.Services.Configure<RiskBundleWorkerOptions>(builder.Configuration.GetSection("RiskBundles"));
builder.Services.Configure<RiskBundleManifestSigningOptions>(builder.Configuration.GetSection("RiskBundles:Signing"));
builder.Services.Configure<FileSystemRiskBundleStorageOptions>(builder.Configuration.GetSection("RiskBundles:Storage"));
builder.Services.AddSingleton<DevPortalOfflineBundleBuilder>();
builder.Services.AddSingleton<IDevPortalOfflineManifestSigner, HmacDevPortalOfflineManifestSigner>();
builder.Services.AddSingleton<IDevPortalOfflineObjectStore, FileSystemDevPortalOfflineObjectStore>();
builder.Services.AddSingleton<DevPortalOfflineJob>();
builder.Services.AddSingleton<RiskBundleBuilder>();
builder.Services.AddSingleton<IRiskBundleManifestSigner>(sp =>
{
var signing = sp.GetRequiredService<IOptions<RiskBundleManifestSigningOptions>>().Value;
var key = string.IsNullOrWhiteSpace(signing.Key) ? throw new InvalidOperationException("Risk bundle signing key is not configured.") : signing.Key;
var keyId = string.IsNullOrWhiteSpace(signing.KeyId) ? "risk-bundle-hmac" : signing.KeyId!;
return new HmacRiskBundleManifestSigner(key, keyId);
});
builder.Services.AddSingleton<IRiskBundleObjectStore, FileSystemRiskBundleObjectStore>();
builder.Services.AddSingleton<RiskBundleJob>();
builder.Services.AddHostedService<Worker>();
builder.Services.AddHostedService<RiskBundleWorker>();
var host = builder.Build();
host.Run();

View File

@@ -0,0 +1,81 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.ExportCenter.RiskBundles;
namespace StellaOps.ExportCenter.Worker;
public sealed class RiskBundleWorker : BackgroundService
{
private readonly ILogger<RiskBundleWorker> _logger;
private readonly RiskBundleJob _job;
private readonly IOptions<RiskBundleWorkerOptions> _options;
public RiskBundleWorker(
ILogger<RiskBundleWorker> logger,
RiskBundleJob job,
IOptions<RiskBundleWorkerOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_job = job ?? throw new ArgumentNullException(nameof(job));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var opts = _options.Value ?? new RiskBundleWorkerOptions();
if (!opts.Enabled)
{
_logger.LogInformation("Risk bundle worker disabled. Idling.");
await Task.Delay(Timeout.Infinite, stoppingToken).ConfigureAwait(false);
return;
}
try
{
var request = BuildRequest(opts);
var outcome = await _job.ExecuteAsync(request, stoppingToken).ConfigureAwait(false);
_logger.LogInformation(
"Risk bundle built with {ProviderCount} providers. Stored bundle={BundleKey} manifest={ManifestKey} signature={SignatureKey}.",
outcome.Manifest.Providers.Count,
outcome.BundleStorage.StorageKey,
outcome.ManifestStorage.StorageKey,
outcome.ManifestSignatureStorage.StorageKey);
}
catch (Exception ex) when (!stoppingToken.IsCancellationRequested)
{
_logger.LogError(ex, "Risk bundle job failed.");
throw;
}
await Task.Delay(Timeout.Infinite, stoppingToken).ConfigureAwait(false);
}
private static RiskBundleJobRequest BuildRequest(RiskBundleWorkerOptions options)
{
if (options.Providers is not { Count: > 0 })
{
throw new InvalidOperationException("Risk bundle worker requires at least one provider entry.");
}
var bundleId = options.BundleId ?? Guid.NewGuid();
var providers = options.Providers
.Select(p => p.ToInput())
.ToArray();
var build = new RiskBundleBuildRequest(
bundleId,
providers,
BundleFileName: options.BundleFileName ?? "risk-bundle.tar.gz",
BundlePrefix: options.StoragePrefix ?? "risk-bundles",
ManifestFileName: options.ManifestFileName ?? "provider-manifest.json",
ManifestDsseFileName: options.ManifestDsseFileName ?? "provider-manifest.dsse",
AllowMissingOptional: options.AllowMissingOptional);
return new RiskBundleJobRequest(
build,
StoragePrefix: options.StoragePrefix ?? "risk-bundles",
BundleFileName: options.BundleFileName ?? "risk-bundle.tar.gz");
}
}

View File

@@ -0,0 +1,69 @@
using System.ComponentModel.DataAnnotations;
using StellaOps.ExportCenter.RiskBundles;
namespace StellaOps.ExportCenter.Worker;
public sealed class RiskBundleWorkerOptions
{
public bool Enabled { get; set; } = false;
public Guid? BundleId { get; set; }
public string? StoragePrefix { get; set; } = "risk-bundles";
public string? BundleFileName { get; set; } = "risk-bundle.tar.gz";
public string? ManifestFileName { get; set; } = "provider-manifest.json";
public string? ManifestDsseFileName { get; set; } = "provider-manifest.dsse";
public bool AllowMissingOptional { get; set; } = true;
[MinLength(1)]
public List<RiskBundleProviderOption> Providers { get; set; } = new();
}
public sealed class RiskBundleProviderOption
{
public string? ProviderId { get; set; }
public string? SourcePath { get; set; }
public string? Source { get; set; }
public bool Optional { get; set; }
public DateOnly? SnapshotDate { get; set; }
public RiskBundleProviderInput ToInput()
{
if (string.IsNullOrWhiteSpace(ProviderId))
{
throw new ValidationException("ProviderId is required for risk bundle provider options.");
}
if (string.IsNullOrWhiteSpace(SourcePath))
{
throw new ValidationException("SourcePath is required for risk bundle provider options.");
}
if (string.IsNullOrWhiteSpace(Source))
{
throw new ValidationException("Source descriptor is required for risk bundle provider options.");
}
return new RiskBundleProviderInput(
ProviderId,
SourcePath,
Source,
Optional,
SnapshotDate);
}
}
public sealed class RiskBundleManifestSigningOptions
{
public string? Key { get; set; }
public string? KeyId { get; set; }
}
public sealed class RiskBundleStorageOptions
{
public string? RootPath { get; set; }
}

View File

@@ -28,16 +28,19 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj"/>
<ProjectReference Include="..\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj"/>
</ItemGroup>
</Project>
<ItemGroup>
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj"/>
<ProjectReference Include="..\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj"/>
<ProjectReference Include="..\..\StellaOps.ExportCenter.RiskBundles\StellaOps.ExportCenter.RiskBundles.csproj"/>
</ItemGroup>
</Project>

View File

@@ -1,8 +1,26 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"RiskBundles": {
"Enabled": false,
"Storage": {
"RootPath": "./out/risk-bundles-dev"
},
"Signing": {
"Key": "dev-risk-bundle-key",
"KeyId": "risk-bundle-hmac"
},
"Providers": [
{
"ProviderId": "cisa-kev",
"SourcePath": "./inputs/kev.json",
"Source": "CISA KEV",
"Optional": false
}
]
}
}

View File

@@ -5,6 +5,35 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
"RiskBundles": {
"Enabled": false,
"StoragePrefix": "risk-bundles",
"BundleFileName": "risk-bundle.tar.gz",
"ManifestFileName": "provider-manifest.json",
"ManifestDsseFileName": "provider-manifest.dsse",
"AllowMissingOptional": true,
"Storage": {
"RootPath": "./out/risk-bundles"
},
"Signing": {
"Key": "change-me-risk-bundle-key",
"KeyId": "risk-bundle-hmac"
},
"Providers": [
{
"ProviderId": "cisa-kev",
"SourcePath": "./inputs/kev.json",
"Source": "CISA KEV",
"Optional": false
},
{
"ProviderId": "first-epss",
"SourcePath": "./inputs/epss.csv",
"Source": "FIRST EPSS",
"Optional": true
}
]
},
"DevPortalOffline": {
"Enabled": false,
"StoragePrefix": "devportal/offline",