Merge pull request #295 from Moonlight-Panel/AddIpFiltering

Implemented ip filtering to detect datacenter and vpn ips
This commit is contained in:
Marcel Baumgartner
2023-08-29 15:03:31 +02:00
committed by GitHub
6 changed files with 194 additions and 70 deletions

View File

@@ -304,6 +304,14 @@ public class ConfigV1
public int BlockIpDuration { get; set; } = 15; public int BlockIpDuration { get; set; } = 15;
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new(); [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 public class ReCaptchaData

View File

@@ -39,16 +39,17 @@ public class StorageService
} }
private async Task UpdateResources() private async Task UpdateResources()
{
try
{ {
Logger.Info("Checking resources"); Logger.Info("Checking resources");
var client = new GitHubClient( var client = new GitHubClient(
new ProductHeaderValue("Moonlight-Panel")); new ProductHeaderValue("Moonlight-Panel"));
string user = "Moonlight-Panel"; var user = "Moonlight-Panel";
string repo = "Resources"; var repo = "Resources";
string branch = "main"; var resourcesDir = PathBuilder.Dir("storage", "resources");
string resourcesDir = PathBuilder.Dir("storage", "resources");
async Task CopyDirectory(string dirPath, string localDir) async Task CopyDirectory(string dirPath, string localDir)
{ {
@@ -73,10 +74,7 @@ public class StorageService
if (content.Name.EndsWith(".lang") && File.Exists(localPath) && if (content.Name.EndsWith(".lang") && File.Exists(localPath) &&
new FileInfo(localPath).Length == content.Size) new FileInfo(localPath).Length == content.Size)
{
Logger.Info($"Skipped language file '{content.Name}'");
continue; continue;
}
var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path); var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path);
Directory.CreateDirectory(localDir); // Ensure the directory exists Directory.CreateDirectory(localDir); // Ensure the directory exists
@@ -93,11 +91,22 @@ public class StorageService
await CopyDirectory("", resourcesDir); 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);
}
}
private bool IsEmpty(string path) private bool IsEmpty(string path)
{ {
return !Directory.EnumerateFileSystemEntries(path).Any(); return !Directory.EnumerateFileSystemEntries(path).Any();
} }
private static void CopyFilesRecursively(string sourcePath, string targetPath) private static void CopyFilesRecursively(string sourcePath, string targetPath)
{ {
//Now Create all of the directories //Now Create all of the directories

View File

@@ -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<bool> IsDatacenterOrVpn(string ip)
{
if (!ConfigService.Get().Moonlight.Security.BlockDatacenterIps)
return false;
if (string.IsNullOrEmpty(ip))
return false;
var datacenterNames = new List<string>()
{
"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;
}
}

View File

@@ -55,6 +55,7 @@
<PackageReference Include="SSH.NET" Version="2020.0.2" /> <PackageReference Include="SSH.NET" Version="2020.0.2" />
<PackageReference Include="Stripe.net" Version="41.23.0-beta.1" /> <PackageReference Include="Stripe.net" Version="41.23.0-beta.1" />
<PackageReference Include="UAParser" Version="3.1.47" /> <PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="WhoisClient.NET" Version="5.0.0" />
<PackageReference Include="XtermBlazor" Version="1.8.1" /> <PackageReference Include="XtermBlazor" Version="1.8.1" />
</ItemGroup> </ItemGroup>

View File

@@ -220,6 +220,7 @@ namespace Moonlight
builder.Services.AddScoped<TicketClientService>(); builder.Services.AddScoped<TicketClientService>();
builder.Services.AddScoped<TicketAdminService>(); builder.Services.AddScoped<TicketAdminService>();
builder.Services.AddScoped<MalwareScanService>(); builder.Services.AddScoped<MalwareScanService>();
builder.Services.AddSingleton<IpVerificationService>();
builder.Services.AddScoped<SessionClientService>(); builder.Services.AddScoped<SessionClientService>();
builder.Services.AddSingleton<SessionServerService>(); builder.Services.AddSingleton<SessionServerService>();

View File

@@ -22,6 +22,7 @@
@inject DynamicBackgroundService DynamicBackgroundService @inject DynamicBackgroundService DynamicBackgroundService
@inject KeyListenerService KeyListenerService @inject KeyListenerService KeyListenerService
@inject ConfigService ConfigService @inject ConfigService ConfigService
@inject IpVerificationService IpVerificationService
@{ @{
var uri = new Uri(NavigationManager.Uri); var uri = new Uri(NavigationManager.Uri);
@@ -46,9 +47,35 @@
<DefaultLayout> <DefaultLayout>
<SoftErrorBoundary> <SoftErrorBoundary>
@if (!IsIpBanned) @if (UserProcessed)
{ {
if (UserProcessed) if (IsIpBanned)
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
</div>
</div>
</div>
</div>
}
else if (IsIpSuspicious)
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Your ip his blocked. VPNs and Datacenter IPs are prohibited from accessing this site"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Please disable your vpn or proxy and try it again"))</p>
</div>
</div>
</div>
</div>
}
else
{ {
if (uri.LocalPath != "/login" && if (uri.LocalPath != "/login" &&
uri.LocalPath != "/passwordreset" && uri.LocalPath != "/passwordreset" &&
@@ -102,6 +129,7 @@
} }
} }
} }
}
else else
{ {
<div class="modal d-block"> <div class="modal d-block">
@@ -115,20 +143,6 @@
</div> </div>
</div> </div>
} }
}
else
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
</div>
</div>
</div>
</div>
}
</SoftErrorBoundary> </SoftErrorBoundary>
</DefaultLayout> </DefaultLayout>
</GlobalErrorBoundary> </GlobalErrorBoundary>
@@ -137,6 +151,7 @@
{ {
private bool UserProcessed = false; private bool UserProcessed = false;
private bool IsIpBanned = false; private bool IsIpBanned = false;
private bool IsIpSuspicious = false;
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
@@ -146,11 +161,6 @@
{ {
DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) => { await InvokeAsync(StateHasChanged); }; DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) => { await InvokeAsync(StateHasChanged); };
IsIpBanned = await IpBanService.IsBanned();
if (IsIpBanned)
await InvokeAsync(StateHasChanged);
await Event.On<Object>("ipBan.update", this, async _ => await Event.On<Object>("ipBan.update", this, async _ =>
{ {
IsIpBanned = await IpBanService.IsBanned(); IsIpBanned = await IpBanService.IsBanned();
@@ -158,6 +168,10 @@
}); });
await IdentityService.Load(); await IdentityService.Load();
IsIpBanned = await IpBanService.IsBanned();
IsIpSuspicious = await IpVerificationService.IsDatacenterOrVpn(IdentityService.Ip);
UserProcessed = true; UserProcessed = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);