Files
git.stella-ops.org/src/Integrations/StellaOps.Integrations.WebService/Program.cs
master 78afc39d2d feat(integrations): secret authority service for UI-driven secret staging
Add SecretAuthorityService + endpoints so the setup wizard and
integrations hub can stage secret bundles and bind authref URIs
directly from the UI, instead of requiring out-of-band Vault seeding.
Wire the new service behind IntegrationPolicies, expose
SecretAuthorityDtos on the contracts library, and register an
UpsertSecretBundle audit action for the emission library.

Closes BOOTSTRAP-006 from SPRINT_20260413_004.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 07:55:49 +03:00

189 lines
7.3 KiB
C#

using Microsoft.EntityFrameworkCore;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Integrations.Persistence;
using StellaOps.Integrations.Plugin.GitHubApp;
using StellaOps.Integrations.Plugin.Harbor;
using StellaOps.Integrations.Plugin.InMemory;
using StellaOps.Integrations.Plugin.Gitea;
using StellaOps.Integrations.Plugin.Jenkins;
using StellaOps.Integrations.Plugin.Nexus;
using StellaOps.Integrations.Plugin.DockerRegistry;
using StellaOps.Integrations.Plugin.GitLab;
using StellaOps.Integrations.Plugin.Vault;
using StellaOps.Integrations.Plugin.Consul;
using StellaOps.Integrations.Plugin.EbpfAgent;
using StellaOps.Integrations.WebService;
using StellaOps.Integrations.WebService.AiCodeGuard;
using StellaOps.Integrations.WebService.Infrastructure;
using StellaOps.Integrations.WebService.Security;
using StellaOps.Audit.Emission;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Infrastructure.Postgres.Migrations;
using StellaOps.Infrastructure.Postgres.Options;
using StellaOps.Localization;
using StellaOps.Router.AspNet;
using System.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new() { Title = "StellaOps Integration Catalog API", Version = "v1" });
});
// Database
var connectionString = builder.Configuration.GetConnectionString("IntegrationsDb")
?? builder.Configuration.GetConnectionString("Default")
?? "Host=localhost;Database=stellaops_integrations;Username=postgres;Password=postgres";
builder.Services.Configure<PostgresOptions>(options =>
{
options.ConnectionString = connectionString;
options.SchemaName = IntegrationDbContext.DefaultSchemaName;
});
builder.Services.AddDbContext<IntegrationDbContext>(options =>
options.UseNpgsql(connectionString));
builder.Services.AddStartupMigrations(
IntegrationDbContext.DefaultSchemaName,
"Integrations.Persistence",
typeof(IntegrationDbContext).Assembly);
// Repository
builder.Services.AddScoped<IIntegrationRepository, PostgresIntegrationRepository>();
// HttpClient factory (used by AuthRef resolver for Vault)
builder.Services.AddHttpClient(VaultAuthRefResolver.HttpClientName, client =>
{
var vaultAddr = builder.Configuration["VAULT_ADDR"] ?? "http://vault.stella-ops.local:8200";
client.BaseAddress = new Uri(vaultAddr.TrimEnd('/') + "/");
client.Timeout = TimeSpan.FromSeconds(15);
});
builder.Services.AddHttpClient(S3CompatibleConnectorPlugin.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOps", "1.0"));
});
builder.Services.AddHttpClient(FeedMirrorConnectorPluginBase.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOps", "1.0"));
});
// Plugin loader
builder.Services.AddSingleton<IntegrationPluginLoader>(sp =>
{
var logger = sp.GetRequiredService<ILogger<IntegrationPluginLoader>>();
var loader = new IntegrationPluginLoader(logger, sp);
// Load from plugins directory
var pluginsDir = builder.Configuration.GetValue<string>("Integrations:PluginsDirectory")
?? Path.Combine(AppContext.BaseDirectory, "plugins");
if (Directory.Exists(pluginsDir))
{
loader.LoadFromDirectory(pluginsDir);
}
// Also load from current assembly (for built-in plugins)
loader.LoadFromAssemblies(
[
typeof(Program).Assembly,
typeof(GitHubAppConnectorPlugin).Assembly,
typeof(HarborConnectorPlugin).Assembly,
typeof(InMemoryConnectorPlugin).Assembly,
typeof(GiteaConnectorPlugin).Assembly,
typeof(JenkinsConnectorPlugin).Assembly,
typeof(NexusConnectorPlugin).Assembly,
typeof(DockerRegistryConnectorPlugin).Assembly,
typeof(GitLabConnectorPlugin).Assembly,
typeof(VaultConnectorPlugin).Assembly,
typeof(ConsulConnectorPlugin).Assembly,
typeof(EbpfAgentConnectorPlugin).Assembly
]);
return loader;
});
builder.Services.AddSingleton(TimeProvider.System);
// Infrastructure
builder.Services.AddScoped<IIntegrationEventPublisher, LoggingEventPublisher>();
builder.Services.AddScoped<IIntegrationAuditLogger, LoggingAuditLogger>();
builder.Services.AddScoped<IAuthRefResolver, VaultAuthRefResolver>();
// Core service
builder.Services.AddScoped<IntegrationService>();
builder.Services.AddScoped<SecretAuthorityService>();
builder.Services.AddSingleton<IAiCodeGuardPipelineConfigLoader, AiCodeGuardPipelineConfigLoader>();
builder.Services.AddScoped<IAiCodeGuardRunService, AiCodeGuardRunService>();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.Services.AddStellaOpsLocalization(builder.Configuration);
builder.Services.AddTranslationBundle(System.Reflection.Assembly.GetExecutingAssembly());
// Authentication and authorization
builder.Services.AddStellaOpsResourceServerAuthentication(builder.Configuration);
builder.Services.AddAuthorization(options =>
{
options.AddStellaOpsScopePolicy(IntegrationPolicies.Read, StellaOpsScopes.IntegrationRead);
options.AddStellaOpsScopePolicy(IntegrationPolicies.Write, StellaOpsScopes.IntegrationWrite);
options.AddStellaOpsScopePolicy(IntegrationPolicies.Operate, StellaOpsScopes.IntegrationOperate);
options.AddStellaOpsScopePolicy(IntegrationPolicies.SecretAuthorityRead, StellaOpsScopes.IntegrationRead);
options.AddStellaOpsScopePolicy(IntegrationPolicies.SecretAuthorityWrite, StellaOpsScopes.IntegrationWrite);
});
// Unified audit emission (posts audit events to Timeline service)
builder.Services.AddAuditEmission(builder.Configuration);
// Stella Router integration
var routerEnabled = builder.Services.AddRouterMicroservice(
builder.Configuration,
serviceName: "integrations",
version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0",
routerOptionsSection: "Router");
builder.Services.AddStellaOpsTenantServices();
builder.TryAddStellaOpsLocalBinding("integrations");
var app = builder.Build();
app.LogStellaOpsLocalHostname("integrations");
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseStellaOpsCors();
app.UseStellaOpsLocalization();
app.UseIdentityEnvelopeAuthentication();
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
// Map endpoints
app.MapIntegrationEndpoints();
app.MapSecretAuthorityEndpoints();
// Health endpoint
app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTimeOffset.UtcNow }))
.WithTags("Health")
.WithName("HealthCheck")
.WithDescription("Returns the liveness status and current UTC timestamp for the Integration Catalog service. Used by the Router gateway and container orchestrator for health polling.")
.AllowAnonymous();
// Database schema created by SQL migrations (001_initial_schema.sql)
// Run via: stella-ops migration run Integrations --category startup
app.TryRefreshStellaRouterEndpoints(routerEnabled);
await app.LoadTranslationsAsync();
app.Run();
public partial class Program { }