using System.Linq; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration; using StellaOps.Plugin.DependencyInjection; using StellaOps.Plugin.Hosting; using StellaOps.Scheduler.WebService.Hosting; using StellaOps.Scheduler.ImpactIndex; using StellaOps.Scheduler.Storage.Postgres; using StellaOps.Scheduler.Storage.Postgres.Repositories; using StellaOps.Scheduler.WebService; using StellaOps.Scheduler.WebService.Auth; using StellaOps.Scheduler.WebService.EventWebhooks; using StellaOps.Scheduler.WebService.GraphJobs; using StellaOps.Scheduler.WebService.GraphJobs.Events; using StellaOps.Scheduler.WebService.Schedules; using StellaOps.Scheduler.WebService.Options; using StellaOps.Scheduler.WebService.PolicyRuns; using StellaOps.Scheduler.WebService.PolicySimulations; using StellaOps.Scheduler.WebService.VulnerabilityResolverJobs; using StellaOps.Scheduler.WebService.Runs; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRouting(options => options.LowercaseUrls = true); builder.Services.AddSingleton(); builder.Services.TryAddSingleton(TimeProvider.System); var authorityOptions = new SchedulerAuthorityOptions(); builder.Configuration.GetSection("Scheduler:Authority").Bind(authorityOptions); if (!authorityOptions.RequiredScopes.Any(scope => string.Equals(scope, StellaOpsScopes.GraphRead, StringComparison.OrdinalIgnoreCase))) { authorityOptions.RequiredScopes.Add(StellaOpsScopes.GraphRead); } if (!authorityOptions.RequiredScopes.Any(scope => string.Equals(scope, StellaOpsScopes.GraphWrite, StringComparison.OrdinalIgnoreCase))) { authorityOptions.RequiredScopes.Add(StellaOpsScopes.GraphWrite); } if (authorityOptions.Audiences.Count == 0) { authorityOptions.Audiences.Add("api://scheduler"); } authorityOptions.Validate(); builder.Services.AddSingleton(authorityOptions); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("Scheduler:Events")) .PostConfigure(options => { options.Webhooks ??= new SchedulerInboundWebhooksOptions(); options.Webhooks.Conselier ??= SchedulerWebhookOptions.CreateDefault("conselier"); options.Webhooks.Excitor ??= SchedulerWebhookOptions.CreateDefault("excitor"); options.Webhooks.Conselier.Name = string.IsNullOrWhiteSpace(options.Webhooks.Conselier.Name) ? "conselier" : options.Webhooks.Conselier.Name; options.Webhooks.Excitor.Name = string.IsNullOrWhiteSpace(options.Webhooks.Excitor.Name) ? "excitor" : options.Webhooks.Excitor.Name; options.Webhooks.Conselier.Validate(); options.Webhooks.Excitor.Validate(); }); builder.Services.AddMemoryCache(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); var cartographerOptions = builder.Configuration.GetSection("Scheduler:Cartographer").Get() ?? new SchedulerCartographerOptions(); builder.Services.AddSingleton(cartographerOptions); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("Scheduler:Cartographer")); var storageSection = builder.Configuration.GetSection("Scheduler:Storage"); if (storageSection.Exists()) { builder.Services.AddSchedulerPostgresStorage(storageSection); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(static sp => (IPolicySimulationMetricsRecorder)sp.GetRequiredService()); } else { builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); } builder.Services.AddSingleton(); builder.Services.AddSingleton(); if (cartographerOptions.Webhook.Enabled) { builder.Services.AddHttpClient((serviceProvider, client) => { var options = serviceProvider.GetRequiredService>().CurrentValue; client.Timeout = TimeSpan.FromSeconds(options.Webhook.TimeoutSeconds <= 0 ? 10 : options.Webhook.TimeoutSeconds); }); } else { builder.Services.AddSingleton(); } builder.Services.AddScoped(); builder.Services.AddImpactIndexStub(); builder.Services.AddResolverJobServices(); var schedulerOptions = builder.Configuration.GetSection("Scheduler").Get() ?? new SchedulerOptions(); schedulerOptions.Validate(); builder.Services.AddSingleton(schedulerOptions); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("Scheduler")) .PostConfigure(options => options.Validate()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("Scheduler:RunStream")); var pluginHostOptions = SchedulerPluginHostFactory.Build(schedulerOptions.Plugins, builder.Environment.ContentRootPath); builder.Services.AddSingleton(pluginHostOptions); builder.Services.RegisterPluginRoutines(builder.Configuration, pluginHostOptions); if (authorityOptions.Enabled) { builder.Services.AddHttpContextAccessor(); builder.Services.AddStellaOpsResourceServerAuthentication( builder.Configuration, configurationSection: null, configure: resourceOptions => { resourceOptions.Authority = authorityOptions.Issuer; resourceOptions.RequireHttpsMetadata = authorityOptions.RequireHttpsMetadata; resourceOptions.MetadataAddress = authorityOptions.MetadataAddress; resourceOptions.BackchannelTimeout = TimeSpan.FromSeconds(authorityOptions.BackchannelTimeoutSeconds); resourceOptions.TokenClockSkew = TimeSpan.FromSeconds(authorityOptions.TokenClockSkewSeconds); foreach (var audience in authorityOptions.Audiences) { resourceOptions.Audiences.Add(audience); } foreach (var scope in authorityOptions.RequiredScopes) { resourceOptions.RequiredScopes.Add(scope); } foreach (var tenant in authorityOptions.RequiredTenants) { resourceOptions.RequiredTenants.Add(tenant); } foreach (var network in authorityOptions.BypassNetworks) { resourceOptions.BypassNetworks.Add(network); } }); builder.Services.AddAuthorization(); builder.Services.AddScoped(); builder.Services.AddScoped(); } else { builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "Anonymous"; options.DefaultChallengeScheme = "Anonymous"; }).AddScheme("Anonymous", static _ => { }); builder.Services.AddAuthorization(); builder.Services.AddScoped(); builder.Services.AddScoped(); } builder.Services.AddEndpointsApiExplorer(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); if (!authorityOptions.Enabled) { app.Logger.LogWarning("Scheduler Authority authentication is disabled; relying on header-based development fallback."); } else if (authorityOptions.AllowAnonymousFallback) { app.Logger.LogWarning("Scheduler Authority authentication is enabled but anonymous fallback remains allowed. Disable fallback before production rollout."); } app.MapGet("/healthz", () => Results.Json(new { status = "ok" })); app.MapGet("/readyz", () => Results.Json(new { status = "ready" })); app.MapGraphJobEndpoints(); ResolverJobEndpointExtensions.MapResolverJobEndpoints(app); app.MapScheduleEndpoints(); app.MapRunEndpoints(); app.MapPolicyRunEndpoints(); app.MapPolicySimulationEndpoints(); app.MapSchedulerEventWebhookEndpoints(); app.Run(); public partial class Program;