feat: Implement vulnerability token signing and verification utilities
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Added VulnTokenSigner for signing JWT tokens with specified algorithms and keys.
- Introduced VulnTokenUtilities for resolving tenant and subject claims, and sanitizing context dictionaries.
- Created VulnTokenVerificationUtilities for parsing tokens, verifying signatures, and deserializing payloads.
- Developed VulnWorkflowAntiForgeryTokenIssuer for issuing anti-forgery tokens with configurable options.
- Implemented VulnWorkflowAntiForgeryTokenVerifier for verifying anti-forgery tokens and validating payloads.
- Added AuthorityVulnerabilityExplorerOptions to manage configuration for vulnerability explorer features.
- Included tests for FilesystemPackRunDispatcher to ensure proper job handling under egress policy restrictions.
This commit is contained in:
master
2025-11-03 10:02:29 +02:00
parent bf2bf4b395
commit b1e78fe412
215 changed files with 19441 additions and 12185 deletions

View File

@@ -1,203 +1,203 @@
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.Mongo;
using StellaOps.Scheduler.Storage.Mongo.Repositories;
using StellaOps.Scheduler.Storage.Mongo.Services;
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.Runs;
using StellaOps.Scheduler.WebService.PolicyRuns;
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);
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.Mongo;
using StellaOps.Scheduler.Storage.Mongo.Repositories;
using StellaOps.Scheduler.Storage.Mongo.Services;
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.Runs;
using StellaOps.Scheduler.WebService.PolicyRuns;
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.Feedser ??= SchedulerWebhookOptions.CreateDefault("feedser");
options.Webhooks.Vexer ??= SchedulerWebhookOptions.CreateDefault("vexer");
options.Webhooks.Feedser.Name = string.IsNullOrWhiteSpace(options.Webhooks.Feedser.Name)
? "feedser"
: options.Webhooks.Feedser.Name;
options.Webhooks.Vexer.Name = string.IsNullOrWhiteSpace(options.Webhooks.Vexer.Name)
? "vexer"
: options.Webhooks.Vexer.Name;
options.Webhooks.Feedser.Validate();
options.Webhooks.Vexer.Validate();
});
.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.AddSchedulerMongoStorage(storageSection);
builder.Services.AddSingleton<IGraphJobStore, MongoGraphJobStore>();
builder.Services.AddSingleton<IPolicyRunService, PolicyRunService>();
}
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>();
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();
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());
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();
app.MapScheduleEndpoints();
app.MapRunEndpoints();
app.MapPolicyRunEndpoints();
app.MapSchedulerEventWebhookEndpoints();
app.Run();
public partial class Program;
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.AddSchedulerMongoStorage(storageSection);
builder.Services.AddSingleton<IGraphJobStore, MongoGraphJobStore>();
builder.Services.AddSingleton<IPolicyRunService, PolicyRunService>();
}
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>();
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();
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());
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();
app.MapScheduleEndpoints();
app.MapRunEndpoints();
app.MapPolicyRunEndpoints();
app.MapSchedulerEventWebhookEndpoints();
app.Run();
public partial class Program;