Refactor and update test projects, remove obsolete tests, and upgrade dependencies

- Deleted obsolete test files for SchedulerAuditService and SchedulerMongoSessionFactory.
- Removed unused TestDataFactory class.
- Updated project files for Mongo.Tests to remove references to deleted files.
- Upgraded BouncyCastle.Cryptography package to version 2.6.2 across multiple projects.
- Replaced Microsoft.Extensions.Http.Polly with Microsoft.Extensions.Http.Resilience in Zastava.Webhook project.
- Updated NetEscapades.Configuration.Yaml package to version 3.1.0 in Configuration library.
- Upgraded Pkcs11Interop package to version 5.1.2 in Cryptography libraries.
- Refactored Argon2idPasswordHasher to use BouncyCastle for hashing instead of Konscious.
- Updated JsonSchema.Net package to version 7.3.2 in Microservice project.
- Updated global.json to use .NET SDK version 10.0.101.
This commit is contained in:
master
2025-12-10 19:13:29 +02:00
parent a3c7fe5e88
commit b7059d523e
369 changed files with 11125 additions and 14245 deletions

View File

@@ -21,6 +21,7 @@ using StellaOps.Excititor.Attestation.Transparency;
using StellaOps.Excititor.ArtifactStores.S3.Extensions;
using StellaOps.Excititor.Connectors.RedHat.CSAF.DependencyInjection;
using StellaOps.Excititor.Core;
using StellaOps.Excititor.Core.Evidence;
using StellaOps.Excititor.Core.Observations;
using StellaOps.Excititor.Export;
using StellaOps.Excititor.Formats.CSAF;
@@ -28,6 +29,7 @@ using StellaOps.Excititor.Formats.CycloneDX;
using StellaOps.Excititor.Formats.OpenVEX;
using StellaOps.Excititor.Policy;
using StellaOps.Excititor.Storage.Postgres;
using StellaOps.Infrastructure.Postgres.Options;
using StellaOps.Excititor.WebService.Endpoints;
using StellaOps.Excititor.WebService.Extensions;
using StellaOps.Excititor.WebService.Options;
@@ -46,10 +48,12 @@ var services = builder.Services;
services.AddOptions<VexStorageOptions>()
.Bind(configuration.GetSection("Excititor:Storage"))
.ValidateOnStart();
services.AddOptions<GraphOptions>()
.Bind(configuration.GetSection("Excititor:Graph"));
services.AddExcititorPostgresStorage(configuration);
services.TryAddSingleton<IVexProviderStore, InMemoryVexProviderStore>();
services.TryAddSingleton<IVexConnectorStateRepository, InMemoryVexConnectorStateRepository>();
services.TryAddScoped<IVexConnectorStateRepository, InMemoryVexConnectorStateRepository>();
services.TryAddSingleton<IVexClaimStore, InMemoryVexClaimStore>();
services.AddCsafNormalizer();
services.AddCycloneDxNormalizer();
@@ -62,7 +66,24 @@ services.AddSingleton<AirgapSignerTrustService>();
services.AddSingleton<AirgapModeEnforcer>();
services.AddSingleton<ConsoleTelemetry>();
services.AddMemoryCache();
services.AddSingleton<IGraphOverlayCache, GraphOverlayCacheStore>();
services.AddSingleton<IGraphOverlayStore>(sp =>
{
var graphOptions = sp.GetRequiredService<IOptions<GraphOptions>>().Value;
var pgOptions = sp.GetRequiredService<IOptions<PostgresOptions>>().Value;
if (graphOptions.UsePostgresOverlayStore && !string.IsNullOrWhiteSpace(pgOptions.ConnectionString))
{
return new PostgresGraphOverlayStore(
sp.GetRequiredService<ExcititorDataSource>(),
sp.GetRequiredService<ILogger<PostgresGraphOverlayStore>>());
}
return new InMemoryGraphOverlayStore();
});
services.AddSingleton<IVexEvidenceLockerService, VexEvidenceLockerService>();
services.AddSingleton<IVexEvidenceAttestor, StellaOps.Excititor.Attestation.Evidence.VexEvidenceAttestor>();
services.AddScoped<IVexIngestOrchestrator, VexIngestOrchestrator>();
services.AddSingleton<VexStatementBackfillService>();
services.AddOptions<ExcititorObservabilityOptions>()
.Bind(configuration.GetSection("Excititor:Observability"));
services.AddScoped<ExcititorHealthService>();
@@ -93,7 +114,7 @@ services.AddSingleton<IVexObservationProjectionService, VexObservationProjection
services.AddScoped<IVexObservationQueryService, VexObservationQueryService>();
// EXCITITOR-RISK-66-001: Risk feed service for Risk Engine integration
services.AddScoped<StellaOps.Excititor.Core.RiskFeed.IRiskFeedService, StellaOps.Excititor.Core.RiskFeed.RiskFeedService>();
services.AddScoped<StellaOps.Excititor.Core.RiskFeed.IRiskFeedService, OverlayRiskFeedService>();
var rekorSection = configuration.GetSection("Excititor:Attestation:Rekor");
if (rekorSection.Exists())
@@ -1505,7 +1526,7 @@ app.MapGet("/v1/graph/status", async (
return Results.BadRequest(ex.Message);
}
var items = GraphStatusFactory.Build(orderedPurls, result.Observations);
var items = GraphStatusFactory.Build(tenant!, timeProvider.GetUtcNow(), orderedPurls, result.Observations);
var response = new GraphStatusResponse(items, false, null);
cache.Set(cacheKey, new CachedGraphStatus(items, now), TimeSpan.FromSeconds(graphOptions.Value.OverlayTtlSeconds));
@@ -1521,7 +1542,8 @@ app.MapGet("/v1/graph/overlays", async (
IOptions<VexStorageOptions> storageOptions,
IOptions<GraphOptions> graphOptions,
IVexObservationQueryService queryService,
IMemoryCache cache,
IGraphOverlayCache overlayCache,
IGraphOverlayStore overlayStore,
TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
@@ -1541,13 +1563,12 @@ app.MapGet("/v1/graph/overlays", async (
return Results.BadRequest($"purls limit exceeded (max {graphOptions.Value.MaxPurls})");
}
var cacheKey = $"graph-overlays:{tenant}:{includeJustifications}:{string.Join('|', orderedPurls)}";
var now = timeProvider.GetUtcNow();
if (cache.TryGetValue<CachedGraphOverlay>(cacheKey, out var cached) && cached is not null)
var cached = await overlayCache.TryGetAsync(tenant!, includeJustifications, orderedPurls, cancellationToken).ConfigureAwait(false);
if (cached is not null)
{
var ageMs = (long)Math.Max(0, (now - cached.CachedAt).TotalMilliseconds);
return Results.Ok(new GraphOverlaysResponse(cached.Items, true, ageMs));
return Results.Ok(new GraphOverlaysResponse(cached.Items, true, cached.AgeMilliseconds));
}
var options = new VexObservationQueryOptions(
@@ -1565,10 +1586,11 @@ app.MapGet("/v1/graph/overlays", async (
return Results.BadRequest(ex.Message);
}
var overlays = GraphOverlayFactory.Build(orderedPurls, result.Observations, includeJustifications);
var overlays = GraphOverlayFactory.Build(tenant!, now, orderedPurls, result.Observations, includeJustifications);
await overlayStore.SaveAsync(tenant!, overlays, cancellationToken).ConfigureAwait(false);
var response = new GraphOverlaysResponse(overlays, false, null);
cache.Set(cacheKey, new CachedGraphOverlay(overlays, now), TimeSpan.FromSeconds(graphOptions.Value.OverlayTtlSeconds));
await overlayCache.SaveAsync(tenant!, includeJustifications, orderedPurls, overlays, now, cancellationToken).ConfigureAwait(false);
return Results.Ok(response);
}).WithName("GetGraphOverlays");
@@ -1712,8 +1734,9 @@ app.MapGet("/vex/raw", async (
var formatFilter = query.TryGetValue("format", out var formats)
? formats
.Where(static f => !string.IsNullOrWhiteSpace(f))
.Select(static f => Enum.TryParse<VexDocumentFormat>(f, true, out var parsed) ? parsed : VexDocumentFormat.Unknown)
.Where(static f => f != VexDocumentFormat.Unknown)
.Select(static f => Enum.TryParse<VexDocumentFormat>(f, true, out var parsed) ? parsed : (VexDocumentFormat?)null)
.Where(static f => f is not null)
.Select(static f => f!.Value)
.ToArray()
: Array.Empty<VexDocumentFormat>();
@@ -1910,112 +1933,6 @@ app.MapGet("/v1/vex/observations/{vulnerabilityId}/{productKey}", async (
return Results.Json(response);
});
app.MapGet("/v1/vex/evidence/chunks", async (
HttpContext context,
[FromServices] IVexEvidenceChunkService chunkService,
[FromServices] IOptions<VexStorageOptions> storageOptions,
[FromServices] ChunkTelemetry chunkTelemetry,
[FromServices] ILogger<VexEvidenceChunkRequest> logger,
[FromServices] TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
var start = Stopwatch.GetTimestamp();
var scopeResult = ScopeAuthorization.RequireScope(context, "vex.read");
if (scopeResult is not null)
{
chunkTelemetry.RecordIngested(null, null, "unauthorized", "missing-scope", 0, 0, 0);
return scopeResult;
}
if (!TryResolveTenant(context, storageOptions.Value, requireHeader: false, out var tenant, out var tenantError))
{
chunkTelemetry.RecordIngested(tenant, null, "rejected", "tenant-invalid", 0, 0, Stopwatch.GetElapsedTime(start).TotalMilliseconds);
return tenantError;
}
var vulnerabilityId = context.Request.Query["vulnerabilityId"].FirstOrDefault();
var productKey = context.Request.Query["productKey"].FirstOrDefault();
if (string.IsNullOrWhiteSpace(vulnerabilityId) || string.IsNullOrWhiteSpace(productKey))
{
return ValidationProblem("vulnerabilityId and productKey are required.");
}
var providerFilter = BuildStringFilterSet(context.Request.Query["providerId"]);
var statusFilter = BuildStatusFilter(context.Request.Query["status"]);
var since = ParseSinceTimestamp(context.Request.Query["since"]);
var limit = ResolveLimit(context.Request.Query["limit"], defaultValue: 200, min: 1, max: 500);
var request = new VexEvidenceChunkRequest(
tenant,
vulnerabilityId.Trim(),
productKey.Trim(),
providerFilter,
statusFilter,
since,
limit);
VexEvidenceChunkResult result;
try
{
result = await chunkService.QueryAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
EvidenceTelemetry.RecordChunkOutcome(tenant, "cancelled");
chunkTelemetry.RecordIngested(tenant, providerFilter.Count > 0 ? string.Join(',', providerFilter) : null, "cancelled", null, 0, 0, Stopwatch.GetElapsedTime(start).TotalMilliseconds);
return Results.StatusCode(StatusCodes.Status499ClientClosedRequest);
}
catch
{
EvidenceTelemetry.RecordChunkOutcome(tenant, "error");
chunkTelemetry.RecordIngested(tenant, providerFilter.Count > 0 ? string.Join(',', providerFilter) : null, "error", null, 0, 0, Stopwatch.GetElapsedTime(start).TotalMilliseconds);
throw;
}
EvidenceTelemetry.RecordChunkOutcome(tenant, "success", result.Chunks.Count, result.Truncated);
EvidenceTelemetry.RecordChunkSignatureStatus(tenant, result.Chunks);
logger.LogInformation(
"vex_evidence_chunks_success tenant={Tenant} vulnerabilityId={Vuln} productKey={ProductKey} providers={Providers} statuses={Statuses} limit={Limit} total={Total} truncated={Truncated} returned={Returned}",
tenant ?? "(default)",
request.VulnerabilityId,
request.ProductKey,
providerFilter.Count,
statusFilter.Count,
request.Limit,
result.TotalCount,
result.Truncated,
result.Chunks.Count);
// Align headers with published contract.
context.Response.Headers["Excititor-Results-Total"] = result.TotalCount.ToString(CultureInfo.InvariantCulture);
context.Response.Headers["Excititor-Results-Truncated"] = result.Truncated ? "true" : "false";
context.Response.ContentType = "application/x-ndjson";
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
long payloadBytes = 0;
foreach (var chunk in result.Chunks)
{
var line = JsonSerializer.Serialize(chunk, options);
payloadBytes += Encoding.UTF8.GetByteCount(line) + 1;
await context.Response.WriteAsync(line, cancellationToken).ConfigureAwait(false);
await context.Response.WriteAsync("\n", cancellationToken).ConfigureAwait(false);
}
var elapsedMs = Stopwatch.GetElapsedTime(start).TotalMilliseconds;
chunkTelemetry.RecordIngested(
tenant,
providerFilter.Count > 0 ? string.Join(',', providerFilter) : null,
"success",
null,
result.TotalCount,
payloadBytes,
elapsedMs);
return Results.Empty;
});
app.MapPost("/aoc/verify", async (
HttpContext context,
VexAocVerifyRequest? request,
@@ -2060,10 +1977,10 @@ app.MapPost("/aoc/verify", async (
sources ?? Array.Empty<string>(),
Array.Empty<string>(),
Array.Empty<VexDocumentFormat>(),
since: new DateTimeOffset(since, TimeSpan.Zero),
until: new DateTimeOffset(until, TimeSpan.Zero),
cursor: null,
limit),
Since: new DateTimeOffset(since, TimeSpan.Zero),
Until: new DateTimeOffset(until, TimeSpan.Zero),
Cursor: null,
Limit: limit),
cancellationToken).ConfigureAwait(false);
var checkedCount = 0;