From b2afbc687246d539449bf17c52290c6d62b9d7ac Mon Sep 17 00:00:00 2001 From: soup Date: Mon, 2 Dec 2024 01:20:08 +0100 Subject: [PATCH] New: Add config file setting for CGNAT authentication bypass (cherry picked from commit 4c41a4f368046f73f82306bbd73bec992392938b) --- .../IPAddressExtensionsFixture.cs | 19 +++++++++++++++++++ .../Extensions/IpAddressExtensions.cs | 6 ++++++ src/NzbDrone.Common/Options/AuthOptions.cs | 1 + .../Configuration/ConfigFileProvider.cs | 3 +++ .../Configuration/ConfigService.cs | 6 ++++++ .../Config/HostConfigResource.cs | 1 + .../Authentication/UiAuthorizationHandler.cs | 9 ++++++--- 7 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs b/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs index ff5d7383e..0f7ad3004 100644 --- a/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs +++ b/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs @@ -21,9 +21,28 @@ namespace NzbDrone.Common.Test.ExtensionTests [TestCase("1.2.3.4")] [TestCase("172.55.0.1")] [TestCase("192.55.0.1")] + [TestCase("100.64.0.1")] + [TestCase("100.127.255.254")] public void should_return_false_for_public_ip_address(string ipAddress) { IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse(); } + + [TestCase("100.64.0.1")] + [TestCase("100.127.255.254")] + [TestCase("100.100.100.100")] + public void should_return_true_for_cgnat_ip_address(string ipAddress) + { + IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeTrue(); + } + + [TestCase("1.2.3.4")] + [TestCase("192.168.5.1")] + [TestCase("100.63.255.255")] + [TestCase("100.128.0.0")] + public void should_return_false_for_non_cgnat_ip_address(string ipAddress) + { + IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeFalse(); + } } } diff --git a/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs b/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs index b60d4271a..cbc1f5f83 100644 --- a/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs +++ b/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs @@ -52,5 +52,11 @@ namespace NzbDrone.Common.Extensions return isLinkLocal || isClassA || isClassC || isClassB; } + + public static bool IsCgnatIpAddress(this IPAddress ipAddress) + { + var bytes = ipAddress.GetAddressBytes(); + return bytes.Length == 4 && bytes[0] == 100 && bytes[1] >= 64 && bytes[1] <= 127; + } } } diff --git a/src/NzbDrone.Common/Options/AuthOptions.cs b/src/NzbDrone.Common/Options/AuthOptions.cs index 2b63308d3..64330b68b 100644 --- a/src/NzbDrone.Common/Options/AuthOptions.cs +++ b/src/NzbDrone.Common/Options/AuthOptions.cs @@ -6,4 +6,5 @@ public class AuthOptions public bool? Enabled { get; set; } public string Method { get; set; } public string Required { get; set; } + public bool? TrustCgnatIpAddresses { get; set; } } diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index 6b5203e47..b9f4f23d2 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -65,6 +65,7 @@ namespace NzbDrone.Core.Configuration string PostgresPassword { get; } string PostgresMainDb { get; } string PostgresLogDb { get; } + bool TrustCgnatIpAddresses { get; } } public class ConfigFileProvider : IConfigFileProvider @@ -479,5 +480,7 @@ namespace NzbDrone.Core.Configuration SetValue("ApiKey", GenerateApiKey()); _eventAggregator.PublishEvent(new ApiKeyChangedEvent()); } + + public bool TrustCgnatIpAddresses => _authOptions.TrustCgnatIpAddresses ?? GetValueBoolean("TrustCgnatIpAddresses", false, persist: false); } } diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 208692c97..27a953823 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -183,6 +183,12 @@ namespace NzbDrone.Core.Configuration public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty); + public bool TrustCgnatIpAddresses + { + get { return GetValueBoolean("TrustCgnatIpAddresses", false); } + set { SetValue("TrustCgnatIpAddresses", value); } + } + private string GetValue(string key) { return GetValue(key, string.Empty); diff --git a/src/Prowlarr.Api.V1/Config/HostConfigResource.cs b/src/Prowlarr.Api.V1/Config/HostConfigResource.cs index 77c274d66..4bd0cd10a 100644 --- a/src/Prowlarr.Api.V1/Config/HostConfigResource.cs +++ b/src/Prowlarr.Api.V1/Config/HostConfigResource.cs @@ -46,6 +46,7 @@ namespace Prowlarr.Api.V1.Config public int BackupInterval { get; set; } public int BackupRetention { get; set; } public int HistoryCleanupDays { get; set; } + public bool TrustCgnatIpAddresses { get; set; } } public static class HostConfigResourceMapper diff --git a/src/Prowlarr.Http/Authentication/UiAuthorizationHandler.cs b/src/Prowlarr.Http/Authentication/UiAuthorizationHandler.cs index 738364748..7ec3b5220 100644 --- a/src/Prowlarr.Http/Authentication/UiAuthorizationHandler.cs +++ b/src/Prowlarr.Http/Authentication/UiAuthorizationHandler.cs @@ -27,10 +27,13 @@ namespace Prowlarr.Http.Authentication if (_authenticationRequired == AuthenticationRequiredType.DisabledForLocalAddresses) { if (context.Resource is HttpContext httpContext && - IPAddress.TryParse(httpContext.GetRemoteIP(), out var ipAddress) && - ipAddress.IsLocalAddress()) + IPAddress.TryParse(httpContext.GetRemoteIP(), out var ipAddress)) { - context.Succeed(requirement); + if (ipAddress.IsLocalAddress() || + (_configService.TrustCgnatIpAddresses && ipAddress.IsCgnatIpAddress())) + { + context.Succeed(requirement); + } } }