Agrega Quartz.Extensions.Hosting 3.13.1 al catálogo central.
SIGCM2.Infrastructure/Audit/Jobs/:
- AuditPartitionManagerJob — mensual (cron '0 0 2 1 * ?', UTC). Extiende
pf_AuditEvent_Monthly y pf_SecurityEvent_Monthly con SPLIT RANGE para el
mes+2 (mantiene +1 de buffer). Idempotente: verifica existencia antes.
- AuditRetentionEnforcerJob — anual (cron '0 0 3 1 1 ?', UTC). DELETE rows
> 10 años en AuditEvent y > 5 años en SecurityEvent. Temporal history se
purga solo vía HISTORY_RETENTION_PERIOD del engine.
- AuditIntegrityCheckJob — semanal domingos (cron '0 0 1 ? * SUN', UTC).
Valida SYSTEM_VERSIONING=ON + partitions próximos 3 meses. Emite
SecurityEvent 'system.integrity_alert' failure via ISecurityEventLogger
cuando detecta inconsistencias.
AuditMaintenanceRegistration.cs:
- services.AddAuditMaintenance(configuration) wraps AddQuartz + AddQuartzHostedService
con los 3 triggers crónicos.
Program.cs:
- builder.Services.AddAuditMaintenance(configuration) wired ONLY en entornos
productivos — skipeado en 'Testing' para que los integration tests no
disparen los triggers cron durante el ciclo de vida del TestWebAppFactory.
Row-based DELETE en RetentionEnforcerJob es la opción conservadora para la
primera generación — cuando los volúmenes lo justifiquen (>200M filas), se
upgradea a SWITCH OUT + DROP para partition-level drop. Documentado en
comentario de la clase.
Tests (Strict TDD, integration):
- AuditJobsTests (3): PartitionManager crea target boundary + idempotencia,
RetentionEnforcer purga > threshold (10y audit, 5y security), IntegrityCheck
all-OK no emite alert.
Suite: 381/381 Application.Tests + 147/147 Api.Tests = 528/528 passing.
Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-6 #REQ-SEC-5, design, tasks#B11}
100 lines
3.0 KiB
C#
100 lines
3.0 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Serilog;
|
|
using Scalar.AspNetCore;
|
|
using SIGCM2.Api.Authorization;
|
|
using SIGCM2.Api.HealthChecks;
|
|
using SIGCM2.Api.Middleware;
|
|
using SIGCM2.Application;
|
|
using SIGCM2.Infrastructure;
|
|
using SIGCM2.Infrastructure.Audit.Jobs;
|
|
using SIGCM2.Api.Filters;
|
|
|
|
// Bootstrap logger — before DI is built
|
|
Log.Logger = new LoggerConfiguration()
|
|
.WriteTo.Console()
|
|
.CreateBootstrapLogger();
|
|
|
|
Log.Information("Starting SIGCM2 API");
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Serilog — reads from appsettings.json "Serilog" section
|
|
builder.Host.UseSerilog((ctx, lc) => lc
|
|
.ReadFrom.Configuration(ctx.Configuration));
|
|
|
|
// Application + Infrastructure DI
|
|
builder.Services.AddApplication();
|
|
builder.Services.AddInfrastructure(builder.Configuration);
|
|
|
|
// UDT-010: Quartz.NET + 3 audit maintenance jobs (partition, retention, integrity).
|
|
// Disabled in Testing environment to keep integration tests deterministic.
|
|
if (!builder.Environment.IsEnvironment("Testing"))
|
|
builder.Services.AddAuditMaintenance(builder.Configuration);
|
|
|
|
// Authorization — handler lives in Api layer; DO NOT move to Infrastructure DI
|
|
builder.Services.AddAuthorization();
|
|
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
|
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, ForbiddenProblemDetailsHandler>();
|
|
|
|
// Controllers with exception filter
|
|
builder.Services.AddControllers(opts =>
|
|
{
|
|
opts.Filters.Add<ExceptionFilter>();
|
|
});
|
|
|
|
// OpenAPI / Scalar
|
|
builder.Services.AddOpenApi();
|
|
|
|
// UDT-010: Audit infrastructure health check
|
|
builder.Services.AddHealthChecks()
|
|
.AddCheck<AuditHealthCheck>("audit", tags: new[] { "audit" });
|
|
|
|
// CORS
|
|
var allowedOrigins = builder.Configuration
|
|
.GetSection("Cors:AllowedOrigins")
|
|
.Get<string[]>() ?? [];
|
|
|
|
builder.Services.AddCors(opts =>
|
|
{
|
|
opts.AddDefaultPolicy(policy =>
|
|
policy.WithOrigins(allowedOrigins)
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod());
|
|
});
|
|
|
|
var app = builder.Build();
|
|
|
|
// Middleware pipeline
|
|
app.UseSerilogRequestLogging();
|
|
|
|
if (app.Environment.IsDevelopment() || app.Environment.IsEnvironment("Testing"))
|
|
{
|
|
app.MapOpenApi();
|
|
app.MapScalarApiReference(opts =>
|
|
{
|
|
opts.Title = "SIGCM2 API";
|
|
});
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.UseCors();
|
|
// UDT-010: correlation id + ip/ua capture runs BEFORE auth so anonymous requests
|
|
// still get a correlation id and so logs can tie pre-auth events to the request.
|
|
app.UseMiddleware<CorrelationIdMiddleware>();
|
|
app.UseAuthentication();
|
|
// UDT-010: actor extraction runs AFTER auth to read the JWT sub claim.
|
|
app.UseMiddleware<AuditActorMiddleware>();
|
|
app.UseAuthorization();
|
|
app.MapControllers();
|
|
|
|
// UDT-010: /health/audit returns the audit check status (public but sparse data).
|
|
app.MapHealthChecks("/health/audit", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
|
|
{
|
|
Predicate = r => r.Tags.Contains("audit"),
|
|
});
|
|
|
|
app.Run();
|
|
|
|
// Exposed for WebApplicationFactory in integration tests
|
|
public partial class Program { }
|