Merge pull request #238 from Moonlight-Panel/AddDdosProtection
Added new ddos protection
This commit is contained in:
@@ -276,6 +276,10 @@ public class ConfigV1
|
|||||||
[Blur]
|
[Blur]
|
||||||
public string Token { get; set; } = Guid.NewGuid().ToString();
|
public string Token { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
[JsonProperty("BlockIpDuration")]
|
||||||
|
[Description("The duration in minutes a ip will be blocked by the anti ddos system")]
|
||||||
|
public int BlockIpDuration { get; set; } = 15;
|
||||||
|
|
||||||
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
|
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ public class DataContext : DbContext
|
|||||||
public DbSet<IpBan> IpBans { get; set; }
|
public DbSet<IpBan> IpBans { get; set; }
|
||||||
public DbSet<PermissionGroup> PermissionGroups { get; set; }
|
public DbSet<PermissionGroup> PermissionGroups { get; set; }
|
||||||
public DbSet<SecurityLog> SecurityLogs { get; set; }
|
public DbSet<SecurityLog> SecurityLogs { get; set; }
|
||||||
|
public DbSet<BlocklistIp> BlocklistIps { get; set; }
|
||||||
|
public DbSet<WhitelistIp> WhitelistIps { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
10
Moonlight/App/Database/Entities/BlocklistIp.cs
Normal file
10
Moonlight/App/Database/Entities/BlocklistIp.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
public class BlocklistIp
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Ip { get; set; } = "";
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
public long Packets { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
7
Moonlight/App/Database/Entities/WhitelistIp.cs
Normal file
7
Moonlight/App/Database/Entities/WhitelistIp.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
public class WhitelistIp
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Ip { get; set; } = "";
|
||||||
|
}
|
||||||
1107
Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs
generated
Normal file
1107
Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddIpRules : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "BlocklistIps",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
ExpiresAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Packets = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_BlocklistIps", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "WhitelistIps",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_WhitelistIps", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "BlocklistIps");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "WhitelistIps");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,30 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.HasAnnotation("ProductVersion", "7.0.3")
|
.HasAnnotation("ProductVersion", "7.0.3")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.App.Database.Entities.BlocklistIp", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Ip")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<long>("Packets")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("BlocklistIps");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -842,6 +866,21 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("WebSpaces");
|
b.ToTable("WebSpaces");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.App.Database.Entities.WhitelistIp", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Ip")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("WhitelistIps");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Moonlight.App.Database.Entities.Node", "Node")
|
b.HasOne("Moonlight.App.Database.Entities.Node", "Node")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Moonlight.App.Database.Entities;
|
|||||||
using Moonlight.App.Events;
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Http.Requests.Daemon;
|
using Moonlight.App.Http.Requests.Daemon;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
|
using Moonlight.App.Services.Background;
|
||||||
|
|
||||||
namespace Moonlight.App.Http.Controllers.Api.Remote;
|
namespace Moonlight.App.Http.Controllers.Api.Remote;
|
||||||
|
|
||||||
@@ -10,19 +11,17 @@ namespace Moonlight.App.Http.Controllers.Api.Remote;
|
|||||||
[Route("api/remote/ddos")]
|
[Route("api/remote/ddos")]
|
||||||
public class DdosController : Controller
|
public class DdosController : Controller
|
||||||
{
|
{
|
||||||
private readonly NodeRepository NodeRepository;
|
private readonly Repository<Node> NodeRepository;
|
||||||
private readonly EventSystem Event;
|
private readonly DdosProtectionService DdosProtectionService;
|
||||||
private readonly DdosAttackRepository DdosAttackRepository;
|
|
||||||
|
|
||||||
public DdosController(NodeRepository nodeRepository, EventSystem eventSystem, DdosAttackRepository ddosAttackRepository)
|
public DdosController(Repository<Node> nodeRepository, DdosProtectionService ddosProtectionService)
|
||||||
{
|
{
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
Event = eventSystem;
|
DdosProtectionService = ddosProtectionService;
|
||||||
DdosAttackRepository = ddosAttackRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("update")]
|
[HttpPost("start")]
|
||||||
public async Task<ActionResult> Update([FromBody] DdosStatus ddosStatus)
|
public async Task<ActionResult> Start([FromBody] DdosStart ddosStart)
|
||||||
{
|
{
|
||||||
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||||
var id = tokenData.Split(".")[0];
|
var id = tokenData.Split(".")[0];
|
||||||
@@ -36,17 +35,25 @@ public class DdosController : Controller
|
|||||||
if (token != node.Token)
|
if (token != node.Token)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
var ddosAttack = new DdosAttack()
|
await DdosProtectionService.ProcessDdosSignal(ddosStart.Ip, ddosStart.Packets);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("stop")]
|
||||||
|
public async Task<ActionResult> Stop([FromBody] DdosStop ddosStop)
|
||||||
{
|
{
|
||||||
Ongoing = ddosStatus.Ongoing,
|
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||||
Data = ddosStatus.Data,
|
var id = tokenData.Split(".")[0];
|
||||||
Ip = ddosStatus.Ip,
|
var token = tokenData.Split(".")[1];
|
||||||
Node = node
|
|
||||||
};
|
|
||||||
|
|
||||||
ddosAttack = DdosAttackRepository.Add(ddosAttack);
|
var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id);
|
||||||
|
|
||||||
await Event.Emit("node.ddos", ddosAttack);
|
if (node == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
if (token != node.Token)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
7
Moonlight/App/Http/Requests/Daemon/DdosStart.cs
Normal file
7
Moonlight/App/Http/Requests/Daemon/DdosStart.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.App.Http.Requests.Daemon;
|
||||||
|
|
||||||
|
public class DdosStart
|
||||||
|
{
|
||||||
|
public string Ip { get; set; } = "";
|
||||||
|
public long Packets { get; set; }
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Moonlight.App.Http.Requests.Daemon;
|
|
||||||
|
|
||||||
public class DdosStatus
|
|
||||||
{
|
|
||||||
public bool Ongoing { get; set; }
|
|
||||||
public long Data { get; set; }
|
|
||||||
public string Ip { get; set; } = "";
|
|
||||||
}
|
|
||||||
7
Moonlight/App/Http/Requests/Daemon/DdosStop.cs
Normal file
7
Moonlight/App/Http/Requests/Daemon/DdosStop.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.App.Http.Requests.Daemon;
|
||||||
|
|
||||||
|
public class DdosStop
|
||||||
|
{
|
||||||
|
public string Ip { get; set; } = "";
|
||||||
|
public long Traffic { get; set; }
|
||||||
|
}
|
||||||
@@ -51,13 +51,6 @@ public static class Permissions
|
|||||||
Description = "Create a new shared domain in the admin area"
|
Description = "Create a new shared domain in the admin area"
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Permission AdminNodeDdos = new()
|
|
||||||
{
|
|
||||||
Index = 8,
|
|
||||||
Name = "Admin Node DDoS",
|
|
||||||
Description = "Manage DDoS protection for nodes in the admin area"
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Permission AdminNodeEdit = new()
|
public static Permission AdminNodeEdit = new()
|
||||||
{
|
{
|
||||||
Index = 9,
|
Index = 9,
|
||||||
@@ -408,6 +401,13 @@ public static class Permissions
|
|||||||
Description = "View the security logs"
|
Description = "View the security logs"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static Permission AdminSecurityDdos = new()
|
||||||
|
{
|
||||||
|
Index = 59,
|
||||||
|
Name = "Admin security ddos",
|
||||||
|
Description = "Manage the integrated ddos protection"
|
||||||
|
};
|
||||||
|
|
||||||
public static Permission? FromString(string name)
|
public static Permission? FromString(string name)
|
||||||
{
|
{
|
||||||
var type = typeof(Permissions);
|
var type = typeof(Permissions);
|
||||||
|
|||||||
129
Moonlight/App/Services/Background/DdosProtectionService.cs
Normal file
129
Moonlight/App/Services/Background/DdosProtectionService.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Moonlight.App.ApiClients.Daemon;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Background;
|
||||||
|
|
||||||
|
public class DdosProtectionService
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
|
||||||
|
public DdosProtectionService(IServiceScopeFactory serviceScopeFactory)
|
||||||
|
{
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
|
||||||
|
Task.Run(UnBlocker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UnBlocker()
|
||||||
|
{
|
||||||
|
var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var blocklistIpRepo = scope.ServiceProvider.GetRequiredService<Repository<BlocklistIp>>();
|
||||||
|
|
||||||
|
var ips = blocklistIpRepo
|
||||||
|
.Get()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var ip in ips)
|
||||||
|
{
|
||||||
|
if (DateTime.UtcNow > ip.ExpiresAt)
|
||||||
|
{
|
||||||
|
blocklistIpRepo.Delete(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newCount = blocklistIpRepo
|
||||||
|
.Get()
|
||||||
|
.Count();
|
||||||
|
|
||||||
|
if (newCount != ips.Length)
|
||||||
|
{
|
||||||
|
await RebuildNodeFirewalls();
|
||||||
|
}
|
||||||
|
|
||||||
|
await periodicTimer.WaitForNextTickAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RebuildNodeFirewalls()
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var blocklistIpRepo = scope.ServiceProvider.GetRequiredService<Repository<BlocklistIp>>();
|
||||||
|
var nodeRepo = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
|
||||||
|
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
||||||
|
|
||||||
|
var ips = blocklistIpRepo
|
||||||
|
.Get()
|
||||||
|
.Select(x => x.Ip)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var node in nodeRepo.Get().ToArray())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await nodeService.RebuildFirewall(node, ips);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn($"Error rebuilding firewall on node {node.Name}");
|
||||||
|
Logger.Warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessDdosSignal(string ip, long packets)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var blocklistRepo = scope.ServiceProvider.GetRequiredService<Repository<BlocklistIp>>();
|
||||||
|
var whitelistRepo = scope.ServiceProvider.GetRequiredService<Repository<WhitelistIp>>();
|
||||||
|
|
||||||
|
var whitelistIps = whitelistRepo.Get().ToArray();
|
||||||
|
|
||||||
|
if(whitelistIps.Any(x => x.Ip == ip))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var blocklistIps = blocklistRepo.Get().ToArray();
|
||||||
|
|
||||||
|
if(blocklistIps.Any(x => x.Ip == ip))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await BlocklistIp(ip, packets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task BlocklistIp(string ip, long packets)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var blocklistRepo = scope.ServiceProvider.GetRequiredService<Repository<BlocklistIp>>();
|
||||||
|
var configService = scope.ServiceProvider.GetRequiredService<ConfigService>();
|
||||||
|
var eventSystem = scope.ServiceProvider.GetRequiredService<EventSystem>();
|
||||||
|
|
||||||
|
var blocklistIp = blocklistRepo.Add(new()
|
||||||
|
{
|
||||||
|
Ip = ip,
|
||||||
|
Packets = packets,
|
||||||
|
ExpiresAt = DateTime.UtcNow.AddMinutes(configService.Get().Moonlight.Security.BlockIpDuration),
|
||||||
|
CreatedAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
|
||||||
|
await RebuildNodeFirewalls();
|
||||||
|
await eventSystem.Emit("ddos.add", blocklistIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UnBlocklistIp(string ip)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var blocklistRepo = scope.ServiceProvider.GetRequiredService<Repository<BlocklistIp>>();
|
||||||
|
|
||||||
|
var blocklist = blocklistRepo.Get().First(x => x.Ip == ip);
|
||||||
|
blocklistRepo.Delete(blocklist);
|
||||||
|
|
||||||
|
await RebuildNodeFirewalls();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ public class DiscordNotificationService
|
|||||||
Event.On<User>("supportChat.close", this, OnSupportChatClose);
|
Event.On<User>("supportChat.close", this, OnSupportChatClose);
|
||||||
Event.On<User>("user.rating", this, OnUserRated);
|
Event.On<User>("user.rating", this, OnUserRated);
|
||||||
Event.On<User>("billing.completed", this, OnBillingCompleted);
|
Event.On<User>("billing.completed", this, OnBillingCompleted);
|
||||||
|
Event.On<BlocklistIp>("ddos.add", this, OnIpBlockListed);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -43,6 +44,18 @@ public class DiscordNotificationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnIpBlockListed(BlocklistIp blocklistIp)
|
||||||
|
{
|
||||||
|
await SendNotification("", builder =>
|
||||||
|
{
|
||||||
|
builder.Color = Color.Red;
|
||||||
|
builder.Title = "New ddos attack detected";
|
||||||
|
|
||||||
|
builder.AddField("IP", blocklistIp.Ip);
|
||||||
|
builder.AddField("Packets", blocklistIp.Packets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnBillingCompleted(User user)
|
private async Task OnBillingCompleted(User user)
|
||||||
{
|
{
|
||||||
await SendNotification("", builder =>
|
await SendNotification("", builder =>
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ public class NodeService
|
|||||||
return await DaemonApiHelper.Get<DockerMetrics>(node, "metrics/docker");
|
return await DaemonApiHelper.Get<DockerMetrics>(node, "metrics/docker");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RebuildFirewall(Node node, string[] ips)
|
||||||
|
{
|
||||||
|
await DaemonApiHelper.Post(node, "firewall/rebuild", ips);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Mount(Node node, string server, string serverPath, string path)
|
public async Task Mount(Node node, string server, string serverPath, string path)
|
||||||
{
|
{
|
||||||
await DaemonApiHelper.Post(node, "mount", new Mount()
|
await DaemonApiHelper.Post(node, "mount", new Mount()
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<MalwareScanService>();
|
builder.Services.AddSingleton<MalwareScanService>();
|
||||||
builder.Services.AddSingleton<TelemetryService>();
|
builder.Services.AddSingleton<TelemetryService>();
|
||||||
builder.Services.AddSingleton<TempMailService>();
|
builder.Services.AddSingleton<TempMailService>();
|
||||||
|
builder.Services.AddSingleton<DdosProtectionService>();
|
||||||
builder.Services.AddSingleton(pluginService);
|
builder.Services.AddSingleton(pluginService);
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
@@ -296,7 +296,7 @@ namespace Moonlight
|
|||||||
_ = app.Services.GetRequiredService<MalwareScanService>();
|
_ = app.Services.GetRequiredService<MalwareScanService>();
|
||||||
_ = app.Services.GetRequiredService<TelemetryService>();
|
_ = app.Services.GetRequiredService<TelemetryService>();
|
||||||
_ = app.Services.GetRequiredService<TempMailService>();
|
_ = app.Services.GetRequiredService<TempMailService>();
|
||||||
|
_ = app.Services.GetRequiredService<DdosProtectionService>();
|
||||||
_ = app.Services.GetRequiredService<MoonlightService>();
|
_ = app.Services.GetRequiredService<MoonlightService>();
|
||||||
|
|
||||||
// Discord bot service
|
// Discord bot service
|
||||||
|
|||||||
@@ -26,6 +26,11 @@
|
|||||||
<TL>Logs</TL>
|
<TL>Logs</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 5 ? "active" : "")" href="/admin/security/ddos">
|
||||||
|
<TL>Ddos protection</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
@page "/admin/nodes/ddos"
|
|
||||||
|
|
||||||
@using Moonlight.Shared.Components.Navigations
|
|
||||||
@using Moonlight.App.Repositories
|
|
||||||
@using BlazorTable
|
|
||||||
@using Microsoft.EntityFrameworkCore
|
|
||||||
@using Moonlight.App.Database.Entities
|
|
||||||
@using Moonlight.App.Events
|
|
||||||
@using Moonlight.App.Helpers
|
|
||||||
@using Moonlight.App.Services
|
|
||||||
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
@inject DdosAttackRepository DdosAttackRepository
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
|
||||||
@inject EventSystem Event
|
|
||||||
|
|
||||||
@attribute [PermissionRequired(nameof(Permissions.AdminNodeDdos))]
|
|
||||||
|
|
||||||
<AdminNodesNavigation Index="1"/>
|
|
||||||
|
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body pt-0">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<Table TableItem="DdosAttack" Items="DdosAttacks" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
|
||||||
<Template>
|
|
||||||
@if (context.Ongoing)
|
|
||||||
{
|
|
||||||
<TL>DDos attack started</TL>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<TL>DDos attack stopped</TL>
|
|
||||||
}
|
|
||||||
</Template>
|
|
||||||
</Column>
|
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Node"))" Field="@(x => x.Node)" Sortable="false" Filterable="false">
|
|
||||||
<Template>
|
|
||||||
<a href="/admin/nodes/view/@(context.Id)">
|
|
||||||
@(context.Node.Name)
|
|
||||||
</a>
|
|
||||||
</Template>
|
|
||||||
</Column>
|
|
||||||
<Column TableItem="DdosAttack" Title="Ip" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/>
|
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
|
||||||
<Template>
|
|
||||||
@if (context.Ongoing)
|
|
||||||
{
|
|
||||||
@(context.Data)
|
|
||||||
<TL> packets</TL>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@(context.Data)
|
|
||||||
<span> MB</span>
|
|
||||||
}
|
|
||||||
</Template>
|
|
||||||
</Column>
|
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Date"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
|
||||||
<Template>
|
|
||||||
@(Formatter.FormatDate(context.CreatedAt))
|
|
||||||
</Template>
|
|
||||||
</Column>
|
|
||||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</LazyLoader>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
private DdosAttack[] DdosAttacks;
|
|
||||||
private LazyLoader LazyLoader;
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
await Event.On<DdosAttack>("node.ddos", this, async attack => await LazyLoader.Reload());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Load(LazyLoader arg)
|
|
||||||
{
|
|
||||||
DdosAttacks = DdosAttackRepository
|
|
||||||
.Get()
|
|
||||||
.Include(x => x.Node)
|
|
||||||
.OrderByDescending(x => x.CreatedAt)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Dispose()
|
|
||||||
{
|
|
||||||
await Event.Off("node.ddos", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Move to security
|
|
||||||
}
|
|
||||||
148
Moonlight/Shared/Views/Admin/Security/Ddos.razor
Normal file
148
Moonlight/Shared/Views/Admin/Security/Ddos.razor
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
@page "/admin/security/ddos"
|
||||||
|
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using BlazorTable
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Services.Background
|
||||||
|
|
||||||
|
@inject Repository<BlocklistIp> BlocklistIpRepository
|
||||||
|
@inject Repository<WhitelistIp> WhitelistIpRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject DdosProtectionService DdosProtectionService
|
||||||
|
|
||||||
|
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityDdos))]
|
||||||
|
|
||||||
|
<AdminSecurityNavigation Index="5"/>
|
||||||
|
|
||||||
|
<div class="card card-body mb-5">
|
||||||
|
<div class="d-flex justify-content-center fs-4">
|
||||||
|
<span class="me-3">
|
||||||
|
@(BlocklistIps.Length) <TL>blocked IPs</TL>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
@(WhitelistIps.Length) <TL>whitelisted IPs</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body mb-5">
|
||||||
|
<div class="input-group input-group-lg">
|
||||||
|
<input @bind="Ip" type="text" class="form-control">
|
||||||
|
<WButton CssClasses="btn-secondary" OnClick="BlockIp" Text="@(SmartTranslateService.Translate("Block"))"/>
|
||||||
|
<WButton CssClasses="btn-secondary" OnClick="WhitelistIp" Text="@(SmartTranslateService.Translate("Whitelist"))"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body mb-5">
|
||||||
|
<LazyLoader @ref="BlocklistLazyLoader" Load="LoadBlocklist">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="BlocklistIp" Items="BlocklistIps" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="BlocklistIp" Width="30%" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
|
||||||
|
<Column TableItem="BlocklistIp" Width="30%" Title="@(SmartTranslateService.Translate("Packets"))" Field="@(x => x.Packets)" Filterable="true" Sortable="true">
|
||||||
|
<Template>
|
||||||
|
@(context.Packets) <TL>packets</TL>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="BlocklistIp" Width="30%" Title="" Field="@(x => x.ExpiresAt)" Filterable="true" Sortable="true">
|
||||||
|
<Template>
|
||||||
|
@Formatter.FormatUptime(context.ExpiresAt - DateTime.UtcNow) <TL>remaining</TL>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="BlocklistIp" Width="15%" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
|
||||||
|
<Template>
|
||||||
|
<div class="text-end">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Details"))" />
|
||||||
|
<DeleteButton Confirm="true" OnClick="() => RevokeBlocklistIp(context)" />
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body">
|
||||||
|
<LazyLoader @ref="WhitelistLazyLoader" Load="LoadWhitelist">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="WhitelistIp" Items="WhitelistIps" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="WhitelistIp" Width="85%" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
|
||||||
|
<Column TableItem="WhitelistIp" Width="15%" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
|
||||||
|
<Template>
|
||||||
|
<div class="text-end">
|
||||||
|
<DeleteButton Confirm="true" OnClick="() => RevokeWhitelistIp(context)" />
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private BlocklistIp[] BlocklistIps = Array.Empty<BlocklistIp>();
|
||||||
|
private WhitelistIp[] WhitelistIps = Array.Empty<WhitelistIp>();
|
||||||
|
|
||||||
|
private LazyLoader BlocklistLazyLoader;
|
||||||
|
private LazyLoader WhitelistLazyLoader;
|
||||||
|
|
||||||
|
private string Ip = "";
|
||||||
|
|
||||||
|
private async Task LoadBlocklist(LazyLoader _)
|
||||||
|
{
|
||||||
|
BlocklistIps = BlocklistIpRepository
|
||||||
|
.Get()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadWhitelist(LazyLoader _)
|
||||||
|
{
|
||||||
|
WhitelistIps = WhitelistIpRepository
|
||||||
|
.Get()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task BlockIp()
|
||||||
|
{
|
||||||
|
await DdosProtectionService.BlocklistIp(Ip, -1);
|
||||||
|
|
||||||
|
Ip = "";
|
||||||
|
|
||||||
|
await BlocklistLazyLoader.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RevokeBlocklistIp(BlocklistIp blocklistIp)
|
||||||
|
{
|
||||||
|
await DdosProtectionService.UnBlocklistIp(blocklistIp.Ip);
|
||||||
|
|
||||||
|
await BlocklistLazyLoader.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WhitelistIp()
|
||||||
|
{
|
||||||
|
WhitelistIpRepository.Add(new()
|
||||||
|
{
|
||||||
|
Ip = Ip
|
||||||
|
});
|
||||||
|
|
||||||
|
Ip = "";
|
||||||
|
|
||||||
|
await WhitelistLazyLoader.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RevokeWhitelistIp(WhitelistIp whitelistIp)
|
||||||
|
{
|
||||||
|
WhitelistIpRepository.Delete(whitelistIp);
|
||||||
|
|
||||||
|
await WhitelistLazyLoader.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user