Files
Chatbot-ElDia/ChatbotApi/Program.cs
dmolinari 67e179441d feat: Añadidos de seguridad (Backend, Frontend e IA)
Implementación de medidas de seguridad críticas tras auditoría:

Backend (API & IA):
- Anti-Prompt Injection: Reestructuración de prompts con delimitadores XML y sanitización estricta de inputs (Tag Injection).
- Anti-SSRF: Implementación de servicio `UrlSecurity` para validar URLs y bloquear accesos a IPs internas/privadas en funciones de scraping.
- Moderación: Activación de `SafetySettings` en Gemini API.
- Infraestructura:
  - Configuración de Headers de seguridad (HSTS, CSP, NoSniff).
  - CORS restrictivo (solo métodos HTTP necesarios).
  - Rate Limiting global y política estricta para Login (5 req/min).
  - Timeouts en HttpClient para prevenir DoS.
- Auth: Endpoint `setup-admin` restringido exclusivamente a entorno Debug.

Frontend (React):
- Anti-XSS & Tabnabbing: Configuración de esquema estricto en `rehype-sanitize` y forzado de `rel="noopener noreferrer"` en enlaces.
- Validación de longitud de input en cliente.

IA:
- Se realiza afinación de contexto de preguntas.
2025-11-27 15:11:54 -03:00

189 lines
6.6 KiB
C#

using ChatbotApi.Data.Models;
using DotNetEnv;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Identity;
using Microsoft.OpenApi.Models;
using Microsoft.EntityFrameworkCore; // Necesario para UseSqlServer
// Cargar variables de entorno
Env.Load();
var builder = WebApplication.CreateBuilder(args);
// [SEGURIDAD] Configuración de Kestrel para ocultar el header "Server" (Information Disclosure)
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.AddServerHeader = false;
});
// [SEGURIDAD] CORS Restrictivo
var myAllowSpecificOrigins = "_myAllowSpecificOrigins";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: myAllowSpecificOrigins,
policy =>
{
policy.WithOrigins(
"http://192.168.5.129:8081",
"http://192.168.5.129:8082",
"http://localhost:5173",
"http://localhost:5174",
"http://localhost:5175")
.AllowAnyHeader()
// [SEGURIDAD] Solo permitimos los verbos necesarios. Bloqueamos TRACE, HEAD, etc.
.WithMethods("GET", "POST", "PUT", "DELETE", "OPTIONS");
});
});
// 1. DbContext
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppContexto>(options =>
options.UseSqlServer(connectionString));
// 2. Identity
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
// [SEGURIDAD] Políticas de contraseñas robustas
options.Password.RequireDigit = true;
options.Password.RequiredLength = 12; // Aumentado de 8 a 12
options.Password.RequireNonAlphanumeric = true; // Requerimos símbolos
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
// [SEGURIDAD] Bloqueo de cuenta tras intentos fallidos (Mitigación Fuerza Bruta)
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
})
.AddEntityFrameworkStores<AppContexto>()
.AddDefaultTokenProviders();
builder.Services.AddMemoryCache();
// JWT Config
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("La clave JWT no está configurada.")
)),
ClockSkew = TimeSpan.Zero // Token expira exactamente cuando dice
};
});
// [SEGURIDAD] RATE LIMITING AVANZADO
builder.Services.AddRateLimiter(options =>
{
// Política General: 30 peticiones por minuto (Suficiente para uso normal del chat)
options.AddFixedWindowLimiter(policyName: "fixed", limiterOptions =>
{
limiterOptions.PermitLimit = 30;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 2;
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
// [SEGURIDAD] Política Estricta para Login: 5 intentos por minuto (Anti Fuerza Bruta)
options.AddFixedWindowLimiter(policyName: "login-limit", limiterOptions =>
{
limiterOptions.PermitLimit = 5;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 0; // No encolar, rechazar directo
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Chatbot API", Version = "v1" });
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
},
new string[] {}
}
});
});
var app = builder.Build();
// [SEGURIDAD] Headers de Seguridad HTTP
// Se recomienda usar la librería 'NetEscapades.AspNetCore.SecurityHeaders'
// Si no la tienes, instálala: dotnet add package NetEscapades.AspNetCore.SecurityHeaders
app.UseSecurityHeaders(policy =>
{
policy.AddDefaultSecurityHeaders();
policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365); // HSTS 1 año
policy.AddContentSecurityPolicy(builder =>
{
builder.AddDefaultSrc().Self();
// Permitimos scripts inline solo si es estrictamente necesario para Swagger, idealmente usar nonces
builder.AddScriptSrc().Self().UnsafeInline();
builder.AddStyleSrc().Self().UnsafeInline();
builder.AddImgSrc().Self().Data();
builder.AddConnectSrc().Self()
.From("http://localhost:5173")
.From("http://localhost:5174")
.From("http://localhost:5175"); // Permitir conexiones explícitas
builder.AddFrameAncestors().None(); // Previene Clickjacking
});
policy.RemoveServerHeader(); // Capa extra para ocultar Kestrel
});
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
// [SEGURIDAD] Forzar HTTPS en producción
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCors(myAllowSpecificOrigins);
// [SEGURIDAD] Aplicar Rate Limiting
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// Mapeamos los controladores. Las políticas de Rate Limiting se aplicarán vía Atributos en cada Controller.
app.MapControllers();
app.Run();