Security hardening: Fix critical JWT, rate limiting, and deployment issues

This commit is contained in:
SysAdmin 2025-09-28 18:52:05 +01:00
parent 586d491b83
commit 1b46222300
3 changed files with 130 additions and 15 deletions

View File

@ -33,6 +33,7 @@
<PackageReference Include="NBitcoin" Version="7.0.37" /> <PackageReference Include="NBitcoin" Version="7.0.37" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="WebPush" Version="1.0.12" /> <PackageReference Include="WebPush" Version="1.0.12" />
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -6,6 +6,7 @@ using LittleShop.Data;
using LittleShop.Services; using LittleShop.Services;
using FluentValidation; using FluentValidation;
using Serilog; using Serilog;
using AspNetCoreRateLimit;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -42,8 +43,58 @@ else
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
} }
// Rate Limiting - protect anonymous endpoints
builder.Services.AddMemoryCache();
builder.Services.Configure<AspNetCoreRateLimit.IpRateLimitOptions>(options =>
{
options.EnableEndpointRateLimiting = true;
options.StackBlockedRequests = false;
options.HttpStatusCode = 429;
options.RealIpHeader = "X-Real-IP";
options.ClientIdHeader = "X-ClientId";
options.GeneralRules = new List<AspNetCoreRateLimit.RateLimitRule>
{
new AspNetCoreRateLimit.RateLimitRule
{
Endpoint = "*/api/orders/by-identity/*",
Period = "1m",
Limit = 10
},
new AspNetCoreRateLimit.RateLimitRule
{
Endpoint = "*/api/orders/by-customer/*",
Period = "1m",
Limit = 10
},
new AspNetCoreRateLimit.RateLimitRule
{
Endpoint = "*",
Period = "1s",
Limit = 10
},
new AspNetCoreRateLimit.RateLimitRule
{
Endpoint = "*",
Period = "1m",
Limit = 100
}
};
});
builder.Services.AddSingleton<AspNetCoreRateLimit.IIpPolicyStore, AspNetCoreRateLimit.MemoryCacheIpPolicyStore>();
builder.Services.AddSingleton<AspNetCoreRateLimit.IRateLimitCounterStore, AspNetCoreRateLimit.MemoryCacheRateLimitCounterStore>();
builder.Services.AddSingleton<AspNetCoreRateLimit.IRateLimitConfiguration, AspNetCoreRateLimit.RateLimitConfiguration>();
builder.Services.AddSingleton<AspNetCoreRateLimit.IProcessingStrategy, AspNetCoreRateLimit.AsyncKeyLockProcessingStrategy>();
// Authentication - Cookie for Admin Panel, JWT for API // Authentication - Cookie for Admin Panel, JWT for API
var jwtKey = builder.Configuration["Jwt:Key"] ?? "ThisIsASuperSecretKeyForJWTAuthenticationThatIsDefinitelyLongerThan32Characters!"; var jwtKey = builder.Configuration["Jwt:Key"];
if (string.IsNullOrEmpty(jwtKey))
{
Log.Fatal("🚨 SECURITY: Jwt:Key configuration is missing. Application cannot start securely.");
throw new InvalidOperationException(
"JWT:Key must be configured in environment variables or user secrets. " +
"Set the Jwt__Key environment variable or use: dotnet user-secrets set \"Jwt:Key\" \"<your-secure-key>\"");
}
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "LittleShop"; var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "LittleShop";
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "LittleShop"; var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "LittleShop";
@ -251,6 +302,9 @@ if (!app.Environment.IsDevelopment())
app.UseHsts(); // Use HSTS for production security app.UseHsts(); // Use HSTS for production security
} }
// Add rate limiting middleware (after CORS, before authentication)
app.UseIpRateLimiting();
app.UseStaticFiles(); // Enable serving static files app.UseStaticFiles(); // Enable serving static files
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
@ -299,8 +353,26 @@ using (var scope = app.Services.CreateScope())
{ {
var context = scope.ServiceProvider.GetRequiredService<LittleShopContext>(); var context = scope.ServiceProvider.GetRequiredService<LittleShopContext>();
// Ensure database is created (temporary while fixing migrations) // Use proper migrations in production, EnsureCreated only for development/testing
if (app.Environment.IsProduction())
{
Log.Information("Production environment: Applying database migrations...");
try
{
context.Database.Migrate();
Log.Information("Database migrations applied successfully");
}
catch (Exception ex)
{
Log.Fatal(ex, "Database migration failed. Application cannot start.");
throw;
}
}
else
{
Log.Information("Development/Testing environment: Using EnsureCreated");
context.Database.EnsureCreated(); context.Database.EnsureCreated();
}
// Seed default admin user // Seed default admin user
var authService = scope.ServiceProvider.GetRequiredService<IAuthService>(); var authService = scope.ServiceProvider.GetRequiredService<IAuthService>();
@ -310,11 +382,15 @@ using (var scope = app.Services.CreateScope())
var dataSeeder = scope.ServiceProvider.GetRequiredService<IDataSeederService>(); var dataSeeder = scope.ServiceProvider.GetRequiredService<IDataSeederService>();
await dataSeeder.SeedSampleDataAsync(); await dataSeeder.SeedSampleDataAsync();
// Seed system settings - enable test currencies for development // Seed system settings - enable test currencies only in development
if (app.Environment.IsDevelopment())
{
Log.Information("Development environment: Enabling test currencies");
var systemSettings = scope.ServiceProvider.GetRequiredService<ISystemSettingsService>(); var systemSettings = scope.ServiceProvider.GetRequiredService<ISystemSettingsService>();
await systemSettings.SetTestCurrencyEnabledAsync("TBTC", true); await systemSettings.SetTestCurrencyEnabledAsync("TBTC", true);
await systemSettings.SetTestCurrencyEnabledAsync("TLTC", true); await systemSettings.SetTestCurrencyEnabledAsync("TLTC", true);
} }
}
Log.Information("LittleShop API starting up..."); Log.Information("LittleShop API starting up...");

View File

@ -1,28 +1,44 @@
#!/bin/bash #!/bin/bash
# Force upgrade production database - WARNING: This will DELETE all data! # Force upgrade production database - Preserves configuration and wallet data
# Use this script when data is not critical and you need a clean migration # This script will recreate the database structure while keeping critical config
set -e # Exit on error set -e # Exit on error
PRODUCTION_DB_PATH="/opt/littleshop/littleshop-production.db" PRODUCTION_DB_PATH="/opt/littleshop/littleshop-production.db"
BACKUP_PATH="/opt/littleshop/backups/littleshop-production-backup-$(date +%Y%m%d_%H%M%S).db" BACKUP_PATH="/opt/littleshop/backups/littleshop-production-backup-$(date +%Y%m%d_%H%M%S).db"
CONFIG_EXPORT="/tmp/littleshop-config-export.sql"
echo "🔧 Force Upgrade Production Database" echo "🔧 Force Upgrade Production Database (Preserving Config)"
echo "=====================================" echo "========================================================"
echo "" echo ""
echo "⚠️ WARNING: This will DELETE all existing data!"
echo "📍 Database: $PRODUCTION_DB_PATH" echo "📍 Database: $PRODUCTION_DB_PATH"
echo "💾 Will preserve: SystemSettings, Users, BotRegistrations"
echo "" echo ""
# Create backups directory if it doesn't exist # Create backups directory if it doesn't exist
mkdir -p /opt/littleshop/backups mkdir -p /opt/littleshop/backups
# Backup existing database if it exists # Backup and export configuration if database exists
if [ -f "$PRODUCTION_DB_PATH" ]; then if [ -f "$PRODUCTION_DB_PATH" ]; then
echo "📦 Backing up existing database..." echo "📦 Backing up existing database..."
cp "$PRODUCTION_DB_PATH" "$BACKUP_PATH" cp "$PRODUCTION_DB_PATH" "$BACKUP_PATH"
echo "✅ Backup saved to: $BACKUP_PATH" echo "✅ Backup saved to: $BACKUP_PATH"
echo "" echo ""
echo "💾 Exporting configuration data..."
# Export critical tables to SQL
sqlite3 "$PRODUCTION_DB_PATH" <<'EXPORT_SQL' > "$CONFIG_EXPORT"
.mode insert SystemSettings
SELECT * FROM SystemSettings WHERE 1=1;
.mode insert Users
SELECT * FROM Users WHERE 1=1;
.mode insert BotRegistrations
SELECT * FROM BotRegistrations WHERE 1=1;
EXPORT_SQL
echo "✅ Configuration exported to: $CONFIG_EXPORT"
echo ""
fi fi
# Remove old database files # Remove old database files
@ -39,20 +55,42 @@ cd /opt/littleshop
dotnet ef database update --project LittleShop.csproj dotnet ef database update --project LittleShop.csproj
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "✅ Migrations applied successfully"
echo "" echo ""
# Restore configuration data if export exists
if [ -f "$CONFIG_EXPORT" ]; then
echo "♻️ Restoring configuration data..."
# Import the exported data
sqlite3 "$PRODUCTION_DB_PATH" < "$CONFIG_EXPORT"
echo "✅ Configuration data restored"
echo " - SystemSettings (wallet addresses, API keys)"
echo " - Users (admin accounts)"
echo " - BotRegistrations (Telegram bot config)"
echo ""
# Clean up export file
rm -f "$CONFIG_EXPORT"
fi
echo "✅ Database successfully upgraded!" echo "✅ Database successfully upgraded!"
echo "📋 All migrations applied successfully"
echo "" echo ""
echo "📊 Database info:" echo "📊 Database info:"
echo " Path: $PRODUCTION_DB_PATH" echo " Path: $PRODUCTION_DB_PATH"
echo " Backup: $BACKUP_PATH" echo " Backup: $BACKUP_PATH"
echo "" echo ""
echo "🎉 Production database is now ready!" echo "🎉 Production database is now ready!"
echo ""
echo "⚠️ Note: Product catalog and orders were reset"
echo " Configuration and users were preserved"
else else
echo "" echo ""
echo "❌ Migration failed!" echo "❌ Migration failed!"
echo "🔄 Restoring from backup..." echo "🔄 Restoring from backup..."
cp "$BACKUP_PATH" "$PRODUCTION_DB_PATH" cp "$BACKUP_PATH" "$PRODUCTION_DB_PATH"
echo "✅ Backup restored" echo "✅ Backup restored"
rm -f "$CONFIG_EXPORT"
exit 1 exit 1
fi fi