up
Some checks failed
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-12 09:35:37 +02:00
parent ce5ec9c158
commit efaf3cb789
238 changed files with 146274 additions and 5767 deletions

View File

@@ -2,31 +2,31 @@ using System;
using System.Collections.Generic;
using StellaOps.Configuration;
using StellaOps.Scanner.Storage;
namespace StellaOps.Scanner.WebService.Options;
/// <summary>
/// Strongly typed configuration for the Scanner WebService host.
/// </summary>
public sealed class ScannerWebServiceOptions
{
public const string SectionName = "scanner";
/// <summary>
/// Schema version for configuration consumers to coordinate breaking changes.
/// </summary>
public int SchemaVersion { get; set; } = 1;
/// <summary>
/// Mongo storage configuration used for catalog and job state.
/// </summary>
public StorageOptions Storage { get; set; } = new();
/// <summary>
/// Queue configuration used to enqueue scan jobs.
/// </summary>
public QueueOptions Queue { get; set; } = new();
namespace StellaOps.Scanner.WebService.Options;
/// <summary>
/// Strongly typed configuration for the Scanner WebService host.
/// </summary>
public sealed class ScannerWebServiceOptions
{
public const string SectionName = "scanner";
/// <summary>
/// Schema version for configuration consumers to coordinate breaking changes.
/// </summary>
public int SchemaVersion { get; set; } = 1;
/// <summary>
/// PostgreSQL storage configuration used for catalog and job state.
/// </summary>
public StorageOptions Storage { get; set; } = new();
/// <summary>
/// Queue configuration used to enqueue scan jobs.
/// </summary>
public QueueOptions Queue { get; set; } = new();
/// <summary>
/// Object store configuration for SBOM artefacts.
/// </summary>
@@ -36,32 +36,32 @@ public sealed class ScannerWebServiceOptions
/// Registry credential configuration for report/export operations.
/// </summary>
public RegistryOptions Registry { get; set; } = new();
/// <summary>
/// Feature flags toggling optional behaviours.
/// </summary>
public FeatureFlagOptions Features { get; set; } = new();
/// <summary>
/// Plug-in loader configuration.
/// </summary>
public PluginOptions Plugins { get; set; } = new();
/// <summary>
/// Telemetry configuration for logs, metrics, traces.
/// </summary>
public TelemetryOptions Telemetry { get; set; } = new();
/// <summary>
/// Authority / authentication configuration.
/// </summary>
public AuthorityOptions Authority { get; set; } = new();
/// <summary>
/// Signing configuration for report envelopes and attestations.
/// </summary>
public SigningOptions Signing { get; set; } = new();
/// <summary>
/// Feature flags toggling optional behaviours.
/// </summary>
public FeatureFlagOptions Features { get; set; } = new();
/// <summary>
/// Plug-in loader configuration.
/// </summary>
public PluginOptions Plugins { get; set; } = new();
/// <summary>
/// Telemetry configuration for logs, metrics, traces.
/// </summary>
public TelemetryOptions Telemetry { get; set; } = new();
/// <summary>
/// Authority / authentication configuration.
/// </summary>
public AuthorityOptions Authority { get; set; } = new();
/// <summary>
/// Signing configuration for report envelopes and attestations.
/// </summary>
public SigningOptions Signing { get; set; } = new();
/// <summary>
/// API-specific settings such as base path.
/// </summary>
@@ -91,39 +91,42 @@ public sealed class ScannerWebServiceOptions
/// Deterministic execution switches for tests and replay.
/// </summary>
public DeterminismOptions Determinism { get; set; } = new();
public sealed class StorageOptions
{
public string Driver { get; set; } = "mongo";
public string Dsn { get; set; } = string.Empty;
public string? Database { get; set; }
public int CommandTimeoutSeconds { get; set; } = 30;
public int HealthCheckTimeoutSeconds { get; set; } = 5;
public IList<string> Migrations { get; set; } = new List<string>();
}
public sealed class QueueOptions
{
public string Driver { get; set; } = "redis";
public string Dsn { get; set; } = string.Empty;
public string Namespace { get; set; } = "scanner";
public int VisibilityTimeoutSeconds { get; set; } = 300;
public int LeaseHeartbeatSeconds { get; set; } = 30;
public int MaxDeliveryAttempts { get; set; } = 5;
public IDictionary<string, string> DriverSettings { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public sealed class StorageOptions
{
public string Driver { get; set; } = "postgres";
public string Dsn { get; set; } = string.Empty;
/// <summary>
/// Optional schema name for scanner tables. Defaults to <c>scanner</c> when not provided.
/// </summary>
public string? Database { get; set; }
public int CommandTimeoutSeconds { get; set; } = 30;
public int HealthCheckTimeoutSeconds { get; set; } = 5;
public IList<string> Migrations { get; set; } = new List<string>();
}
public sealed class QueueOptions
{
public string Driver { get; set; } = "redis";
public string Dsn { get; set; } = string.Empty;
public string Namespace { get; set; } = "scanner";
public int VisibilityTimeoutSeconds { get; set; } = 300;
public int LeaseHeartbeatSeconds { get; set; } = 30;
public int MaxDeliveryAttempts { get; set; } = 5;
public IDictionary<string, string> DriverSettings { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public sealed class ArtifactStoreOptions
{
public string Driver { get; set; } = "rustfs";
@@ -159,114 +162,114 @@ public sealed class ScannerWebServiceOptions
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public sealed class FeatureFlagOptions
{
public bool AllowAnonymousScanSubmission { get; set; }
public bool EnableSignedReports { get; set; } = true;
public bool EnablePolicyPreview { get; set; } = true;
public IDictionary<string, bool> Experimental { get; set; } = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
}
public sealed class PluginOptions
{
public string? BaseDirectory { get; set; }
public string? Directory { get; set; }
public IList<string> SearchPatterns { get; set; } = new List<string>();
public IList<string> OrderedPlugins { get; set; } = new List<string>();
}
public sealed class TelemetryOptions
{
public bool Enabled { get; set; } = true;
public bool EnableTracing { get; set; } = true;
public bool EnableMetrics { get; set; } = true;
public bool EnableLogging { get; set; } = true;
public bool EnableRequestLogging { get; set; } = true;
public string MinimumLogLevel { get; set; } = "Information";
public string? ServiceName { get; set; }
public string? OtlpEndpoint { get; set; }
public IDictionary<string, string> OtlpHeaders { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public IDictionary<string, string> ResourceAttributes { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public sealed class AuthorityOptions
{
public bool Enabled { get; set; }
public bool AllowAnonymousFallback { get; set; } = true;
public string Issuer { get; set; } = string.Empty;
public string? MetadataAddress { get; set; }
public bool RequireHttpsMetadata { get; set; } = true;
public int BackchannelTimeoutSeconds { get; set; } = 30;
public int TokenClockSkewSeconds { get; set; } = 60;
public IList<string> Audiences { get; set; } = new List<string>();
public IList<string> RequiredScopes { get; set; } = new List<string>();
public IList<string> BypassNetworks { get; set; } = new List<string>();
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
public string? ClientSecretFile { get; set; }
public IList<string> ClientScopes { get; set; } = new List<string>();
public ResilienceOptions Resilience { get; set; } = new();
public sealed class ResilienceOptions
{
public bool? EnableRetries { get; set; }
public IList<TimeSpan> RetryDelays { get; set; } = new List<TimeSpan>();
public bool? AllowOfflineCacheFallback { get; set; }
public TimeSpan? OfflineCacheTolerance { get; set; }
}
}
public bool EnablePolicyPreview { get; set; } = true;
public IDictionary<string, bool> Experimental { get; set; } = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
}
public sealed class PluginOptions
{
public string? BaseDirectory { get; set; }
public string? Directory { get; set; }
public IList<string> SearchPatterns { get; set; } = new List<string>();
public IList<string> OrderedPlugins { get; set; } = new List<string>();
}
public sealed class TelemetryOptions
{
public bool Enabled { get; set; } = true;
public bool EnableTracing { get; set; } = true;
public bool EnableMetrics { get; set; } = true;
public bool EnableLogging { get; set; } = true;
public bool EnableRequestLogging { get; set; } = true;
public string MinimumLogLevel { get; set; } = "Information";
public string? ServiceName { get; set; }
public string? OtlpEndpoint { get; set; }
public IDictionary<string, string> OtlpHeaders { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public IDictionary<string, string> ResourceAttributes { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public sealed class AuthorityOptions
{
public bool Enabled { get; set; }
public bool AllowAnonymousFallback { get; set; } = true;
public string Issuer { get; set; } = string.Empty;
public string? MetadataAddress { get; set; }
public bool RequireHttpsMetadata { get; set; } = true;
public int BackchannelTimeoutSeconds { get; set; } = 30;
public int TokenClockSkewSeconds { get; set; } = 60;
public IList<string> Audiences { get; set; } = new List<string>();
public IList<string> RequiredScopes { get; set; } = new List<string>();
public IList<string> BypassNetworks { get; set; } = new List<string>();
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
public string? ClientSecretFile { get; set; }
public IList<string> ClientScopes { get; set; } = new List<string>();
public ResilienceOptions Resilience { get; set; } = new();
public sealed class ResilienceOptions
{
public bool? EnableRetries { get; set; }
public IList<TimeSpan> RetryDelays { get; set; } = new List<TimeSpan>();
public bool? AllowOfflineCacheFallback { get; set; }
public TimeSpan? OfflineCacheTolerance { get; set; }
}
}
public sealed class SigningOptions
{
public bool Enabled { get; set; } = false;
public string KeyId { get; set; } = string.Empty;
public string Algorithm { get; set; } = "ed25519";
public string? Provider { get; set; }
public string? KeyPem { get; set; }
public string? KeyPemFile { get; set; }
public string? CertificatePem { get; set; }
public string? CertificatePemFile { get; set; }
public string Algorithm { get; set; } = "ed25519";
public string? Provider { get; set; }
public string? KeyPem { get; set; }
public string? KeyPemFile { get; set; }
public string? CertificatePem { get; set; }
public string? CertificatePemFile { get; set; }
public string? CertificateChainPem { get; set; }
public string? CertificateChainPemFile { get; set; }
@@ -305,7 +308,7 @@ public sealed class ScannerWebServiceOptions
public string? Email { get; set; }
}
public sealed class ApiOptions
{
public string BasePath { get; set; } = "/api/v1";
@@ -333,13 +336,13 @@ public sealed class ScannerWebServiceOptions
public sealed class EventsOptions
{
public bool Enabled { get; set; }
public string Driver { get; set; } = "redis";
public string Dsn { get; set; } = string.Empty;
public string Stream { get; set; } = "stella.events";
public string Driver { get; set; } = "redis";
public string Dsn { get; set; } = string.Empty;
public string Stream { get; set; } = "stella.events";
public double PublishTimeoutSeconds { get; set; } = 5;
public long MaxStreamLength { get; set; } = 10000;

View File

@@ -38,7 +38,6 @@ using StellaOps.Scanner.WebService.Security;
using StellaOps.Scanner.WebService.Replay;
using StellaOps.Scanner.Storage;
using StellaOps.Scanner.Storage.Extensions;
using StellaOps.Scanner.Storage.Mongo;
using StellaOps.Scanner.WebService.Endpoints;
using StellaOps.Scanner.WebService.Options;
@@ -138,15 +137,12 @@ else
builder.Services.AddSingleton<IReportEventDispatcher, ReportEventDispatcher>();
builder.Services.AddScannerStorage(storageOptions =>
{
storageOptions.Mongo.ConnectionString = bootstrapOptions.Storage.Dsn;
if (!string.IsNullOrWhiteSpace(bootstrapOptions.Storage.Database))
{
storageOptions.Mongo.DatabaseName = bootstrapOptions.Storage.Database;
}
storageOptions.Mongo.CommandTimeout = TimeSpan.FromSeconds(bootstrapOptions.Storage.CommandTimeoutSeconds);
storageOptions.Mongo.UseMajorityReadConcern = true;
storageOptions.Mongo.UseMajorityWriteConcern = true;
storageOptions.Postgres.ConnectionString = bootstrapOptions.Storage.Dsn;
storageOptions.Postgres.SchemaName = string.IsNullOrWhiteSpace(bootstrapOptions.Storage.Database)
? ScannerStorageDefaults.DefaultSchemaName
: bootstrapOptions.Storage.Database!.Trim();
storageOptions.Postgres.CommandTimeoutSeconds = bootstrapOptions.Storage.CommandTimeoutSeconds;
storageOptions.Postgres.AutoMigrate = true;
storageOptions.ObjectStore.Headers.Clear();
foreach (var header in bootstrapOptions.ArtifactStore.Headers)
@@ -335,12 +331,6 @@ if (authorityConfigured && resolvedOptions.Authority.AllowAnonymousFallback)
"Scanner authority authentication is enabled but anonymous fallback remains allowed. Disable fallback before production rollout.");
}
using (var scope = app.Services.CreateScope())
{
var bootstrapper = scope.ServiceProvider.GetRequiredService<MongoBootstrapper>();
await bootstrapper.InitializeAsync(CancellationToken.None).ConfigureAwait(false);
}
if (resolvedOptions.Telemetry.EnableLogging && resolvedOptions.Telemetry.EnableRequestLogging)
{
app.UseSerilogRequestLogging(options =>

View File

@@ -184,7 +184,7 @@ internal sealed class RecordModeService : IRecordModeService
{
var manifest = new ReplayManifest
{
SchemaVersion = ReplayManifestVersions.V1,
SchemaVersion = ReplayManifestVersions.V2,
Scan = new ReplayScanMetadata
{
Id = request.ScanId,

View File

@@ -2,7 +2,6 @@ using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text;
using MongoDB.Bson;
using StellaOps.Scanner.Storage.Catalog;
using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.WebService.Options;
@@ -85,7 +84,7 @@ internal sealed class RuntimeEventIngestionService : IRuntimeEventIngestionServi
return RuntimeEventIngestionResult.PayloadTooLarge(totalPayloadBytes, options.MaxPayloadBytes);
}
var payloadDocument = BsonDocument.Parse(Encoding.UTF8.GetString(payloadBytes));
var payloadJson = Encoding.UTF8.GetString(payloadBytes);
var runtimeEvent = envelope.Event;
var normalizedDigest = ExtractImageDigest(runtimeEvent);
var normalizedBuildId = NormalizeBuildId(runtimeEvent.Process?.BuildId);
@@ -113,7 +112,7 @@ internal sealed class RuntimeEventIngestionService : IRuntimeEventIngestionServi
ImageSigned = runtimeEvent.Posture?.ImageSigned,
SbomReferrer = runtimeEvent.Posture?.SbomReferrer,
BuildId = normalizedBuildId,
Payload = payloadDocument
PayloadJson = payloadJson
};
documents.Add(document);