From 2cf2b770903a8c15824e878fdb9b4e9244c0df0d Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Mon, 24 Jul 2023 00:23:29 +0200 Subject: [PATCH] Added new ddos protection --- Moonlight/App/Configuration/ConfigV1.cs | 4 + Moonlight/App/Database/DataContext.cs | 2 + .../App/Database/Entities/BlocklistIp.cs | 10 + .../App/Database/Entities/WhitelistIp.cs | 7 + .../20230721201950_AddIpRules.Designer.cs | 1107 +++++++++++++++++ .../Migrations/20230721201950_AddIpRules.cs | 59 + .../Migrations/DataContextModelSnapshot.cs | 39 + .../Controllers/Api/Remote/DdosController.cs | 41 +- .../App/Http/Requests/Daemon/DdosStart.cs | 7 + .../App/Http/Requests/Daemon/DdosStatus.cs | 8 - .../App/Http/Requests/Daemon/DdosStop.cs | 7 + Moonlight/App/Perms/Permissions.cs | 14 +- .../Background/DdosProtectionService.cs | 129 ++ .../Background/DiscordNotificationService.cs | 13 + Moonlight/App/Services/NodeService.cs | 5 + Moonlight/Program.cs | 2 + .../Navigations/AdminSecurityNavigation.razor | 5 + Moonlight/Shared/Views/Admin/Nodes/Ddos.razor | 104 -- .../Shared/Views/Admin/Security/Ddos.razor | 148 +++ 19 files changed, 1575 insertions(+), 136 deletions(-) create mode 100644 Moonlight/App/Database/Entities/BlocklistIp.cs create mode 100644 Moonlight/App/Database/Entities/WhitelistIp.cs create mode 100644 Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs create mode 100644 Moonlight/App/Database/Migrations/20230721201950_AddIpRules.cs create mode 100644 Moonlight/App/Http/Requests/Daemon/DdosStart.cs delete mode 100644 Moonlight/App/Http/Requests/Daemon/DdosStatus.cs create mode 100644 Moonlight/App/Http/Requests/Daemon/DdosStop.cs create mode 100644 Moonlight/App/Services/Background/DdosProtectionService.cs delete mode 100644 Moonlight/Shared/Views/Admin/Nodes/Ddos.razor create mode 100644 Moonlight/Shared/Views/Admin/Security/Ddos.razor diff --git a/Moonlight/App/Configuration/ConfigV1.cs b/Moonlight/App/Configuration/ConfigV1.cs index 2bf6bed..147301d 100644 --- a/Moonlight/App/Configuration/ConfigV1.cs +++ b/Moonlight/App/Configuration/ConfigV1.cs @@ -276,6 +276,10 @@ public class ConfigV1 [Blur] 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(); } diff --git a/Moonlight/App/Database/DataContext.cs b/Moonlight/App/Database/DataContext.cs index b2caec3..58d58f6 100644 --- a/Moonlight/App/Database/DataContext.cs +++ b/Moonlight/App/Database/DataContext.cs @@ -42,6 +42,8 @@ public class DataContext : DbContext public DbSet IpBans { get; set; } public DbSet PermissionGroups { get; set; } public DbSet SecurityLogs { get; set; } + public DbSet BlocklistIps { get; set; } + public DbSet WhitelistIps { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/Moonlight/App/Database/Entities/BlocklistIp.cs b/Moonlight/App/Database/Entities/BlocklistIp.cs new file mode 100644 index 0000000..5e80daf --- /dev/null +++ b/Moonlight/App/Database/Entities/BlocklistIp.cs @@ -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; } +} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/WhitelistIp.cs b/Moonlight/App/Database/Entities/WhitelistIp.cs new file mode 100644 index 0000000..571263a --- /dev/null +++ b/Moonlight/App/Database/Entities/WhitelistIp.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Database.Entities; + +public class WhitelistIp +{ + public int Id { get; set; } + public string Ip { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs new file mode 100644 index 0000000..2633a17 --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs @@ -0,0 +1,1107 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230721201950_AddIpRules")] + partial class AddIpRules + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.BlocklistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Packets") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("BlocklistIps"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("PermissionGroups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Text") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SecurityLogs"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PermissionGroupId") + .HasColumnType("int"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RegisterIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StreamerMode") + .HasColumnType("tinyint(1)"); + + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.HasIndex("PermissionGroupId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WhitelistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("WhitelistIps"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup") + .WithMany() + .HasForeignKey("PermissionGroupId"); + + b.Navigation("CurrentSubscription"); + + b.Navigation("PermissionGroup"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.cs b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.cs new file mode 100644 index 0000000..fd59d4d --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddIpRules : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BlocklistIps", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Ip = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ExpiresAt = table.Column(type: "datetime(6)", nullable: false), + Packets = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(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(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Ip = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_WhitelistIps", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BlocklistIps"); + + migrationBuilder.DropTable( + name: "WhitelistIps"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs index 5d934c5..fc8fa18 100644 --- a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs +++ b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs @@ -19,6 +19,30 @@ namespace Moonlight.App.Database.Migrations .HasAnnotation("ProductVersion", "7.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder.Entity("Moonlight.App.Database.Entities.BlocklistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Packets") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("BlocklistIps"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => { b.Property("Id") @@ -842,6 +866,21 @@ namespace Moonlight.App.Database.Migrations b.ToTable("WebSpaces"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.WhitelistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("WhitelistIps"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => { b.HasOne("Moonlight.App.Database.Entities.Node", "Node") diff --git a/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs b/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs index 9ada8cd..55987b9 100644 --- a/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs +++ b/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs @@ -3,6 +3,7 @@ using Moonlight.App.Database.Entities; using Moonlight.App.Events; using Moonlight.App.Http.Requests.Daemon; using Moonlight.App.Repositories; +using Moonlight.App.Services.Background; namespace Moonlight.App.Http.Controllers.Api.Remote; @@ -10,19 +11,17 @@ namespace Moonlight.App.Http.Controllers.Api.Remote; [Route("api/remote/ddos")] public class DdosController : Controller { - private readonly NodeRepository NodeRepository; - private readonly EventSystem Event; - private readonly DdosAttackRepository DdosAttackRepository; + private readonly Repository NodeRepository; + private readonly DdosProtectionService DdosProtectionService; - public DdosController(NodeRepository nodeRepository, EventSystem eventSystem, DdosAttackRepository ddosAttackRepository) + public DdosController(Repository nodeRepository, DdosProtectionService ddosProtectionService) { NodeRepository = nodeRepository; - Event = eventSystem; - DdosAttackRepository = ddosAttackRepository; + DdosProtectionService = ddosProtectionService; } - [HttpPost("update")] - public async Task Update([FromBody] DdosStatus ddosStatus) + [HttpPost("start")] + public async Task Start([FromBody] DdosStart ddosStart) { var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", ""); var id = tokenData.Split(".")[0]; @@ -35,18 +34,26 @@ public class DdosController : Controller if (token != node.Token) return Unauthorized(); + + await DdosProtectionService.ProcessDdosSignal(ddosStart.Ip, ddosStart.Packets); + + return Ok(); + } - var ddosAttack = new DdosAttack() - { - Ongoing = ddosStatus.Ongoing, - Data = ddosStatus.Data, - Ip = ddosStatus.Ip, - Node = node - }; + [HttpPost("stop")] + public async Task Stop([FromBody] DdosStop ddosStop) + { + var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", ""); + var id = tokenData.Split(".")[0]; + var token = tokenData.Split(".")[1]; - 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(); } diff --git a/Moonlight/App/Http/Requests/Daemon/DdosStart.cs b/Moonlight/App/Http/Requests/Daemon/DdosStart.cs new file mode 100644 index 0000000..aab3c54 --- /dev/null +++ b/Moonlight/App/Http/Requests/Daemon/DdosStart.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Http.Requests.Daemon; + +public class DdosStart +{ + public string Ip { get; set; } = ""; + public long Packets { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Http/Requests/Daemon/DdosStatus.cs b/Moonlight/App/Http/Requests/Daemon/DdosStatus.cs deleted file mode 100644 index b08a576..0000000 --- a/Moonlight/App/Http/Requests/Daemon/DdosStatus.cs +++ /dev/null @@ -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; } = ""; -} \ No newline at end of file diff --git a/Moonlight/App/Http/Requests/Daemon/DdosStop.cs b/Moonlight/App/Http/Requests/Daemon/DdosStop.cs new file mode 100644 index 0000000..f131f20 --- /dev/null +++ b/Moonlight/App/Http/Requests/Daemon/DdosStop.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Http.Requests.Daemon; + +public class DdosStop +{ + public string Ip { get; set; } = ""; + public long Traffic { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Perms/Permissions.cs b/Moonlight/App/Perms/Permissions.cs index 9a3fbd7..29fcf6a 100644 --- a/Moonlight/App/Perms/Permissions.cs +++ b/Moonlight/App/Perms/Permissions.cs @@ -44,13 +44,6 @@ public static class Permissions 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() { Index = 9, @@ -400,6 +393,13 @@ public static class Permissions Name = "Admin 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) { diff --git a/Moonlight/App/Services/Background/DdosProtectionService.cs b/Moonlight/App/Services/Background/DdosProtectionService.cs new file mode 100644 index 0000000..e11d500 --- /dev/null +++ b/Moonlight/App/Services/Background/DdosProtectionService.cs @@ -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>(); + + 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>(); + var nodeRepo = scope.ServiceProvider.GetRequiredService>(); + var nodeService = scope.ServiceProvider.GetRequiredService(); + + 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>(); + var whitelistRepo = scope.ServiceProvider.GetRequiredService>(); + + 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>(); + var configService = scope.ServiceProvider.GetRequiredService(); + var eventSystem = scope.ServiceProvider.GetRequiredService(); + + 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>(); + + var blocklist = blocklistRepo.Get().First(x => x.Ip == ip); + blocklistRepo.Delete(blocklist); + + await RebuildNodeFirewalls(); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Background/DiscordNotificationService.cs b/Moonlight/App/Services/Background/DiscordNotificationService.cs index 014b9c6..8bc0098 100644 --- a/Moonlight/App/Services/Background/DiscordNotificationService.cs +++ b/Moonlight/App/Services/Background/DiscordNotificationService.cs @@ -36,6 +36,7 @@ public class DiscordNotificationService Event.On("supportChat.close", this, OnSupportChatClose); Event.On("user.rating", this, OnUserRated); Event.On("billing.completed", this, OnBillingCompleted); + Event.On("ddos.add", this, OnIpBlockListed); } 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) { await SendNotification("", builder => diff --git a/Moonlight/App/Services/NodeService.cs b/Moonlight/App/Services/NodeService.cs index 57a5636..704c22f 100644 --- a/Moonlight/App/Services/NodeService.cs +++ b/Moonlight/App/Services/NodeService.cs @@ -50,6 +50,11 @@ public class NodeService return await DaemonApiHelper.Get(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) { await DaemonApiHelper.Post(node, "mount", new Mount() diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index bd2c59e..0cc877b 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -239,6 +239,7 @@ namespace Moonlight builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Other builder.Services.AddSingleton(); @@ -289,6 +290,7 @@ namespace Moonlight _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); + _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); diff --git a/Moonlight/Shared/Components/Navigations/AdminSecurityNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminSecurityNavigation.razor index a05b47a..483a1ab 100644 --- a/Moonlight/Shared/Components/Navigations/AdminSecurityNavigation.razor +++ b/Moonlight/Shared/Components/Navigations/AdminSecurityNavigation.razor @@ -26,6 +26,11 @@ Logs + diff --git a/Moonlight/Shared/Views/Admin/Nodes/Ddos.razor b/Moonlight/Shared/Views/Admin/Nodes/Ddos.razor deleted file mode 100644 index fe90fb5..0000000 --- a/Moonlight/Shared/Views/Admin/Nodes/Ddos.razor +++ /dev/null @@ -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))] - - - - -
-
-
- - - - - - - - - - - - - - - - -
-
-
-
-
- -@code -{ - private DdosAttack[] DdosAttacks; - private LazyLoader LazyLoader; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await Event.On("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 -} \ No newline at end of file diff --git a/Moonlight/Shared/Views/Admin/Security/Ddos.razor b/Moonlight/Shared/Views/Admin/Security/Ddos.razor new file mode 100644 index 0000000..b3bf520 --- /dev/null +++ b/Moonlight/Shared/Views/Admin/Security/Ddos.razor @@ -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 BlocklistIpRepository +@inject Repository WhitelistIpRepository +@inject SmartTranslateService SmartTranslateService +@inject DdosProtectionService DdosProtectionService + +@attribute [PermissionRequired(nameof(Permissions.AdminSecurityDdos))] + + + +
+
+ + @(BlocklistIps.Length) blocked IPs + + + @(WhitelistIps.Length) whitelisted IPs + +
+
+ +
+
+ + + +
+
+ +
+ +
+ + + + + + + + + + + + +
+
+
+
+ +
+ +
+ + + + + + +
+
+
+
+ +@code +{ + private BlocklistIp[] BlocklistIps = Array.Empty(); + private WhitelistIp[] WhitelistIps = Array.Empty(); + + 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(); + } +} \ No newline at end of file