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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user