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