138 lines
4.8 KiB
C#
138 lines
4.8 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using System.Linq;
|
|
using StellaOps.RiskEngine.Core.Contracts;
|
|
using StellaOps.RiskEngine.Core.Providers;
|
|
using StellaOps.RiskEngine.Core.Services;
|
|
using StellaOps.RiskEngine.Infrastructure.Stores;
|
|
using StellaOps.Router.AspNet;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.AddOpenApi();
|
|
|
|
builder.Services.AddSingleton<RiskScoreQueue>();
|
|
builder.Services.AddSingleton<IRiskScoreResultStore, InMemoryRiskScoreResultStore>();
|
|
builder.Services.AddSingleton<IRiskScoreProviderRegistry>(_ =>
|
|
new RiskScoreProviderRegistry(new IRiskScoreProvider[]
|
|
{
|
|
new DefaultTransformsProvider(),
|
|
new CvssKevProvider(new NullCvssSource(), new NullKevSource()),
|
|
new VexGateProvider(),
|
|
new FixExposureProvider()
|
|
}));
|
|
|
|
// Stella Router integration
|
|
var routerOptions = builder.Configuration.GetSection("RiskEngine:Router").Get<StellaRouterOptionsBase>();
|
|
builder.Services.TryAddStellaRouter(
|
|
serviceName: "riskengine",
|
|
version: typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0",
|
|
routerOptions: routerOptions);
|
|
|
|
var app = builder.Build();
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.MapOpenApi();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.TryUseStellaRouter(routerOptions);
|
|
|
|
app.MapGet("/risk-scores/providers", (IRiskScoreProviderRegistry registry) =>
|
|
Results.Ok(new { providers = registry.ProviderNames.OrderBy(n => n, StringComparer.OrdinalIgnoreCase) }));
|
|
|
|
app.MapPost("/risk-scores/jobs", async (
|
|
ScoreRequest request,
|
|
[FromServices] RiskScoreQueue queue,
|
|
[FromServices] IRiskScoreProviderRegistry registry,
|
|
[FromServices] IRiskScoreResultStore store,
|
|
CancellationToken ct) =>
|
|
{
|
|
var normalized = new ScoreRequest(
|
|
request.Provider,
|
|
request.Subject,
|
|
request.Signals ?? new Dictionary<string, double>());
|
|
|
|
var jobId = await queue.EnqueueWithIdAsync(normalized, ct).ConfigureAwait(false);
|
|
var worker = new RiskScoreWorker(queue, registry, store, TimeProvider.System);
|
|
var result = await worker.ProcessNextAsync(ct).ConfigureAwait(false);
|
|
return Results.Accepted($"/risk-scores/jobs/{jobId}", new { jobId, result });
|
|
});
|
|
|
|
app.MapGet("/risk-scores/jobs/{jobId:guid}", (
|
|
Guid jobId,
|
|
[FromServices] IRiskScoreResultStore store) =>
|
|
store.TryGet(jobId, out var result)
|
|
? Results.Ok(result)
|
|
: Results.NotFound());
|
|
|
|
app.MapPost("/risk-scores/simulations", async (
|
|
IReadOnlyCollection<ScoreRequest> requests,
|
|
[FromServices] IRiskScoreProviderRegistry registry,
|
|
CancellationToken ct) =>
|
|
{
|
|
var results = await EvaluateAsync(requests, registry, ct).ConfigureAwait(false);
|
|
return Results.Ok(new { results });
|
|
});
|
|
|
|
app.MapPost("/risk-scores/simulations/summary", async (
|
|
IReadOnlyCollection<ScoreRequest> requests,
|
|
[FromServices] IRiskScoreProviderRegistry registry,
|
|
CancellationToken ct) =>
|
|
{
|
|
var results = await EvaluateAsync(requests, registry, ct).ConfigureAwait(false);
|
|
|
|
var scores = results.Select(r => r.Score).ToArray();
|
|
var summary = new
|
|
{
|
|
averageScore = scores.Length == 0 ? 0d : scores.Average(),
|
|
minScore = scores.Length == 0 ? 0d : scores.Min(),
|
|
maxScore = scores.Length == 0 ? 0d : scores.Max(),
|
|
topMovers = results
|
|
.OrderByDescending(r => r.Score)
|
|
.ThenBy(r => r.Subject, StringComparer.Ordinal)
|
|
.Take(3)
|
|
.ToArray()
|
|
};
|
|
|
|
return Results.Ok(new { summary, results });
|
|
});
|
|
|
|
// Refresh Router endpoint cache
|
|
app.TryRefreshStellaRouterEndpoints(routerOptions);
|
|
|
|
app.Run();
|
|
|
|
static async Task<List<RiskScoreResult>> EvaluateAsync(
|
|
IReadOnlyCollection<ScoreRequest> requests,
|
|
IRiskScoreProviderRegistry registry,
|
|
CancellationToken ct)
|
|
{
|
|
var results = new List<RiskScoreResult>(requests.Count);
|
|
foreach (var req in requests)
|
|
{
|
|
var normalized = new ScoreRequest(
|
|
req.Provider,
|
|
req.Subject,
|
|
req.Signals ?? new Dictionary<string, double>());
|
|
|
|
if (!registry.TryGet(normalized.Provider, out var provider))
|
|
{
|
|
results.Add(new RiskScoreResult(Guid.NewGuid(), normalized.Provider, normalized.Subject, 0d, false, "Provider not registered", normalized.Signals, TimeProvider.System.GetUtcNow()));
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
var score = await provider.ScoreAsync(normalized, ct).ConfigureAwait(false);
|
|
results.Add(new RiskScoreResult(Guid.NewGuid(), normalized.Provider, normalized.Subject, score, true, null, normalized.Signals, TimeProvider.System.GetUtcNow()));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
results.Add(new RiskScoreResult(Guid.NewGuid(), normalized.Provider, normalized.Subject, 0d, false, ex.Message, normalized.Signals, TimeProvider.System.GetUtcNow()));
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|