138 lines
4.7 KiB
C#
138 lines
4.7 KiB
C#
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using StellaOps.Auth.Abstractions;
|
|
using StellaOps.Auth.ServerIntegration;
|
|
using StellaOps.Cryptography.Audit;
|
|
using StellaOps.Router.AspNet;
|
|
using StellaOps.TimelineIndexer.Core.Abstractions;
|
|
using StellaOps.TimelineIndexer.Core.Models;
|
|
using StellaOps.TimelineIndexer.Infrastructure.DependencyInjection;
|
|
using StellaOps.TimelineIndexer.WebService;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
|
|
builder.Configuration.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);
|
|
builder.Configuration.AddEnvironmentVariables(prefix: "TIMELINE_");
|
|
|
|
builder.Services.AddTimelineIndexerPostgres(builder.Configuration);
|
|
builder.Services.AddSingleton<IAuthEventSink, TimelineAuthorizationAuditSink>();
|
|
|
|
builder.Services.AddStellaOpsResourceServerAuthentication(
|
|
builder.Configuration,
|
|
configure: options =>
|
|
{
|
|
options.RequiredScopes.Clear();
|
|
});
|
|
|
|
builder.Services.AddAuthorization(options =>
|
|
{
|
|
options.AddObservabilityResourcePolicies();
|
|
options.DefaultPolicy = new AuthorizationPolicyBuilder()
|
|
.RequireAuthenticatedUser()
|
|
.AddRequirements(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.TimelineRead }))
|
|
.Build();
|
|
options.FallbackPolicy = options.DefaultPolicy;
|
|
});
|
|
|
|
builder.Services.AddOpenApi();
|
|
|
|
// Stella Router integration
|
|
var routerOptions = builder.Configuration.GetSection("TimelineIndexer:Router").Get<StellaRouterOptionsBase>();
|
|
builder.Services.TryAddStellaRouter(
|
|
serviceName: "timelineindexer",
|
|
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.UseAuthentication();
|
|
app.UseAuthorization();
|
|
app.TryUseStellaRouter(routerOptions);
|
|
|
|
app.MapGet("/timeline", async (
|
|
HttpContext ctx,
|
|
ITimelineQueryService service,
|
|
[FromQuery] string? eventType,
|
|
[FromQuery] string? source,
|
|
[FromQuery] string? correlationId,
|
|
[FromQuery] string? traceId,
|
|
[FromQuery] string? severity,
|
|
[FromQuery] DateTimeOffset? since,
|
|
[FromQuery] long? after,
|
|
[FromQuery] int? limit,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
var tenantId = GetTenantId(ctx);
|
|
var options = new TimelineQueryOptions
|
|
{
|
|
EventType = eventType,
|
|
Source = source,
|
|
CorrelationId = correlationId,
|
|
TraceId = traceId,
|
|
Severity = severity,
|
|
Since = since,
|
|
AfterEventSeq = after,
|
|
Limit = limit ?? 100
|
|
};
|
|
var items = await service.QueryAsync(tenantId, options, cancellationToken).ConfigureAwait(false);
|
|
return Results.Ok(items);
|
|
})
|
|
.RequireAuthorization(StellaOpsResourceServerPolicies.TimelineRead);
|
|
|
|
app.MapGet("/timeline/{eventId}", async (
|
|
HttpContext ctx,
|
|
ITimelineQueryService service,
|
|
string eventId,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
var tenantId = GetTenantId(ctx);
|
|
var item = await service.GetAsync(tenantId, eventId, cancellationToken).ConfigureAwait(false);
|
|
return item is null ? Results.NotFound() : Results.Ok(item);
|
|
})
|
|
.RequireAuthorization(StellaOpsResourceServerPolicies.TimelineRead);
|
|
|
|
app.MapGet("/timeline/{eventId}/evidence", async (
|
|
HttpContext ctx,
|
|
ITimelineQueryService service,
|
|
string eventId,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
var tenantId = GetTenantId(ctx);
|
|
var evidence = await service.GetEvidenceAsync(tenantId, eventId, cancellationToken).ConfigureAwait(false);
|
|
return evidence is null ? Results.NotFound() : Results.Ok(evidence);
|
|
})
|
|
.RequireAuthorization(StellaOpsResourceServerPolicies.TimelineRead);
|
|
|
|
app.MapPost("/timeline/events", () => Results.Accepted("/timeline/events", new { status = "indexed" }))
|
|
.RequireAuthorization(StellaOpsResourceServerPolicies.TimelineWrite);
|
|
|
|
// Refresh Router endpoint cache
|
|
app.TryRefreshStellaRouterEndpoints(routerOptions);
|
|
|
|
app.Run();
|
|
|
|
static string GetTenantId(HttpContext ctx)
|
|
{
|
|
// Temporary: allow explicit header override; fallback to claim if present.
|
|
if (ctx.Request.Headers.TryGetValue("X-Tenant", out var header) && !string.IsNullOrWhiteSpace(header))
|
|
{
|
|
return header!;
|
|
}
|
|
|
|
var tenant = ctx.User.FindFirst("tenant")?.Value;
|
|
if (!string.IsNullOrWhiteSpace(tenant))
|
|
{
|
|
return tenant!;
|
|
}
|
|
|
|
throw new InvalidOperationException("Tenant not provided");
|
|
}
|