From 18b7c82613eef954f8d06305b22bfc18fd445307 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Tue, 29 Aug 2023 15:03:07 +0200 Subject: [PATCH] Implemented ip filtering to detect datacenter and vpn ips --- Moonlight/App/Configuration/ConfigV1.cs | 8 ++ .../App/Services/Files/StorageService.cs | 105 ++++++++++-------- .../App/Services/IpVerificationService.cs | 91 +++++++++++++++ Moonlight/Moonlight.csproj | 1 + Moonlight/Program.cs | 1 + Moonlight/Shared/Layouts/MainLayout.razor | 58 ++++++---- 6 files changed, 194 insertions(+), 70 deletions(-) create mode 100644 Moonlight/App/Services/IpVerificationService.cs diff --git a/Moonlight/App/Configuration/ConfigV1.cs b/Moonlight/App/Configuration/ConfigV1.cs index 5f9b1b32..4964bbc4 100644 --- a/Moonlight/App/Configuration/ConfigV1.cs +++ b/Moonlight/App/Configuration/ConfigV1.cs @@ -304,6 +304,14 @@ public class ConfigV1 public int BlockIpDuration { get; set; } = 15; [JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new(); + + [JsonProperty("BlockDatacenterIps")] + [Description("If this option is enabled, users with an ip from datacenters will not be able to access the panel")] + public bool BlockDatacenterIps { get; set; } = true; + + [JsonProperty("AllowCloudflareIps")] + [Description("Allow cloudflare ips to bypass the datacenter ip check")] + public bool AllowCloudflareIps { get; set; } = false; } public class ReCaptchaData diff --git a/Moonlight/App/Services/Files/StorageService.cs b/Moonlight/App/Services/Files/StorageService.cs index b1a50d9a..49fac176 100644 --- a/Moonlight/App/Services/Files/StorageService.cs +++ b/Moonlight/App/Services/Files/StorageService.cs @@ -15,14 +15,14 @@ public class StorageService Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins")); await UpdateResources(); - + return; - if(IsEmpty(PathBuilder.Dir("storage", "resources"))) + if (IsEmpty(PathBuilder.Dir("storage", "resources"))) { Logger.Info("Default resources not found. Copying default resources"); - + CopyFilesRecursively( - PathBuilder.Dir("defaultstorage", "resources"), + PathBuilder.Dir("defaultstorage", "resources"), PathBuilder.Dir("storage", "resources") ); } @@ -30,9 +30,9 @@ public class StorageService if (IsEmpty(PathBuilder.Dir("storage", "configs"))) { Logger.Info("Default configs not found. Copying default configs"); - + CopyFilesRecursively( - PathBuilder.Dir("defaultstorage", "configs"), + PathBuilder.Dir("defaultstorage", "configs"), PathBuilder.Dir("storage", "configs") ); } @@ -40,64 +40,73 @@ public class StorageService private async Task UpdateResources() { - Logger.Info("Checking resources"); - - var client = new GitHubClient( - new ProductHeaderValue("Moonlight-Panel")); - - string user = "Moonlight-Panel"; - string repo = "Resources"; - string branch = "main"; - string resourcesDir = PathBuilder.Dir("storage", "resources"); - - async Task CopyDirectory(string dirPath, string localDir) + try { - IReadOnlyList contents; - - if(string.IsNullOrEmpty(dirPath)) - contents = await client.Repository.Content.GetAllContents(user, repo); - else - contents = await client.Repository.Content.GetAllContents(user, repo, dirPath); + Logger.Info("Checking resources"); - foreach (var content in contents) + var client = new GitHubClient( + new ProductHeaderValue("Moonlight-Panel")); + + var user = "Moonlight-Panel"; + var repo = "Resources"; + var resourcesDir = PathBuilder.Dir("storage", "resources"); + + async Task CopyDirectory(string dirPath, string localDir) { - string localPath = Path.Combine(localDir, content.Name); + IReadOnlyList contents; - if (content.Type == ContentType.File) + if (string.IsNullOrEmpty(dirPath)) + contents = await client.Repository.Content.GetAllContents(user, repo); + else + contents = await client.Repository.Content.GetAllContents(user, repo, dirPath); + + foreach (var content in contents) { - if(content.Name.EndsWith(".gitattributes")) - continue; - - if(File.Exists(localPath) && !content.Name.EndsWith(".lang")) - continue; + string localPath = Path.Combine(localDir, content.Name); - if (content.Name.EndsWith(".lang") && File.Exists(localPath) && - new FileInfo(localPath).Length == content.Size) + if (content.Type == ContentType.File) { - Logger.Info($"Skipped language file '{content.Name}'"); - continue; - } - - var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path); - Directory.CreateDirectory(localDir); // Ensure the directory exists - await File.WriteAllBytesAsync(localPath, fileContent); + if (content.Name.EndsWith(".gitattributes")) + continue; - Logger.Debug($"Synced file '{content.Path}'"); - } - else if (content.Type == ContentType.Dir) - { - await CopyDirectory(content.Path, localPath); + if (File.Exists(localPath) && !content.Name.EndsWith(".lang")) + continue; + + if (content.Name.EndsWith(".lang") && File.Exists(localPath) && + new FileInfo(localPath).Length == content.Size) + continue; + + var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path); + Directory.CreateDirectory(localDir); // Ensure the directory exists + await File.WriteAllBytesAsync(localPath, fileContent); + + Logger.Debug($"Synced file '{content.Path}'"); + } + else if (content.Type == ContentType.Dir) + { + await CopyDirectory(content.Path, localPath); + } } } + + await CopyDirectory("", resourcesDir); + } + catch (RateLimitExceededException) + { + Logger.Warn("Unable to sync resources due to your ip being rate-limited by github"); + } + catch (Exception e) + { + Logger.Warn("Unable to sync resources"); + Logger.Warn(e); } - - await CopyDirectory("", resourcesDir); } private bool IsEmpty(string path) { return !Directory.EnumerateFileSystemEntries(path).Any(); } + private static void CopyFilesRecursively(string sourcePath, string targetPath) { //Now Create all of the directories @@ -107,7 +116,7 @@ public class StorageService } //Copy all the files & Replaces any files with the same name - foreach (string newPath in Directory.GetFiles(sourcePath, "*.*",SearchOption.AllDirectories)) + foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) { File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true); } diff --git a/Moonlight/App/Services/IpVerificationService.cs b/Moonlight/App/Services/IpVerificationService.cs new file mode 100644 index 00000000..af67b523 --- /dev/null +++ b/Moonlight/App/Services/IpVerificationService.cs @@ -0,0 +1,91 @@ +using Moonlight.App.Helpers; +using Whois.NET; + +namespace Moonlight.App.Services; + +public class IpVerificationService +{ + private readonly ConfigService ConfigService; + + public IpVerificationService(ConfigService configService) + { + ConfigService = configService; + } + + public async Task IsDatacenterOrVpn(string ip) + { + if (!ConfigService.Get().Moonlight.Security.BlockDatacenterIps) + return false; + + if (string.IsNullOrEmpty(ip)) + return false; + + var datacenterNames = new List() + { + "amazon", + "aws", + "microsoft", + "azure", + "google", + "google cloud", + "gcp", + "digitalocean", + "linode", + "vultr", + "ovh", + "ovhcloud", + "alibaba", + "oracle", + "ibm cloud", + "bluehost", + "godaddy", + "rackpace", + "hetzner", + "tencent", + "scaleway", + "softlayer", + "dreamhost", + "a2 hosting", + "inmotion hosting", + "red hat openstack", + "kamatera", + "hostgator", + "siteground", + "greengeeks", + "liquidweb", + "joyent", + "aruba", + "interoute", + "fastcomet", + "rosehosting", + "lunarpages", + "fatcow", + "jelastic", + "datacamp" + }; + + if(!ConfigService.Get().Moonlight.Security.AllowCloudflareIps) + datacenterNames.Add("cloudflare"); + + try + { + var response = await WhoisClient.QueryAsync(ip); + var responseText = response.Raw.ToLower(); + + foreach (var name in datacenterNames) + { + if (responseText.Contains(name)) + { + Logger.Debug(name); + return true; + } + } + } + catch (Exception) + { + return false; + } + + return false; + } +} \ No newline at end of file diff --git a/Moonlight/Moonlight.csproj b/Moonlight/Moonlight.csproj index 7055763d..d5d61207 100644 --- a/Moonlight/Moonlight.csproj +++ b/Moonlight/Moonlight.csproj @@ -55,6 +55,7 @@ + diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index e866b418..e0b769d4 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -220,6 +220,7 @@ namespace Moonlight builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); diff --git a/Moonlight/Shared/Layouts/MainLayout.razor b/Moonlight/Shared/Layouts/MainLayout.razor index 0a75b787..5178124e 100644 --- a/Moonlight/Shared/Layouts/MainLayout.razor +++ b/Moonlight/Shared/Layouts/MainLayout.razor @@ -22,6 +22,7 @@ @inject DynamicBackgroundService DynamicBackgroundService @inject KeyListenerService KeyListenerService @inject ConfigService ConfigService +@inject IpVerificationService IpVerificationService @{ var uri = new Uri(NavigationManager.Uri); @@ -46,9 +47,35 @@ - @if (!IsIpBanned) + @if (UserProcessed) { - if (UserProcessed) + if (IsIpBanned) + { + + } + else if (IsIpSuspicious) + { + + } + else { if (uri.LocalPath != "/login" && uri.LocalPath != "/passwordreset" && @@ -102,19 +129,6 @@ } } } - else - { - - } } else { @@ -122,8 +136,8 @@ @@ -137,6 +151,7 @@ { private bool UserProcessed = false; private bool IsIpBanned = false; + private bool IsIpSuspicious = false; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -146,11 +161,6 @@ { DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) => { await InvokeAsync(StateHasChanged); }; - IsIpBanned = await IpBanService.IsBanned(); - - if (IsIpBanned) - await InvokeAsync(StateHasChanged); - await Event.On("ipBan.update", this, async _ => { IsIpBanned = await IpBanService.IsBanned(); @@ -158,6 +168,10 @@ }); await IdentityService.Load(); + + IsIpBanned = await IpBanService.IsBanned(); + IsIpSuspicious = await IpVerificationService.IsDatacenterOrVpn(IdentityService.Ip); + UserProcessed = true; await InvokeAsync(StateHasChanged);