213 lines
8.7 KiB
C#
213 lines
8.7 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using StellaOps.Auth.ServerIntegration;
|
|
using StellaOps.Auth.ServerIntegration.Tenancy;
|
|
using StellaOps.Infrastructure.Postgres.Options;
|
|
using StellaOps.Localization;
|
|
using StellaOps.Remediation.Core.Abstractions;
|
|
using StellaOps.Remediation.Core.Services;
|
|
using StellaOps.Remediation.Persistence.Postgres;
|
|
using StellaOps.Remediation.Persistence.Repositories;
|
|
using StellaOps.Remediation.WebService.Endpoints;
|
|
using StellaOps.Router.AspNet;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
var storageDriver = ResolveStorageDriver(builder.Configuration, "Remediation");
|
|
|
|
builder.Services.AddProblemDetails();
|
|
builder.Services.AddHealthChecks();
|
|
builder.Services.AddRouting(options => options.LowercaseUrls = true);
|
|
builder.Services.AddAuthorization(options =>
|
|
{
|
|
options.AddPolicy("remediation.read", policy => policy.RequireAssertion(_ => true));
|
|
options.AddPolicy("remediation.submit", policy => policy.RequireAssertion(_ => true));
|
|
options.AddPolicy("remediation.manage", policy => policy.RequireAssertion(_ => true));
|
|
});
|
|
builder.Services.AddAuthentication();
|
|
builder.Services.AddStellaOpsTenantServices();
|
|
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
|
|
builder.Services.AddStellaOpsLocalization(builder.Configuration);
|
|
builder.Services.AddTranslationBundle(System.Reflection.Assembly.GetExecutingAssembly());
|
|
|
|
// Core services
|
|
builder.Services.AddSingleton<IContributorTrustScorer, ContributorTrustScorer>();
|
|
builder.Services.AddSingleton<IRemediationVerifier, RemediationVerifier>();
|
|
|
|
RegisterPersistence(builder.Services, builder.Configuration, builder.Environment, storageDriver);
|
|
|
|
// Registry/matcher: compose from repositories.
|
|
builder.Services.AddSingleton<IRemediationRegistry, RepositoryBackedRemediationRegistry>();
|
|
builder.Services.AddSingleton<IRemediationMatcher, RepositoryBackedRemediationMatcher>();
|
|
|
|
var routerEnabled = builder.Services.AddRouterMicroservice(
|
|
builder.Configuration,
|
|
serviceName: "remediation",
|
|
version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0",
|
|
routerOptionsSection: "Router");
|
|
builder.TryAddStellaOpsLocalBinding("remediation");
|
|
|
|
var app = builder.Build();
|
|
app.LogStellaOpsLocalHostname("remediation");
|
|
|
|
app.UseAuthentication();
|
|
app.UseStellaOpsCors();
|
|
app.UseStellaOpsLocalization();
|
|
app.UseAuthorization();
|
|
app.UseStellaOpsTenantMiddleware();
|
|
app.TryUseStellaRouter(routerEnabled);
|
|
|
|
app.MapHealthChecks("/healthz").AllowAnonymous();
|
|
|
|
app.MapRemediationRegistryEndpoints();
|
|
app.MapRemediationMatchEndpoints();
|
|
app.MapRemediationSourceEndpoints();
|
|
|
|
await app.LoadTranslationsAsync();
|
|
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
|
app.Run();
|
|
|
|
static void RegisterPersistence(
|
|
IServiceCollection services,
|
|
IConfiguration configuration,
|
|
IHostEnvironment environment,
|
|
string storageDriver)
|
|
{
|
|
if (string.Equals(storageDriver, "postgres", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var connectionString = ResolvePostgresConnectionString(configuration, "Remediation");
|
|
if (string.IsNullOrWhiteSpace(connectionString))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Remediation requires PostgreSQL connection settings when Storage:Driver=postgres. " +
|
|
"Set ConnectionStrings:Default or Remediation:Storage:Postgres:ConnectionString.");
|
|
}
|
|
|
|
var schemaName = ResolveSchemaName(configuration, "Remediation") ?? RemediationDataSource.DefaultSchemaName;
|
|
|
|
services.Configure<PostgresOptions>(options =>
|
|
{
|
|
options.ConnectionString = connectionString;
|
|
options.SchemaName = schemaName;
|
|
});
|
|
services.AddSingleton<RemediationDataSource>();
|
|
services.AddSingleton<IFixTemplateRepository, PostgresFixTemplateRepository>();
|
|
services.AddSingleton<IPrSubmissionRepository, PostgresPrSubmissionRepository>();
|
|
services.AddSingleton<IMarketplaceSourceRepository, PostgresMarketplaceSourceRepository>();
|
|
return;
|
|
}
|
|
|
|
if (string.Equals(storageDriver, "inmemory", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (!IsTestEnvironment(environment))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Remediation in-memory storage driver is restricted to Test/Testing environments.");
|
|
}
|
|
|
|
services.AddSingleton<IFixTemplateRepository>(_ => new PostgresFixTemplateRepository());
|
|
services.AddSingleton<IPrSubmissionRepository>(_ => new PostgresPrSubmissionRepository());
|
|
services.AddSingleton<IMarketplaceSourceRepository>(_ => new PostgresMarketplaceSourceRepository());
|
|
return;
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
$"Unsupported Remediation storage driver '{storageDriver}'. Allowed values: postgres, inmemory.");
|
|
}
|
|
|
|
static bool IsTestEnvironment(IHostEnvironment environment)
|
|
{
|
|
return environment.IsEnvironment("Test") || environment.IsEnvironment("Testing");
|
|
}
|
|
|
|
static string ResolveStorageDriver(IConfiguration configuration, string serviceName)
|
|
{
|
|
return FirstNonEmpty(
|
|
configuration[$"{serviceName}:Storage:Driver"],
|
|
configuration["Storage:Driver"])
|
|
?? "postgres";
|
|
}
|
|
|
|
static string? ResolveSchemaName(IConfiguration configuration, string serviceName)
|
|
{
|
|
return FirstNonEmpty(
|
|
configuration[$"{serviceName}:Storage:Postgres:SchemaName"],
|
|
configuration["Storage:Postgres:SchemaName"],
|
|
configuration[$"Postgres:{serviceName}:SchemaName"]);
|
|
}
|
|
|
|
static string? ResolvePostgresConnectionString(IConfiguration configuration, string serviceName)
|
|
{
|
|
return FirstNonEmpty(
|
|
configuration[$"{serviceName}:Storage:Postgres:ConnectionString"],
|
|
configuration["Storage:Postgres:ConnectionString"],
|
|
configuration[$"Postgres:{serviceName}:ConnectionString"],
|
|
configuration[$"ConnectionStrings:{serviceName}"],
|
|
configuration["ConnectionStrings:Default"]);
|
|
}
|
|
|
|
static string? FirstNonEmpty(params string?[] values)
|
|
{
|
|
foreach (var value in values)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public partial class Program { }
|
|
|
|
/// <summary>
|
|
/// Repository-backed registry implementation composed from repositories.
|
|
/// </summary>
|
|
internal sealed class RepositoryBackedRemediationRegistry : IRemediationRegistry
|
|
{
|
|
private readonly IFixTemplateRepository _templates;
|
|
private readonly IPrSubmissionRepository _submissions;
|
|
|
|
public RepositoryBackedRemediationRegistry(IFixTemplateRepository templates, IPrSubmissionRepository submissions)
|
|
{
|
|
_templates = templates;
|
|
_submissions = submissions;
|
|
}
|
|
|
|
public Task<IReadOnlyList<StellaOps.Remediation.Core.Models.FixTemplate>> ListTemplatesAsync(string? cveId, string? purl, int limit, int offset, CancellationToken ct)
|
|
=> _templates.ListAsync(cveId, purl, limit, offset, ct);
|
|
|
|
public Task<StellaOps.Remediation.Core.Models.FixTemplate?> GetTemplateAsync(Guid id, CancellationToken ct)
|
|
=> _templates.GetByIdAsync(id, ct);
|
|
|
|
public Task<StellaOps.Remediation.Core.Models.FixTemplate> CreateTemplateAsync(StellaOps.Remediation.Core.Models.FixTemplate template, CancellationToken ct)
|
|
=> _templates.InsertAsync(template, ct);
|
|
|
|
public Task<IReadOnlyList<StellaOps.Remediation.Core.Models.PrSubmission>> ListSubmissionsAsync(string? cveId, string? status, int limit, int offset, CancellationToken ct)
|
|
=> _submissions.ListAsync(cveId, status, limit, offset, ct);
|
|
|
|
public Task<StellaOps.Remediation.Core.Models.PrSubmission?> GetSubmissionAsync(Guid id, CancellationToken ct)
|
|
=> _submissions.GetByIdAsync(id, ct);
|
|
|
|
public Task<StellaOps.Remediation.Core.Models.PrSubmission> CreateSubmissionAsync(StellaOps.Remediation.Core.Models.PrSubmission submission, CancellationToken ct)
|
|
=> _submissions.InsertAsync(submission, ct);
|
|
|
|
public Task UpdateSubmissionStatusAsync(Guid id, string status, string? verdict, CancellationToken ct)
|
|
=> _submissions.UpdateStatusAsync(id, status, verdict, ct);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository-backed matcher implementation that delegates to template repository.
|
|
/// </summary>
|
|
internal sealed class RepositoryBackedRemediationMatcher : IRemediationMatcher
|
|
{
|
|
private readonly IFixTemplateRepository _templates;
|
|
|
|
public RepositoryBackedRemediationMatcher(IFixTemplateRepository templates)
|
|
{
|
|
_templates = templates;
|
|
}
|
|
|
|
public Task<IReadOnlyList<StellaOps.Remediation.Core.Models.FixTemplate>> FindMatchesAsync(string cveId, string? purl, string? version, CancellationToken ct)
|
|
=> _templates.FindMatchesAsync(cveId, purl, version, ct);
|
|
}
|