Files
git.stella-ops.org/src/Scheduler/StellaOps.Scheduler.WebService/Program.cs
StellaOps Bot 05597616d6 feat: Add Go module and workspace test fixtures
- Created expected JSON files for Go modules and workspaces.
- Added go.mod and go.sum files for example projects.
- Implemented private module structure with expected JSON output.
- Introduced vendored dependencies with corresponding expected JSON.
- Developed PostgresGraphJobStore for managing graph jobs.
- Established SQL migration scripts for graph jobs schema.
- Implemented GraphJobRepository for CRUD operations on graph jobs.
- Created IGraphJobRepository interface for repository abstraction.
- Added unit tests for GraphJobRepository to ensure functionality.
2025-12-06 20:04:03 +02:00

218 lines
9.4 KiB
C#

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<StellaOps.Scheduler.WebService.ISystemClock, StellaOps.Scheduler.WebService.SystemClock>();
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<SchedulerEventsOptions>()
.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<IWebhookRateLimiter, InMemoryWebhookRateLimiter>();
builder.Services.AddSingleton<IWebhookRequestAuthenticator, WebhookRequestAuthenticator>();
builder.Services.AddSingleton<IInboundExportEventSink, LoggingExportEventSink>();
builder.Services.AddSingleton<IRedisConnectionFactory, RedisConnectionFactory>();
var cartographerOptions = builder.Configuration.GetSection("Scheduler:Cartographer").Get<SchedulerCartographerOptions>() ?? new SchedulerCartographerOptions();
builder.Services.AddSingleton(cartographerOptions);
builder.Services.AddOptions<SchedulerCartographerOptions>()
.Bind(builder.Configuration.GetSection("Scheduler:Cartographer"));
var storageSection = builder.Configuration.GetSection("Scheduler:Storage");
if (storageSection.Exists())
{
builder.Services.AddSchedulerPostgresStorage(storageSection);
builder.Services.AddScoped<IGraphJobRepository, GraphJobRepository>();
builder.Services.AddSingleton<IGraphJobStore, PostgresGraphJobStore>();
builder.Services.AddSingleton<IPolicyRunService, PolicyRunService>();
builder.Services.AddSingleton<IPolicySimulationMetricsProvider, PolicySimulationMetricsProvider>();
builder.Services.AddSingleton<IPolicySimulationMetricsRecorder>(static sp => (IPolicySimulationMetricsRecorder)sp.GetRequiredService<IPolicySimulationMetricsProvider>());
}
else
{
builder.Services.AddSingleton<IGraphJobStore, InMemoryGraphJobStore>();
builder.Services.AddSingleton<IScheduleRepository, InMemoryScheduleRepository>();
builder.Services.AddSingleton<IRunRepository, InMemoryRunRepository>();
builder.Services.AddSingleton<IRunSummaryService, InMemoryRunSummaryService>();
builder.Services.AddSingleton<ISchedulerAuditService, InMemorySchedulerAuditService>();
builder.Services.AddSingleton<IPolicyRunService, InMemoryPolicyRunService>();
}
builder.Services.AddSingleton<IGraphJobCompletionPublisher, GraphJobEventPublisher>();
builder.Services.AddSingleton<IResolverJobService, InMemoryResolverJobService>();
if (cartographerOptions.Webhook.Enabled)
{
builder.Services.AddHttpClient<ICartographerWebhookClient, CartographerWebhookClient>((serviceProvider, client) =>
{
var options = serviceProvider.GetRequiredService<IOptionsMonitor<SchedulerCartographerOptions>>().CurrentValue;
client.Timeout = TimeSpan.FromSeconds(options.Webhook.TimeoutSeconds <= 0 ? 10 : options.Webhook.TimeoutSeconds);
});
}
else
{
builder.Services.AddSingleton<ICartographerWebhookClient, NullCartographerWebhookClient>();
}
builder.Services.AddScoped<IGraphJobService, GraphJobService>();
builder.Services.AddImpactIndexStub();
builder.Services.AddResolverJobServices();
var schedulerOptions = builder.Configuration.GetSection("Scheduler").Get<SchedulerOptions>() ?? new SchedulerOptions();
schedulerOptions.Validate();
builder.Services.AddSingleton(schedulerOptions);
builder.Services.AddOptions<SchedulerOptions>()
.Bind(builder.Configuration.GetSection("Scheduler"))
.PostConfigure(options => options.Validate());
builder.Services.AddSingleton<IQueueLagSummaryProvider, QueueLagSummaryProvider>();
builder.Services.AddSingleton<IRunStreamCoordinator, RunStreamCoordinator>();
builder.Services.AddSingleton<IPolicySimulationStreamCoordinator, PolicySimulationStreamCoordinator>();
builder.Services.AddOptions<RunStreamOptions>()
.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<ITenantContextAccessor, ClaimsTenantContextAccessor>();
builder.Services.AddScoped<IScopeAuthorizer, TokenScopeAuthorizer>();
}
else
{
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Anonymous";
options.DefaultChallengeScheme = "Anonymous";
}).AddScheme<AuthenticationSchemeOptions, AnonymousAuthenticationHandler>("Anonymous", static _ => { });
builder.Services.AddAuthorization();
builder.Services.AddScoped<ITenantContextAccessor, HeaderTenantContextAccessor>();
builder.Services.AddScoped<IScopeAuthorizer, HeaderScopeAuthorizer>();
}
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;