From c3fea2c325a5e03db3ffe013b4e837d44a3f7ad8 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Fri, 11 Oct 2024 08:08:54 +0200 Subject: [PATCH 01/15] Adds SecurityOptions to Global Configuration --- .../Creator/ISecurityOptionsCreator.cs | 2 +- .../Configuration/Creator/RoutesCreator.cs | 2 +- .../Creator/SecurityOptionsCreator.cs | 44 ++++++----- .../File/FileGlobalConfiguration.cs | 3 + .../File/FileSecurityOptionsExtensions.cs | 8 ++ .../Configuration/RoutesCreatorTests.cs | 2 +- .../SecurityOptionsCreatorTests.cs | 78 ++++++++++++++++--- .../Security/IPSecurityPolicyTests.cs | 48 +++++++++--- 8 files changed, 145 insertions(+), 42 deletions(-) create mode 100644 src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs diff --git a/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs index 5f5f727f3..16cea24a6 100644 --- a/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs @@ -4,6 +4,6 @@ namespace Ocelot.Configuration.Creator { public interface ISecurityOptionsCreator { - SecurityOptions Create(FileSecurityOptions securityOptions); + SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration); } } diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index 015944014..409257700 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -108,7 +108,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf var lbOptions = _loadBalancerOptionsCreator.Create(fileRoute.LoadBalancerOptions); - var securityOptions = _securityOptionsCreator.Create(fileRoute.SecurityOptions); + var securityOptions = _securityOptionsCreator.Create(fileRoute.SecurityOptions, globalConfiguration); var downstreamHttpVersion = _versionCreator.Create(fileRoute.DownstreamHttpVersion); diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index 3c4c93d80..13b3637ff 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -5,28 +5,20 @@ namespace Ocelot.Configuration.Creator { public class SecurityOptionsCreator : ISecurityOptionsCreator { - public SecurityOptions Create(FileSecurityOptions securityOptions) + public SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration) { - var ipAllowedList = new List(); - var ipBlockedList = new List(); - - foreach (var allowed in securityOptions.IPAllowedList) + if (securityOptions.IsFullFilled()) { - if (IPAddressRange.TryParse(allowed, out var allowedIpAddressRange)) - { - var allowedIps = allowedIpAddressRange.Select(x => x.ToString()); - ipAllowedList.AddRange(allowedIps); - } + return Create(securityOptions); } - foreach (var blocked in securityOptions.IPBlockedList) - { - if (IPAddressRange.TryParse(blocked, out var blockedIpAddressRange)) - { - var blockedIps = blockedIpAddressRange.Select(x => x.ToString()); - ipBlockedList.AddRange(blockedIps); - } - } + return Create(globalConfiguration.SecurityOptions); + } + + private static SecurityOptions Create(FileSecurityOptions securityOptions) + { + var ipAllowedList = SetIpAddressList(securityOptions.IPAllowedList); + var ipBlockedList = SetIpAddressList(securityOptions.IPBlockedList); if (securityOptions.ExcludeAllowedFromBlocked) { @@ -35,5 +27,21 @@ public SecurityOptions Create(FileSecurityOptions securityOptions) return new SecurityOptions(ipAllowedList, ipBlockedList); } + + private static List SetIpAddressList(List ipValueList) + { + var ipList = new List(); + + foreach (var ipValue in ipValueList) + { + if (IPAddressRange.TryParse(ipValue, out var ipAddressRange)) + { + var ips = ipAddressRange.Select(x => x.ToString()); + ipList.AddRange(ips); + } + } + + return ipList; + } } } diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 7ce35f99e..1675680d3 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -13,6 +13,7 @@ public FileGlobalConfiguration() HttpHandlerOptions = new FileHttpHandlerOptions(); CacheOptions = new FileCacheOptions(); MetadataOptions = new FileMetadataOptions(); + SecurityOptions = new FileSecurityOptions(); } public string RequestIdKey { get; set; } @@ -48,5 +49,7 @@ public FileGlobalConfiguration() public FileCacheOptions CacheOptions { get; set; } public FileMetadataOptions MetadataOptions { get; set; } + + public FileSecurityOptions SecurityOptions { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs b/src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs new file mode 100644 index 000000000..aa9dc8f17 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.File +{ + internal static class FileSecurityOptionsExtensions + { + internal static bool IsFullFilled(this FileSecurityOptions fileSecurityOptions) + => fileSecurityOptions.IPAllowedList.Count > 0 || fileSecurityOptions.IPBlockedList.Count > 0; + } +} diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index dbb9ef25a..2c2b6e9bc 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -303,7 +303,7 @@ private void ThenTheDepsAreCalledFor(FileRoute fileRoute, FileGlobalConfiguratio _hfarCreator.Verify(x => x.Create(fileRoute), Times.Once); _daCreator.Verify(x => x.Create(fileRoute), Times.Once); _lboCreator.Verify(x => x.Create(fileRoute.LoadBalancerOptions), Times.Once); - _soCreator.Verify(x => x.Create(fileRoute.SecurityOptions), Times.Once); + _soCreator.Verify(x => x.Create(fileRoute.SecurityOptions, globalConfig), Times.Once); _metadataCreator.Verify(x => x.Create(fileRoute.Metadata, globalConfig), Times.Once); } } diff --git a/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs index 81d9fd36d..e6b7ce82f 100644 --- a/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs @@ -6,8 +6,9 @@ namespace Ocelot.UnitTests.Configuration { public class SecurityOptionsCreatorTests : UnitTest { - private FileRoute _fileRoute; + private FileSecurityOptions _fileSecurityOptions; private SecurityOptions _result; + private FileGlobalConfiguration _globalConfig; private readonly ISecurityOptionsCreator _creator; public SecurityOptionsCreatorTests() @@ -16,11 +17,31 @@ public SecurityOptionsCreatorTests() } [Fact] - public void should_create_security_config() + public void should_create_route_security_config() { var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; - var fileRoute = new FileRoute + var securityOptions = new FileSecurityOptions + { + IPAllowedList = ipAllowedList, + IPBlockedList = ipBlockedList, + }; + + var expected = new SecurityOptions(ipAllowedList, ipBlockedList); + + this.Given(x => x.GivenThe(new FileGlobalConfiguration())) + .Given(x => x.GivenThe(securityOptions)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_create_global_security_config() + { + var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; + var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; + var globalConfig = new FileGlobalConfiguration { SecurityOptions = new FileSecurityOptions { @@ -31,20 +52,57 @@ public void should_create_security_config() var expected = new SecurityOptions(ipAllowedList, ipBlockedList); - this.Given(x => x.GivenThe(fileRoute)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheResultIs(expected)) - .BDDfy(); + this.Given(x => x.GivenThe(globalConfig)) + .Given(x => x.GivenThe(new FileSecurityOptions())) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheResultIs(expected)) + .BDDfy(); + } + + [Fact] + public void should_create_global_route_security_config() + { + var routeIpAllowedList = new List { "127.0.0.1", "192.168.1.1" }; + var routeIpBlockedList = new List { "127.0.0.1", "192.168.1.1" }; + var securityOptions = new FileSecurityOptions + { + IPAllowedList = routeIpAllowedList, + IPBlockedList = routeIpBlockedList, + }; + + var globalIpAllowedList = new List { "127.0.0.2", "192.168.1.2" }; + var globalIpBlockedList = new List { "127.0.0.2", "192.168.1.2" }; + var globalConfig = new FileGlobalConfiguration + { + SecurityOptions = new FileSecurityOptions + { + IPAllowedList = globalIpAllowedList, + IPBlockedList = globalIpBlockedList, + }, + }; + + var expected = new SecurityOptions(routeIpAllowedList, routeIpBlockedList); + + this.Given(x => x.GivenThe(globalConfig)) + .Given(x => x.GivenThe(securityOptions)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheResultIs(expected)) + .BDDfy(); + } + + private void GivenThe(FileSecurityOptions fileSecurityOptions) + { + _fileSecurityOptions = fileSecurityOptions; } - private void GivenThe(FileRoute route) + private void GivenThe(FileGlobalConfiguration globalConfiguration) { - _fileRoute = route; + _globalConfig = globalConfiguration; } private void WhenICreate() { - _result = _creator.Create(_fileRoute.SecurityOptions); + _result = _creator.Create(_fileSecurityOptions, _globalConfig); } private void ThenTheResultIs(SecurityOptions expected) diff --git a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs index 83d7bb73f..4d6f1436f 100644 --- a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs +++ b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs @@ -17,6 +17,7 @@ public class IPSecurityPolicyTests : UnitTest private Response response; private readonly HttpContext _httpContext; private readonly SecurityOptionsCreator _securityOptionsCreator; + private readonly FileGlobalConfiguration _emptyFileGlobalConfiguration; public IPSecurityPolicyTests() { @@ -26,6 +27,7 @@ public IPSecurityPolicyTests() _downstreamRouteBuilder = new DownstreamRouteBuilder(); _ipSecurityPolicy = new IPSecurityPolicy(); _securityOptionsCreator = new SecurityOptionsCreator(); + _emptyFileGlobalConfiguration = new FileGlobalConfiguration(); } [Fact] @@ -312,6 +314,17 @@ public void should_exludeAllowedFromBlocked_moreBlocked_clientIp_not_block() .BDDfy(); } + [Fact] + public void should_route_config_overrides_global_config() + { + _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; + this.Given(x => x.GivenRouteConfigAndGlobalConfig(false)) + .Given(x => x.GivenSetDownstreamRoute()) + .When(x => x.WhenTheSecurityPolicy()) + .Then(x => x.ThenSecurityPassing()) + .BDDfy(); + } + private void GivenSetAllowedIP() { _downstreamRouteBuilder.WithSecurityOptions(new SecurityOptions("192.168.1.1")); @@ -329,67 +342,80 @@ private void GivenSetDownstreamRoute() private void GivenCidr24AllowedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/24")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/24"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenCidr29AllowedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/29")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/29"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenCidr24BlockedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/24")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/24"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenRangeAllowedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-192.168.1.10")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-192.168.1.10"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenRangeBlockedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-192.168.1.10")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-192.168.1.10"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenShortRangeAllowedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-10")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-10"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenShortRangeBlockedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-10")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-10"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenIpSubnetAllowedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/255.255.255.0")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/255.255.255.0"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenIpSubnetBlockedIP() { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/255.255.255.0")); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/255.255.255.0"), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenIpMoreAllowedThanBlocked(bool excludeAllowedInBlocked) { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.0.0/255.255.0.0", "192.168.1.100-200", excludeAllowedInBlocked)); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.0.0/255.255.0.0", "192.168.1.100-200", excludeAllowedInBlocked), _emptyFileGlobalConfiguration); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } private void GivenIpMoreBlockedThanAllowed(bool excludeAllowedInBlocked) { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.10-20", "192.168.1.0/23", excludeAllowedInBlocked)); + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.10-20", "192.168.1.0/23", excludeAllowedInBlocked), _emptyFileGlobalConfiguration); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenRouteConfigAndGlobalConfig(bool excludeAllowedInBlocked) + { + var globalConfig = new FileGlobalConfiguration + { + SecurityOptions = new FileSecurityOptions("192.168.1.30-50", "192.168.1.1-100", true), + }; + + var localConfig = new FileSecurityOptions("192.168.1.10", "", excludeAllowedInBlocked); + + var securityOptions = _securityOptionsCreator.Create(localConfig, globalConfig); _downstreamRouteBuilder.WithSecurityOptions(securityOptions); } From 1b0af5f3c015fddcb9df310df82581d5c2eeecf1 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Tue, 15 Oct 2024 12:21:20 +0200 Subject: [PATCH 02/15] Adds acceptance tests --- .../Security/SecurityOptionsTests.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs diff --git a/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs b/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs new file mode 100644 index 000000000..81804808a --- /dev/null +++ b/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs @@ -0,0 +1,163 @@ +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; + +namespace Ocelot.AcceptanceTests.Security +{ + public sealed class SecurityOptionsTests: Steps + { + private readonly ServiceHandler _serviceHandler; + + public SecurityOptionsTests() + { + _serviceHandler = new ServiceHandler(); + } + + public override void Dispose() + { + _serviceHandler.Dispose(); + base.Dispose(); + } + + [Fact] + public void should_call_with_allowed_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.35")[0]; + var route = GivenRoute(port, "/myPath", "/worldPath"); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); + } + + [Fact] + public void should_block_call_with_blocked_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.55")[0]; + var route = GivenRoute(port, "/myPath", "/worldPath"); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); + } + + [Fact] + public void should_call_with_allowed_ip_in_route_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.1")[0]; + var securityConfig = new FileSecurityOptions + { + IPAllowedList = new List { "192.168.1.1" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfiguration(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); + } + + [Fact] + public void should_block_call_with_blocked_ip_in_route_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.1")[0]; + var securityConfig = new FileSecurityOptions + { + IPBlockedList = new List { "192.168.1.1" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfiguration(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); + } + + [Fact] + public void should_call_with_allowed_ip_in_route_config_and_blocked_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.55")[0]; + var securityConfig = new FileSecurityOptions + { + IPAllowedList = new List { "192.168.1.55" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); + } + + [Fact] + public void should_block_call_with_blocked_ip_in_route_config_and_allowed_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.35")[0]; + var securityConfig = new FileSecurityOptions + { + IPBlockedList = new List { "192.168.1.35" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); + } + + private void GivenThereIsAServiceRunningOn(string url, IPAddress ipAddess) + { + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + context.Connection.RemoteIpAddress = ipAddess; + context.Response.StatusCode = (int)HttpStatusCode.OK; + await context.Response.WriteAsync("a valida response body"); + }); + } + + private static FileConfiguration GivenConfigurationWithSecurityOptions(params FileRoute[] routes) + { + var config = GivenConfiguration(routes); + config.GlobalConfiguration.SecurityOptions = new FileSecurityOptions + { + IPAllowedList = new List { "192.168.1.30-50" }, + IPBlockedList = new List { "192.168.1.1-100" }, + ExcludeAllowedFromBlocked = true + }; + + return config; + } + + private FileRoute GivenRoute(int port, string downstream, string upstream, FileSecurityOptions fileSecurityOptions = null) + => new() + { + DownstreamPathTemplate = downstream, + UpstreamPathTemplate = upstream, + UpstreamHttpMethod = new List { "Get" }, + SecurityOptions = fileSecurityOptions ?? new FileSecurityOptions(), + }; + } +} From 7e0f06685090e9dcec5f4c5de782dec0c34614d3 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Tue, 15 Oct 2024 15:08:22 +0200 Subject: [PATCH 03/15] set SecurityOptions constructor with IList --- .../Creator/SecurityOptionsCreator.cs | 22 +++++-------------- src/Ocelot/Configuration/SecurityOptions.cs | 18 +++++++-------- .../Security/IPSecurity/IPSecurityPolicy.cs | 4 ++-- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index 13b3637ff..da71998f7 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -22,26 +22,16 @@ private static SecurityOptions Create(FileSecurityOptions securityOptions) if (securityOptions.ExcludeAllowedFromBlocked) { - ipBlockedList = ipBlockedList.Except(ipAllowedList).ToList(); + ipBlockedList = ipBlockedList.Except(ipAllowedList).ToArray(); } return new SecurityOptions(ipAllowedList, ipBlockedList); } - private static List SetIpAddressList(List ipValueList) - { - var ipList = new List(); - - foreach (var ipValue in ipValueList) - { - if (IPAddressRange.TryParse(ipValue, out var ipAddressRange)) - { - var ips = ipAddressRange.Select(x => x.ToString()); - ipList.AddRange(ips); - } - } - - return ipList; + private static string[] SetIpAddressList(IList ipValueList) + => ipValueList + .Where(ipValue => IPAddressRange.TryParse(ipValue, out _)) + .SelectMany(ipValue => IPAddressRange.Parse(ipValue).Select(ip => ip.ToString())) + .ToArray(); } - } } diff --git a/src/Ocelot/Configuration/SecurityOptions.cs b/src/Ocelot/Configuration/SecurityOptions.cs index 96186fe65..03b9545f0 100644 --- a/src/Ocelot/Configuration/SecurityOptions.cs +++ b/src/Ocelot/Configuration/SecurityOptions.cs @@ -4,8 +4,8 @@ public class SecurityOptions { public SecurityOptions() { - IPAllowedList = new(); - IPBlockedList = new(); + IPAllowedList = new List(); + IPBlockedList = new List(); } public SecurityOptions(string allowed = null, string blocked = null) @@ -13,22 +13,22 @@ public SecurityOptions(string allowed = null, string blocked = null) { if (!string.IsNullOrEmpty(allowed)) { - IPAllowedList.Add(allowed); + IPAllowedList = IPAllowedList.Append(allowed).ToList(); } if (!string.IsNullOrEmpty(blocked)) { - IPBlockedList.Add(blocked); + IPBlockedList = IPBlockedList.Append(blocked).ToList(); } } - public SecurityOptions(List allowedList = null, List blockedList = null) + public SecurityOptions(IList allowedList = null, IList blockedList = null) { - IPAllowedList = allowedList ?? new(); - IPBlockedList = blockedList ?? new(); + IPAllowedList = allowedList ?? new List(); + IPBlockedList = blockedList ?? new List(); } - public List IPAllowedList { get; } - public List IPBlockedList { get; } + public IList IPAllowedList { get; } + public IList IPBlockedList { get; } } } diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs index e76044ecf..a82e86f17 100644 --- a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -18,7 +18,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex if (securityOptions.IPBlockedList != null) { - if (securityOptions.IPBlockedList.Exists(f => f == clientIp.ToString())) + if (securityOptions.IPBlockedList.Contains(clientIp.ToString())) { var error = new UnauthenticatedError($" This request rejects access to {clientIp} IP"); return new ErrorResponse(error); @@ -27,7 +27,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex if (securityOptions.IPAllowedList?.Count > 0) { - if (!securityOptions.IPAllowedList.Exists(f => f == clientIp.ToString())) + if (!securityOptions.IPAllowedList.Contains(clientIp.ToString())) { var error = new UnauthenticatedError($"{clientIp} does not allow access, the request is invalid"); return new ErrorResponse(error); From 9e5466984144d6879933fe2a6207c3e7eee9d493 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 16 Oct 2024 11:45:02 +0200 Subject: [PATCH 04/15] Revert "set SecurityOptions constructor with IList" This reverts commit 3e4d4a4afb539d1e13cf997f91be7d118b34d220. --- .../Creator/SecurityOptionsCreator.cs | 22 ++++++++++++++----- src/Ocelot/Configuration/SecurityOptions.cs | 18 +++++++-------- .../Security/IPSecurity/IPSecurityPolicy.cs | 4 ++-- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index da71998f7..13b3637ff 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -22,16 +22,26 @@ private static SecurityOptions Create(FileSecurityOptions securityOptions) if (securityOptions.ExcludeAllowedFromBlocked) { - ipBlockedList = ipBlockedList.Except(ipAllowedList).ToArray(); + ipBlockedList = ipBlockedList.Except(ipAllowedList).ToList(); } return new SecurityOptions(ipAllowedList, ipBlockedList); } - private static string[] SetIpAddressList(IList ipValueList) - => ipValueList - .Where(ipValue => IPAddressRange.TryParse(ipValue, out _)) - .SelectMany(ipValue => IPAddressRange.Parse(ipValue).Select(ip => ip.ToString())) - .ToArray(); + private static List SetIpAddressList(List ipValueList) + { + var ipList = new List(); + + foreach (var ipValue in ipValueList) + { + if (IPAddressRange.TryParse(ipValue, out var ipAddressRange)) + { + var ips = ipAddressRange.Select(x => x.ToString()); + ipList.AddRange(ips); + } + } + + return ipList; } + } } diff --git a/src/Ocelot/Configuration/SecurityOptions.cs b/src/Ocelot/Configuration/SecurityOptions.cs index 03b9545f0..96186fe65 100644 --- a/src/Ocelot/Configuration/SecurityOptions.cs +++ b/src/Ocelot/Configuration/SecurityOptions.cs @@ -4,8 +4,8 @@ public class SecurityOptions { public SecurityOptions() { - IPAllowedList = new List(); - IPBlockedList = new List(); + IPAllowedList = new(); + IPBlockedList = new(); } public SecurityOptions(string allowed = null, string blocked = null) @@ -13,22 +13,22 @@ public SecurityOptions(string allowed = null, string blocked = null) { if (!string.IsNullOrEmpty(allowed)) { - IPAllowedList = IPAllowedList.Append(allowed).ToList(); + IPAllowedList.Add(allowed); } if (!string.IsNullOrEmpty(blocked)) { - IPBlockedList = IPBlockedList.Append(blocked).ToList(); + IPBlockedList.Add(blocked); } } - public SecurityOptions(IList allowedList = null, IList blockedList = null) + public SecurityOptions(List allowedList = null, List blockedList = null) { - IPAllowedList = allowedList ?? new List(); - IPBlockedList = blockedList ?? new List(); + IPAllowedList = allowedList ?? new(); + IPBlockedList = blockedList ?? new(); } - public IList IPAllowedList { get; } - public IList IPBlockedList { get; } + public List IPAllowedList { get; } + public List IPBlockedList { get; } } } diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs index a82e86f17..e76044ecf 100644 --- a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -18,7 +18,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex if (securityOptions.IPBlockedList != null) { - if (securityOptions.IPBlockedList.Contains(clientIp.ToString())) + if (securityOptions.IPBlockedList.Exists(f => f == clientIp.ToString())) { var error = new UnauthenticatedError($" This request rejects access to {clientIp} IP"); return new ErrorResponse(error); @@ -27,7 +27,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex if (securityOptions.IPAllowedList?.Count > 0) { - if (!securityOptions.IPAllowedList.Contains(clientIp.ToString())) + if (!securityOptions.IPAllowedList.Exists(f => f == clientIp.ToString())) { var error = new UnauthenticatedError($"{clientIp} does not allow access, the request is invalid"); return new ErrorResponse(error); From 4667bc26144281f007d2e6bbcb6fce0bf6572840 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 16 Oct 2024 11:46:29 +0200 Subject: [PATCH 05/15] SecurityOptions se constructor parameters as IList --- src/Ocelot/Configuration/SecurityOptions.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ocelot/Configuration/SecurityOptions.cs b/src/Ocelot/Configuration/SecurityOptions.cs index 96186fe65..03b9545f0 100644 --- a/src/Ocelot/Configuration/SecurityOptions.cs +++ b/src/Ocelot/Configuration/SecurityOptions.cs @@ -4,8 +4,8 @@ public class SecurityOptions { public SecurityOptions() { - IPAllowedList = new(); - IPBlockedList = new(); + IPAllowedList = new List(); + IPBlockedList = new List(); } public SecurityOptions(string allowed = null, string blocked = null) @@ -13,22 +13,22 @@ public SecurityOptions(string allowed = null, string blocked = null) { if (!string.IsNullOrEmpty(allowed)) { - IPAllowedList.Add(allowed); + IPAllowedList = IPAllowedList.Append(allowed).ToList(); } if (!string.IsNullOrEmpty(blocked)) { - IPBlockedList.Add(blocked); + IPBlockedList = IPBlockedList.Append(blocked).ToList(); } } - public SecurityOptions(List allowedList = null, List blockedList = null) + public SecurityOptions(IList allowedList = null, IList blockedList = null) { - IPAllowedList = allowedList ?? new(); - IPBlockedList = blockedList ?? new(); + IPAllowedList = allowedList ?? new List(); + IPBlockedList = blockedList ?? new List(); } - public List IPAllowedList { get; } - public List IPBlockedList { get; } + public IList IPAllowedList { get; } + public IList IPBlockedList { get; } } } From cf362ed57450fdacd78fb5224f33642e19e9c4a8 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 16 Oct 2024 11:53:43 +0200 Subject: [PATCH 06/15] SecurityOptionsCreator edit IP parsing in order to pass string[] as SecurityOptions constructr parameters --- .../Creator/SecurityOptionsCreator.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index 13b3637ff..da71998f7 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -22,26 +22,16 @@ private static SecurityOptions Create(FileSecurityOptions securityOptions) if (securityOptions.ExcludeAllowedFromBlocked) { - ipBlockedList = ipBlockedList.Except(ipAllowedList).ToList(); + ipBlockedList = ipBlockedList.Except(ipAllowedList).ToArray(); } return new SecurityOptions(ipAllowedList, ipBlockedList); } - private static List SetIpAddressList(List ipValueList) - { - var ipList = new List(); - - foreach (var ipValue in ipValueList) - { - if (IPAddressRange.TryParse(ipValue, out var ipAddressRange)) - { - var ips = ipAddressRange.Select(x => x.ToString()); - ipList.AddRange(ips); - } - } - - return ipList; + private static string[] SetIpAddressList(IList ipValueList) + => ipValueList + .Where(ipValue => IPAddressRange.TryParse(ipValue, out _)) + .SelectMany(ipValue => IPAddressRange.Parse(ipValue).Select(ip => ip.ToString())) + .ToArray(); } - } } From eb6036e60abf0db52cd1095c79d4d9766cd0bcda Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 16 Oct 2024 11:55:45 +0200 Subject: [PATCH 07/15] IPSecurityPolicy fix Exists to Contains --- src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs index e76044ecf..a82e86f17 100644 --- a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -18,7 +18,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex if (securityOptions.IPBlockedList != null) { - if (securityOptions.IPBlockedList.Exists(f => f == clientIp.ToString())) + if (securityOptions.IPBlockedList.Contains(clientIp.ToString())) { var error = new UnauthenticatedError($" This request rejects access to {clientIp} IP"); return new ErrorResponse(error); @@ -27,7 +27,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex if (securityOptions.IPAllowedList?.Count > 0) { - if (!securityOptions.IPAllowedList.Exists(f => f == clientIp.ToString())) + if (!securityOptions.IPAllowedList.Contains(clientIp.ToString())) { var error = new UnauthenticatedError($"{clientIp} does not allow access, the request is invalid"); return new ErrorResponse(error); From eb2fa5e8628b0c7e19b96d3374c371da1c59d1a8 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 6 Nov 2024 13:10:25 +0100 Subject: [PATCH 08/15] avoid null check nesting, add clientIp null check --- src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs index a82e86f17..f06510778 100644 --- a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -16,7 +16,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex return new OkResponse(); } - if (securityOptions.IPBlockedList != null) + if (securityOptions.IPBlockedList != null && securityOptions.IPBlockedList.Count > 0 && clientIp != null) { if (securityOptions.IPBlockedList.Contains(clientIp.ToString())) { @@ -25,7 +25,7 @@ public async Task Security(DownstreamRoute downstreamRoute, HttpContex } } - if (securityOptions.IPAllowedList?.Count > 0) + if (securityOptions.IPAllowedList != null && securityOptions.IPAllowedList.Count > 0 && clientIp != null) { if (!securityOptions.IPAllowedList.Contains(clientIp.ToString())) { From 4a37e8bee9552b6f3e3d6366872f549258f3c223 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 6 Nov 2024 14:42:14 +0100 Subject: [PATCH 09/15] simplify SecurityOptions Create method --- .../Configuration/Creator/SecurityOptionsCreator.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index da71998f7..fe1453282 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -5,15 +5,8 @@ namespace Ocelot.Configuration.Creator { public class SecurityOptionsCreator : ISecurityOptionsCreator { - public SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration) - { - if (securityOptions.IsFullFilled()) - { - return Create(securityOptions); - } - - return Create(globalConfiguration.SecurityOptions); - } + public SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration) + => Create(securityOptions.IsFullFilled() ? securityOptions : globalConfiguration.SecurityOptions); private static SecurityOptions Create(FileSecurityOptions securityOptions) { From bd592a3cd3dbf2c9b0182b8b0d2e71d6aa77de68 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Tue, 19 Nov 2024 17:57:49 +0100 Subject: [PATCH 10/15] SecurityOptions removed .ToList() --- src/Ocelot/Configuration/SecurityOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ocelot/Configuration/SecurityOptions.cs b/src/Ocelot/Configuration/SecurityOptions.cs index 03b9545f0..09dd9a662 100644 --- a/src/Ocelot/Configuration/SecurityOptions.cs +++ b/src/Ocelot/Configuration/SecurityOptions.cs @@ -13,12 +13,12 @@ public SecurityOptions(string allowed = null, string blocked = null) { if (!string.IsNullOrEmpty(allowed)) { - IPAllowedList = IPAllowedList.Append(allowed).ToList(); + IPAllowedList.Add(allowed); } if (!string.IsNullOrEmpty(blocked)) { - IPBlockedList = IPBlockedList.Append(blocked).ToList(); + IPBlockedList.Add(blocked); } } From b4dbf32ca1c6925fd58ed41d5f15fc0c5ececf30 Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 20 Nov 2024 15:48:37 +0100 Subject: [PATCH 11/15] SecurityOptionsCreator set native parameter in private methods Co-authored-by: Raman Maksimchuk --- src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index fe1453282..e2ffcb694 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -21,7 +21,7 @@ private static SecurityOptions Create(FileSecurityOptions securityOptions) return new SecurityOptions(ipAllowedList, ipBlockedList); } - private static string[] SetIpAddressList(IList ipValueList) + private static string[] SetIpAddressList(List ipValueList) => ipValueList .Where(ipValue => IPAddressRange.TryParse(ipValue, out _)) .SelectMany(ipValue => IPAddressRange.Parse(ipValue).Select(ip => ip.ToString())) From 8f51effae21f06ff11a299d86dfc01529343fd0a Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 20 Nov 2024 16:23:10 +0100 Subject: [PATCH 12/15] SecurityOptionsCreator edited as suggested --- .../Creator/SecurityOptionsCreator.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index e2ffcb694..09b38897e 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -5,7 +5,7 @@ namespace Ocelot.Configuration.Creator { public class SecurityOptionsCreator : ISecurityOptionsCreator { - public SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration) + public SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration) => Create(securityOptions.IsFullFilled() ? securityOptions : globalConfiguration.SecurityOptions); private static SecurityOptions Create(FileSecurityOptions securityOptions) @@ -21,10 +21,16 @@ private static SecurityOptions Create(FileSecurityOptions securityOptions) return new SecurityOptions(ipAllowedList, ipBlockedList); } - private static string[] SetIpAddressList(List ipValueList) - => ipValueList - .Where(ipValue => IPAddressRange.TryParse(ipValue, out _)) - .SelectMany(ipValue => IPAddressRange.Parse(ipValue).Select(ip => ip.ToString())) - .ToArray(); + private static string[] SetIpAddressList(List ipValues) => ipValues.SelectMany(Parse).ToArray(); + + private static string[] Parse(string ipValue) + { + if (IPAddressRange.TryParse(ipValue, out var range)) + { + return range.Select(ip => ip.ToString()).ToArray(); + } + + return Array.Empty(); } + } } From 2c35ad99aa289671ca71608bf91dae0bd8db4ebb Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 20 Nov 2024 16:59:58 +0100 Subject: [PATCH 13/15] fix traits and BDDfy --- .../Security/SecurityOptionsTests.cs | 293 +++++++++--------- .../SecurityOptionsCreatorTests.cs | 15 +- .../Security/IPSecurityPolicyTests.cs | 135 ++++---- 3 files changed, 206 insertions(+), 237 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs b/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs index 81804808a..052428190 100644 --- a/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs +++ b/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs @@ -1,163 +1,162 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; -namespace Ocelot.AcceptanceTests.Security +namespace Ocelot.AcceptanceTests.Security; + +public sealed class SecurityOptionsTests: Steps { - public sealed class SecurityOptionsTests: Steps + private readonly ServiceHandler _serviceHandler; + + public SecurityOptionsTests() { - private readonly ServiceHandler _serviceHandler; + _serviceHandler = new ServiceHandler(); + } - public SecurityOptionsTests() - { - _serviceHandler = new ServiceHandler(); - } + public override void Dispose() + { + _serviceHandler.Dispose(); + base.Dispose(); + } - public override void Dispose() - { - _serviceHandler.Dispose(); - base.Dispose(); - } + [Fact] + public void Should_call_with_allowed_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.35")[0]; + var route = GivenRoute(port, "/myPath", "/worldPath"); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); + } - [Fact] - public void should_call_with_allowed_ip_in_global_config() - { - var port = PortFinder.GetRandomPort(); - var ip = Dns.GetHostAddresses("192.168.1.35")[0]; - var route = GivenRoute(port, "/myPath", "/worldPath"); - var configuration = GivenConfigurationWithSecurityOptions(route); - - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) - .And(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); - } - - [Fact] - public void should_block_call_with_blocked_ip_in_global_config() - { - var port = PortFinder.GetRandomPort(); - var ip = Dns.GetHostAddresses("192.168.1.55")[0]; - var route = GivenRoute(port, "/myPath", "/worldPath"); - var configuration = GivenConfigurationWithSecurityOptions(route); - - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) - .And(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); - } - - [Fact] - public void should_call_with_allowed_ip_in_route_config() + [Fact] + public void Should_block_call_with_blocked_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.55")[0]; + var route = GivenRoute(port, "/myPath", "/worldPath"); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); + } + + [Fact] + public void Should_call_with_allowed_ip_in_route_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.1")[0]; + var securityConfig = new FileSecurityOptions { - var port = PortFinder.GetRandomPort(); - var ip = Dns.GetHostAddresses("192.168.1.1")[0]; - var securityConfig = new FileSecurityOptions - { - IPAllowedList = new List { "192.168.1.1" }, - }; - - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); - var configuration = GivenConfiguration(route); - - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) - .And(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); - } - - [Fact] - public void should_block_call_with_blocked_ip_in_route_config() + IPAllowedList = new List { "192.168.1.1" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfiguration(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); + } + + [Fact] + public void Should_block_call_with_blocked_ip_in_route_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.1")[0]; + var securityConfig = new FileSecurityOptions { - var port = PortFinder.GetRandomPort(); - var ip = Dns.GetHostAddresses("192.168.1.1")[0]; - var securityConfig = new FileSecurityOptions - { - IPBlockedList = new List { "192.168.1.1" }, - }; - - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); - var configuration = GivenConfiguration(route); - - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) - .And(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); - } - - [Fact] - public void should_call_with_allowed_ip_in_route_config_and_blocked_ip_in_global_config() + IPBlockedList = new List { "192.168.1.1" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfiguration(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); + } + + [Fact] + public void Should_call_with_allowed_ip_in_route_config_and_blocked_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.55")[0]; + var securityConfig = new FileSecurityOptions { - var port = PortFinder.GetRandomPort(); - var ip = Dns.GetHostAddresses("192.168.1.55")[0]; - var securityConfig = new FileSecurityOptions - { - IPAllowedList = new List { "192.168.1.55" }, - }; - - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); - var configuration = GivenConfigurationWithSecurityOptions(route); - - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) - .And(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); - } - - [Fact] - public void should_block_call_with_blocked_ip_in_route_config_and_allowed_ip_in_global_config() + IPAllowedList = new List { "192.168.1.55" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); + } + + [Fact] + public void Should_block_call_with_blocked_ip_in_route_config_and_allowed_ip_in_global_config() + { + var port = PortFinder.GetRandomPort(); + var ip = Dns.GetHostAddresses("192.168.1.35")[0]; + var securityConfig = new FileSecurityOptions { - var port = PortFinder.GetRandomPort(); - var ip = Dns.GetHostAddresses("192.168.1.35")[0]; - var securityConfig = new FileSecurityOptions - { - IPBlockedList = new List { "192.168.1.35" }, - }; - - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); - var configuration = GivenConfigurationWithSecurityOptions(route); - - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) - .And(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); - } - - private void GivenThereIsAServiceRunningOn(string url, IPAddress ipAddess) + IPBlockedList = new List { "192.168.1.35" }, + }; + + var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); + var configuration = GivenConfigurationWithSecurityOptions(route); + + this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); + } + + private void GivenThereIsAServiceRunningOn(string url, IPAddress ipAddess) + { + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - context.Connection.RemoteIpAddress = ipAddess; - context.Response.StatusCode = (int)HttpStatusCode.OK; - await context.Response.WriteAsync("a valida response body"); - }); - } - - private static FileConfiguration GivenConfigurationWithSecurityOptions(params FileRoute[] routes) + context.Connection.RemoteIpAddress = ipAddess; + context.Response.StatusCode = (int)HttpStatusCode.OK; + await context.Response.WriteAsync("a valida response body"); + }); + } + + private static FileConfiguration GivenConfigurationWithSecurityOptions(params FileRoute[] routes) + { + var config = GivenConfiguration(routes); + config.GlobalConfiguration.SecurityOptions = new FileSecurityOptions { - var config = GivenConfiguration(routes); - config.GlobalConfiguration.SecurityOptions = new FileSecurityOptions - { - IPAllowedList = new List { "192.168.1.30-50" }, - IPBlockedList = new List { "192.168.1.1-100" }, - ExcludeAllowedFromBlocked = true - }; - - return config; - } - - private FileRoute GivenRoute(int port, string downstream, string upstream, FileSecurityOptions fileSecurityOptions = null) - => new() - { - DownstreamPathTemplate = downstream, - UpstreamPathTemplate = upstream, - UpstreamHttpMethod = new List { "Get" }, - SecurityOptions = fileSecurityOptions ?? new FileSecurityOptions(), - }; + IPAllowedList = new List { "192.168.1.30-50" }, + IPBlockedList = new List { "192.168.1.1-100" }, + ExcludeAllowedFromBlocked = true + }; + + return config; } + + private FileRoute GivenRoute(int port, string downstream, string upstream, FileSecurityOptions fileSecurityOptions = null) + => new() + { + DownstreamPathTemplate = downstream, + UpstreamPathTemplate = upstream, + UpstreamHttpMethod = new List { "Get" }, + SecurityOptions = fileSecurityOptions ?? new FileSecurityOptions(), + }; } diff --git a/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs index e6b7ce82f..1cde2b724 100644 --- a/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs @@ -17,7 +17,7 @@ public SecurityOptionsCreatorTests() } [Fact] - public void should_create_route_security_config() + public void Should_create_route_security_config() { var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; @@ -32,12 +32,11 @@ public void should_create_route_security_config() this.Given(x => x.GivenThe(new FileGlobalConfiguration())) .Given(x => x.GivenThe(securityOptions)) .When(x => x.WhenICreate()) - .Then(x => x.ThenTheResultIs(expected)) - .BDDfy(); + .Then(x => x.ThenTheResultIs(expected)); } [Fact] - public void should_create_global_security_config() + public void Should_create_global_security_config() { var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; @@ -55,12 +54,11 @@ public void should_create_global_security_config() this.Given(x => x.GivenThe(globalConfig)) .Given(x => x.GivenThe(new FileSecurityOptions())) .When(x => x.WhenICreate()) - .Then(x => x.ThenTheResultIs(expected)) - .BDDfy(); + .Then(x => x.ThenTheResultIs(expected)); } [Fact] - public void should_create_global_route_security_config() + public void Should_create_global_route_security_config() { var routeIpAllowedList = new List { "127.0.0.1", "192.168.1.1" }; var routeIpBlockedList = new List { "127.0.0.1", "192.168.1.1" }; @@ -86,8 +84,7 @@ public void should_create_global_route_security_config() this.Given(x => x.GivenThe(globalConfig)) .Given(x => x.GivenThe(securityOptions)) .When(x => x.WhenICreate()) - .Then(x => x.ThenTheResultIs(expected)) - .BDDfy(); + .Then(x => x.ThenTheResultIs(expected)); } private void GivenThe(FileSecurityOptions fileSecurityOptions) diff --git a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs index 4d6f1436f..e8aa3b5f3 100644 --- a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs +++ b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs @@ -31,298 +31,271 @@ public IPSecurityPolicyTests() } [Fact] - public void should_No_blocked_Ip_and_allowed_Ip() + public void Should_No_blocked_Ip_and_allowed_Ip() { this.Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_blockedIp_clientIp_block() + public void Should_blockedIp_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; this.Given(x => x.GivenSetBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_blockedIp_clientIp_Not_block() + public void Should_blockedIp_clientIp_Not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; this.Given(x => x.GivenSetBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_allowedIp_clientIp_block() + public void Should_allowedIp_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; this.Given(x => x.GivenSetAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_allowedIp_clientIp_Not_block() + public void Should_allowedIp_clientIp_Not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; this.Given(x => x.GivenSetAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_cidrNotation_allowed24_clientIp_block() + public void Should_cidrNotation_allowed24_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.5")[0]; this.Given(x => x.GivenCidr24AllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_cidrNotation_allowed24_clientIp_not_block() + public void Should_cidrNotation_allowed24_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; this.Given(x => x.GivenCidr24AllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_cidrNotation_allowed29_clientIp_block() + public void Should_cidrNotation_allowed29_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; this.Given(x => x.GivenCidr29AllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_cidrNotation_blocked24_clientIp_block() + public void Should_cidrNotation_blocked24_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; this.Given(x => x.GivenCidr24BlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_cidrNotation_blocked24_clientIp_not_block() + public void Should_cidrNotation_blocked24_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.1")[0]; this.Given(x => x.GivenCidr24BlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_range_allowed_clientIp_block() + public void Should_range_allowed_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; this.Given(x => x.GivenRangeAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_range_allowed_clientIp_not_block() + public void Should_range_allowed_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.8")[0]; this.Given(x => x.GivenRangeAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_range_blocked_clientIp_block() + public void Should_range_blocked_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; this.Given(x => x.GivenRangeBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_range_blocked_clientIp_not_block() + public void Should_range_blocked_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; this.Given(x => x.GivenRangeBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_shortRange_allowed_clientIp_block() + public void Should_shortRange_allowed_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; this.Given(x => x.GivenShortRangeAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_shortRange_allowed_clientIp_not_block() + public void Should_shortRange_allowed_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.8")[0]; this.Given(x => x.GivenShortRangeAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_shortRange_blocked_clientIp_block() + public void Should_shortRange_blocked_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; this.Given(x => x.GivenShortRangeBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_shortRange_blocked_clientIp_not_block() + public void Should_shortRange_blocked_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; this.Given(x => x.GivenShortRangeBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_ipSubnet_allowed_clientIp_block() + public void Should_ipSubnet_allowed_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.15")[0]; this.Given(x => x.GivenIpSubnetAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_ipSubnet_allowed_clientIp_not_block() + public void Should_ipSubnet_allowed_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; this.Given(x => x.GivenIpSubnetAllowedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_ipSubnet_blocked_clientIp_block() + public void Should_ipSubnet_blocked_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; this.Given(x => x.GivenIpSubnetBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_ipSubnet_blocked_clientIp_not_block() + public void Should_ipSubnet_blocked_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.1")[0]; this.Given(x => x.GivenIpSubnetBlockedIP()) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_exludeAllowedFromBlocked_moreAllowed_clientIp_block() + public void Should_exludeAllowedFromBlocked_moreAllowed_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.150")[0]; this.Given(x => x.GivenIpMoreAllowedThanBlocked(false)) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_exludeAllowedFromBlocked_moreAllowed_clientIp_not_block() + public void Should_exludeAllowedFromBlocked_moreAllowed_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.150")[0]; this.Given(x => x.GivenIpMoreAllowedThanBlocked(true)) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_exludeAllowedFromBlocked_moreBlocked_clientIp_block() + public void Should_exludeAllowedFromBlocked_moreBlocked_clientIp_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; this.Given(x => x.GivenIpMoreBlockedThanAllowed(false)) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenNotSecurityPassing()); } [Fact] - public void should_exludeAllowedFromBlocked_moreBlocked_clientIp_not_block() + public void Should_exludeAllowedFromBlocked_moreBlocked_clientIp_not_block() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; this.Given(x => x.GivenIpMoreBlockedThanAllowed(true)) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } [Fact] - public void should_route_config_overrides_global_config() + public void Should_route_config_overrides_global_config() { _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; this.Given(x => x.GivenRouteConfigAndGlobalConfig(false)) .Given(x => x.GivenSetDownstreamRoute()) .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()) - .BDDfy(); + .Then(x => x.ThenSecurityPassing()); } private void GivenSetAllowedIP() From 05914fa64fb83fcba74c25c94e22ed7f5de7e85d Mon Sep 17 00:00:00 2001 From: Fabrizio Mancin Date: Wed, 20 Nov 2024 17:11:15 +0100 Subject: [PATCH 14/15] FileSecurityOptions move IsFullFilled inside the class --- src/Ocelot/Configuration/File/FileSecurityOptions.cs | 3 +++ .../Configuration/File/FileSecurityOptionsExtensions.cs | 8 -------- 2 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs diff --git a/src/Ocelot/Configuration/File/FileSecurityOptions.cs b/src/Ocelot/Configuration/File/FileSecurityOptions.cs index ffd595cb4..b1a5b68f1 100644 --- a/src/Ocelot/Configuration/File/FileSecurityOptions.cs +++ b/src/Ocelot/Configuration/File/FileSecurityOptions.cs @@ -50,5 +50,8 @@ public FileSecurityOptions(IEnumerable allowedIPs = null, IEnumerable public bool ExcludeAllowedFromBlocked { get; set; } + + public bool IsFullFilled() + => this.IPAllowedList.Count > 0 || this.IPBlockedList.Count > 0; } } diff --git a/src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs b/src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs deleted file mode 100644 index aa9dc8f17..000000000 --- a/src/Ocelot/Configuration/File/FileSecurityOptionsExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ocelot.Configuration.File -{ - internal static class FileSecurityOptionsExtensions - { - internal static bool IsFullFilled(this FileSecurityOptions fileSecurityOptions) - => fileSecurityOptions.IPAllowedList.Count > 0 || fileSecurityOptions.IPBlockedList.Count > 0; - } -} From a06a4674fb0986e6cbf7f9b8a1fb4fe1401f144c Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Wed, 20 Nov 2024 22:01:38 +0300 Subject: [PATCH 15/15] Code review by @raman-m --- .../Creator/ISecurityOptionsCreator.cs | 9 +- .../Creator/SecurityOptionsCreator.cs | 42 +- .../Configuration/File/FileSecurityOptions.cs | 82 +- .../Security/IPSecurity/IPSecurityPolicy.cs | 48 +- src/Ocelot/Security/ISecurityPolicy.cs | 10 +- .../Security/Middleware/SecurityMiddleware.cs | 5 +- .../Security/SecurityOptionsTests.cs | 70 +- .../SecurityOptionsCreatorTests.cs | 161 ++-- .../Security/IPSecurityPolicyTests.cs | 742 +++++++++--------- .../Security/SecurityMiddlewareTests.cs | 129 +-- 10 files changed, 627 insertions(+), 671 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs index 16cea24a6..f85acf510 100644 --- a/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs @@ -1,9 +1,8 @@ using Ocelot.Configuration.File; -namespace Ocelot.Configuration.Creator +namespace Ocelot.Configuration.Creator; + +public interface ISecurityOptionsCreator { - public interface ISecurityOptionsCreator - { - SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration); - } + SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration global); } diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs index 09b38897e..8e53e963a 100644 --- a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -1,36 +1,28 @@ using NetTools; // using Ocelot.Configuration.File; -namespace Ocelot.Configuration.Creator +namespace Ocelot.Configuration.Creator; + +public class SecurityOptionsCreator : ISecurityOptionsCreator { - public class SecurityOptionsCreator : ISecurityOptionsCreator + public SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration global) { - public SecurityOptions Create(FileSecurityOptions securityOptions, FileGlobalConfiguration globalConfiguration) - => Create(securityOptions.IsFullFilled() ? securityOptions : globalConfiguration.SecurityOptions); + var options = securityOptions.IsEmpty() ? global.SecurityOptions : securityOptions; + var allowedIPs = options.IPAllowedList.SelectMany(Parse) + .ToArray(); + var blockedIPs = options.IPBlockedList.SelectMany(Parse) + .Except(options.ExcludeAllowedFromBlocked ? allowedIPs : Enumerable.Empty()) + .ToArray(); + return new(allowedIPs, blockedIPs); + } - private static SecurityOptions Create(FileSecurityOptions securityOptions) + private static string[] Parse(string ipValue) + { + if (IPAddressRange.TryParse(ipValue, out var range)) { - var ipAllowedList = SetIpAddressList(securityOptions.IPAllowedList); - var ipBlockedList = SetIpAddressList(securityOptions.IPBlockedList); - - if (securityOptions.ExcludeAllowedFromBlocked) - { - ipBlockedList = ipBlockedList.Except(ipAllowedList).ToArray(); - } - - return new SecurityOptions(ipAllowedList, ipBlockedList); + return range.Select(ip => ip.ToString()).ToArray(); } - private static string[] SetIpAddressList(List ipValues) => ipValues.SelectMany(Parse).ToArray(); - - private static string[] Parse(string ipValue) - { - if (IPAddressRange.TryParse(ipValue, out var range)) - { - return range.Select(ip => ip.ToString()).ToArray(); - } - - return Array.Empty(); - } + return Array.Empty(); } } diff --git a/src/Ocelot/Configuration/File/FileSecurityOptions.cs b/src/Ocelot/Configuration/File/FileSecurityOptions.cs index b1a5b68f1..e3aa1fdd0 100644 --- a/src/Ocelot/Configuration/File/FileSecurityOptions.cs +++ b/src/Ocelot/Configuration/File/FileSecurityOptions.cs @@ -1,57 +1,51 @@ -namespace Ocelot.Configuration.File +namespace Ocelot.Configuration.File; + +public class FileSecurityOptions { - public class FileSecurityOptions + public FileSecurityOptions() { - public FileSecurityOptions() - { - IPAllowedList = new List(); - IPBlockedList = new List(); - ExcludeAllowedFromBlocked = false; - } + IPAllowedList = new(); + IPBlockedList = new(); + ExcludeAllowedFromBlocked = false; + } - public FileSecurityOptions(FileSecurityOptions from) - { - IPAllowedList = new(from.IPAllowedList); - IPBlockedList = new(from.IPBlockedList); - ExcludeAllowedFromBlocked = from.ExcludeAllowedFromBlocked; - } + public FileSecurityOptions(FileSecurityOptions from) + { + IPAllowedList = new(from.IPAllowedList); + IPBlockedList = new(from.IPBlockedList); + ExcludeAllowedFromBlocked = from.ExcludeAllowedFromBlocked; + } - public FileSecurityOptions(string allowedIPs = null, string blockedIPs = null, bool? excludeAllowedFromBlocked = null) - : this() + public FileSecurityOptions(string allowedIPs = null, string blockedIPs = null, bool? excludeAllowedFromBlocked = null) + : this() + { + if (!string.IsNullOrEmpty(allowedIPs)) { - if (!string.IsNullOrEmpty(allowedIPs)) - { - IPAllowedList.Add(allowedIPs); - } - - if (!string.IsNullOrEmpty(blockedIPs)) - { - IPBlockedList.Add(blockedIPs); - } - - ExcludeAllowedFromBlocked = excludeAllowedFromBlocked ?? false; + IPAllowedList.Add(allowedIPs); } - public FileSecurityOptions(IEnumerable allowedIPs = null, IEnumerable blockedIPs = null, bool? excludeAllowedFromBlocked = null) - : this() + if (!string.IsNullOrEmpty(blockedIPs)) { - IPAllowedList.AddRange(allowedIPs ?? Enumerable.Empty()); - IPBlockedList.AddRange(blockedIPs ?? Enumerable.Empty()); - ExcludeAllowedFromBlocked = excludeAllowedFromBlocked ?? false; + IPBlockedList.Add(blockedIPs); } - public List IPAllowedList { get; set; } - public List IPBlockedList { get; set; } - - /// - /// Provides the ability to specify a wide range of blocked IP addresses and allow a subrange of IP addresses. - /// - /// - /// Default value: false. - /// - public bool ExcludeAllowedFromBlocked { get; set; } + ExcludeAllowedFromBlocked = excludeAllowedFromBlocked ?? false; + } - public bool IsFullFilled() - => this.IPAllowedList.Count > 0 || this.IPBlockedList.Count > 0; + public FileSecurityOptions(IEnumerable allowedIPs = null, IEnumerable blockedIPs = null, bool? excludeAllowedFromBlocked = null) + : this() + { + IPAllowedList.AddRange(allowedIPs ?? Enumerable.Empty()); + IPBlockedList.AddRange(blockedIPs ?? Enumerable.Empty()); + ExcludeAllowedFromBlocked = excludeAllowedFromBlocked ?? false; } + + public List IPAllowedList { get; set; } + public List IPBlockedList { get; set; } + + /// Provides the ability to specify a wide range of blocked IP addresses and allow a subrange of IP addresses. + /// A value, defaults to . + public bool ExcludeAllowedFromBlocked { get; set; } + + public bool IsEmpty() => IPAllowedList.Count == 0 && IPBlockedList.Count == 0; } diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs index f06510778..8437af39e 100644 --- a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -3,38 +3,40 @@ using Ocelot.Middleware; using Ocelot.Responses; -namespace Ocelot.Security.IPSecurity +namespace Ocelot.Security.IPSecurity; + +public class IPSecurityPolicy : ISecurityPolicy { - public class IPSecurityPolicy : ISecurityPolicy + public Response Security(DownstreamRoute downstreamRoute, HttpContext context) { - public async Task Security(DownstreamRoute downstreamRoute, HttpContext httpContext) + var clientIp = context.Connection.RemoteIpAddress; + var options = downstreamRoute.SecurityOptions; + if (options == null || clientIp == null) { - var clientIp = httpContext.Connection.RemoteIpAddress; - var securityOptions = downstreamRoute.SecurityOptions; - if (securityOptions == null) - { - return new OkResponse(); - } + return new OkResponse(); + } - if (securityOptions.IPBlockedList != null && securityOptions.IPBlockedList.Count > 0 && clientIp != null) + if (options.IPBlockedList?.Count > 0) + { + if (options.IPBlockedList.Contains(clientIp.ToString())) { - if (securityOptions.IPBlockedList.Contains(clientIp.ToString())) - { - var error = new UnauthenticatedError($" This request rejects access to {clientIp} IP"); - return new ErrorResponse(error); - } + var error = new UnauthenticatedError($"This request rejects access to {clientIp} IP"); + return new ErrorResponse(error); } + } - if (securityOptions.IPAllowedList != null && securityOptions.IPAllowedList.Count > 0 && clientIp != null) + if (options.IPAllowedList?.Count > 0) + { + if (!options.IPAllowedList.Contains(clientIp.ToString())) { - if (!securityOptions.IPAllowedList.Contains(clientIp.ToString())) - { - var error = new UnauthenticatedError($"{clientIp} does not allow access, the request is invalid"); - return new ErrorResponse(error); - } + var error = new UnauthenticatedError($"{clientIp} does not allow access, the request is invalid"); + return new ErrorResponse(error); } - - return await Task.FromResult(new OkResponse()); } + + return new OkResponse(); } + + public Task SecurityAsync(DownstreamRoute downstreamRoute, HttpContext context) + => Task.Run(() => Security(downstreamRoute, context)); } diff --git a/src/Ocelot/Security/ISecurityPolicy.cs b/src/Ocelot/Security/ISecurityPolicy.cs index 783d1b5ac..b4eb5bf75 100644 --- a/src/Ocelot/Security/ISecurityPolicy.cs +++ b/src/Ocelot/Security/ISecurityPolicy.cs @@ -2,10 +2,10 @@ using Ocelot.Configuration; using Ocelot.Responses; -namespace Ocelot.Security +namespace Ocelot.Security; + +public interface ISecurityPolicy { - public interface ISecurityPolicy - { - Task Security(DownstreamRoute downstreamRoute, HttpContext httpContext); - } + Response Security(DownstreamRoute downstreamRoute, HttpContext context); + Task SecurityAsync(DownstreamRoute downstreamRoute, HttpContext context); } diff --git a/src/Ocelot/Security/Middleware/SecurityMiddleware.cs b/src/Ocelot/Security/Middleware/SecurityMiddleware.cs index 068ff2ea0..ebbd80248 100644 --- a/src/Ocelot/Security/Middleware/SecurityMiddleware.cs +++ b/src/Ocelot/Security/Middleware/SecurityMiddleware.cs @@ -11,8 +11,7 @@ public class SecurityMiddleware : OcelotMiddleware public SecurityMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IEnumerable securityPolicies - ) + IEnumerable securityPolicies) : base(loggerFactory.CreateLogger()) { _securityPolicies = securityPolicies; @@ -27,7 +26,7 @@ public async Task Invoke(HttpContext httpContext) { foreach (var policy in _securityPolicies) { - var result = await policy.Security(downstreamRoute, httpContext); + var result = policy.Security(downstreamRoute, httpContext); if (!result.IsError) { continue; diff --git a/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs b/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs index 052428190..7a33207b7 100644 --- a/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs +++ b/test/Ocelot.AcceptanceTests/Security/SecurityOptionsTests.cs @@ -19,14 +19,15 @@ public override void Dispose() } [Fact] + [Trait("Feat", "2170")] public void Should_call_with_allowed_ip_in_global_config() { var port = PortFinder.GetRandomPort(); var ip = Dns.GetHostAddresses("192.168.1.35")[0]; var route = GivenRoute(port, "/myPath", "/worldPath"); - var configuration = GivenConfigurationWithSecurityOptions(route); + var configuration = GivenGlobalConfiguration(route, "192.168.1.30-50", "192.168.1.1-100"); - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, ip)) .And(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenOcelotIsRunning()) .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) @@ -34,14 +35,15 @@ public void Should_call_with_allowed_ip_in_global_config() } [Fact] + [Trait("Feat", "2170")] public void Should_block_call_with_blocked_ip_in_global_config() { var port = PortFinder.GetRandomPort(); var ip = Dns.GetHostAddresses("192.168.1.55")[0]; var route = GivenRoute(port, "/myPath", "/worldPath"); - var configuration = GivenConfigurationWithSecurityOptions(route); + var configuration = GivenGlobalConfiguration(route, "192.168.1.30-50", "192.168.1.1-100"); - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, ip)) .And(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenOcelotIsRunning()) .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) @@ -55,13 +57,12 @@ public void Should_call_with_allowed_ip_in_route_config() var ip = Dns.GetHostAddresses("192.168.1.1")[0]; var securityConfig = new FileSecurityOptions { - IPAllowedList = new List { "192.168.1.1" }, + IPAllowedList = new() { "192.168.1.1" }, }; - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); var configuration = GivenConfiguration(route); - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, ip)) .And(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenOcelotIsRunning()) .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) @@ -75,13 +76,12 @@ public void Should_block_call_with_blocked_ip_in_route_config() var ip = Dns.GetHostAddresses("192.168.1.1")[0]; var securityConfig = new FileSecurityOptions { - IPBlockedList = new List { "192.168.1.1" }, + IPBlockedList = new() { "192.168.1.1" }, }; - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); var configuration = GivenConfiguration(route); - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, ip)) .And(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenOcelotIsRunning()) .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) @@ -89,74 +89,74 @@ public void Should_block_call_with_blocked_ip_in_route_config() } [Fact] + [Trait("Feat", "2170")] public void Should_call_with_allowed_ip_in_route_config_and_blocked_ip_in_global_config() { var port = PortFinder.GetRandomPort(); var ip = Dns.GetHostAddresses("192.168.1.55")[0]; var securityConfig = new FileSecurityOptions { - IPAllowedList = new List { "192.168.1.55" }, + IPAllowedList = new() { "192.168.1.55" }, }; - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); - var configuration = GivenConfigurationWithSecurityOptions(route); + var configuration = GivenGlobalConfiguration(route, "192.168.1.30-50", "192.168.1.1-100"); - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, ip)) .And(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenOcelotIsRunning()) .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)); + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .Then(x => ThenTheResponseBodyShouldBe("Hello from Fabrizio")); } [Fact] + [Trait("Feat", "2170")] public void Should_block_call_with_blocked_ip_in_route_config_and_allowed_ip_in_global_config() { var port = PortFinder.GetRandomPort(); var ip = Dns.GetHostAddresses("192.168.1.35")[0]; var securityConfig = new FileSecurityOptions { - IPBlockedList = new List { "192.168.1.35" }, + IPBlockedList = new() { "192.168.1.35" }, }; - var route = GivenRoute(port, "/myPath", "/worldPath", securityConfig); - var configuration = GivenConfigurationWithSecurityOptions(route); + var configuration = GivenGlobalConfiguration(route, "192.168.1.30-50", "192.168.1.1-100"); - this.Given(x => x.GivenThereIsAServiceRunningOn(DownstreamUrl(port), ip)) + this.Given(x => x.GivenThereIsAServiceRunningOn(port, ip)) .And(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenOcelotIsRunning()) .When(x => WhenIGetUrlOnTheApiGateway("/worldPath")) .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)); } - private void GivenThereIsAServiceRunningOn(string url, IPAddress ipAddess) + private void GivenThereIsAServiceRunningOn(int port, IPAddress ipAddess) { + string url = DownstreamUrl(port); _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => { context.Connection.RemoteIpAddress = ipAddess; context.Response.StatusCode = (int)HttpStatusCode.OK; - await context.Response.WriteAsync("a valida response body"); + await context.Response.WriteAsync("Hello from Fabrizio"); }); } - private static FileConfiguration GivenConfigurationWithSecurityOptions(params FileRoute[] routes) + private static FileConfiguration GivenGlobalConfiguration(FileRoute route, string allowed, string blocked, bool exclude = true) { - var config = GivenConfiguration(routes); + var config = GivenConfiguration(route); config.GlobalConfiguration.SecurityOptions = new FileSecurityOptions { - IPAllowedList = new List { "192.168.1.30-50" }, - IPBlockedList = new List { "192.168.1.1-100" }, - ExcludeAllowedFromBlocked = true + IPAllowedList = new() { allowed }, + IPBlockedList = new() { blocked }, + ExcludeAllowedFromBlocked = exclude, }; - return config; } - private FileRoute GivenRoute(int port, string downstream, string upstream, FileSecurityOptions fileSecurityOptions = null) - => new() - { - DownstreamPathTemplate = downstream, - UpstreamPathTemplate = upstream, - UpstreamHttpMethod = new List { "Get" }, - SecurityOptions = fileSecurityOptions ?? new FileSecurityOptions(), - }; + private static FileRoute GivenRoute(int port, string downstream, string upstream, FileSecurityOptions fileSecurityOptions = null) => new() + { + DownstreamPathTemplate = downstream, + UpstreamPathTemplate = upstream, + UpstreamHttpMethod = new() { HttpMethods.Get }, + SecurityOptions = fileSecurityOptions ?? new(), + }; } diff --git a/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs index 1cde2b724..2a223c78f 100644 --- a/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs @@ -2,117 +2,98 @@ using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration; + +public sealed class SecurityOptionsCreatorTests : UnitTest { - public class SecurityOptionsCreatorTests : UnitTest - { - private FileSecurityOptions _fileSecurityOptions; - private SecurityOptions _result; - private FileGlobalConfiguration _globalConfig; - private readonly ISecurityOptionsCreator _creator; + private readonly SecurityOptionsCreator _creator = new(); - public SecurityOptionsCreatorTests() + [Fact] + public void Should_create_route_security_config() + { + // Arrange + var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; + var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; + var securityOptions = new FileSecurityOptions { - _creator = new SecurityOptionsCreator(); - } + IPAllowedList = ipAllowedList, + IPBlockedList = ipBlockedList, + }; + var expected = new SecurityOptions(ipAllowedList, ipBlockedList); + var globalConfig = new FileGlobalConfiguration(); - [Fact] - public void Should_create_route_security_config() + // Act + var actual = _creator.Create(securityOptions, globalConfig); + + // Assert + ThenTheResultIs(actual, expected); + } + + [Fact] + [Trait("Feat", "2170")] + public void Should_create_global_security_config() + { + // Arrange + var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; + var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; + var globalConfig = new FileGlobalConfiguration { - var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; - var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; - var securityOptions = new FileSecurityOptions + SecurityOptions = new() { IPAllowedList = ipAllowedList, IPBlockedList = ipBlockedList, - }; + }, + }; + var expected = new SecurityOptions(ipAllowedList, ipBlockedList); - var expected = new SecurityOptions(ipAllowedList, ipBlockedList); + // Act + var actual = _creator.Create(new(), globalConfig); - this.Given(x => x.GivenThe(new FileGlobalConfiguration())) - .Given(x => x.GivenThe(securityOptions)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheResultIs(expected)); - } + // Assert + ThenTheResultIs(actual, expected); + } - [Fact] - public void Should_create_global_security_config() + [Fact] + [Trait("Feat", "2170")] + public void Should_create_global_route_security_config() + { + // Arrange + var routeIpAllowedList = new List { "127.0.0.1", "192.168.1.1" }; + var routeIpBlockedList = new List { "127.0.0.1", "192.168.1.1" }; + var securityOptions = new FileSecurityOptions { - var ipAllowedList = new List { "127.0.0.1", "192.168.1.1" }; - var ipBlockedList = new List { "127.0.0.1", "192.168.1.1" }; - var globalConfig = new FileGlobalConfiguration - { - SecurityOptions = new FileSecurityOptions - { - IPAllowedList = ipAllowedList, - IPBlockedList = ipBlockedList, - }, - }; - - var expected = new SecurityOptions(ipAllowedList, ipBlockedList); - - this.Given(x => x.GivenThe(globalConfig)) - .Given(x => x.GivenThe(new FileSecurityOptions())) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheResultIs(expected)); - } - - [Fact] - public void Should_create_global_route_security_config() + IPAllowedList = routeIpAllowedList, + IPBlockedList = routeIpBlockedList, + }; + var globalIpAllowedList = new List { "127.0.0.2", "192.168.1.2" }; + var globalIpBlockedList = new List { "127.0.0.2", "192.168.1.2" }; + var globalConfig = new FileGlobalConfiguration { - var routeIpAllowedList = new List { "127.0.0.1", "192.168.1.1" }; - var routeIpBlockedList = new List { "127.0.0.1", "192.168.1.1" }; - var securityOptions = new FileSecurityOptions - { - IPAllowedList = routeIpAllowedList, - IPBlockedList = routeIpBlockedList, - }; - - var globalIpAllowedList = new List { "127.0.0.2", "192.168.1.2" }; - var globalIpBlockedList = new List { "127.0.0.2", "192.168.1.2" }; - var globalConfig = new FileGlobalConfiguration + SecurityOptions = new FileSecurityOptions { - SecurityOptions = new FileSecurityOptions - { - IPAllowedList = globalIpAllowedList, - IPBlockedList = globalIpBlockedList, - }, - }; + IPAllowedList = globalIpAllowedList, + IPBlockedList = globalIpBlockedList, + }, + }; + var expected = new SecurityOptions(routeIpAllowedList, routeIpBlockedList); - var expected = new SecurityOptions(routeIpAllowedList, routeIpBlockedList); - - this.Given(x => x.GivenThe(globalConfig)) - .Given(x => x.GivenThe(securityOptions)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheResultIs(expected)); - } - - private void GivenThe(FileSecurityOptions fileSecurityOptions) - { - _fileSecurityOptions = fileSecurityOptions; - } + // Act + var actual = _creator.Create(securityOptions, globalConfig); - private void GivenThe(FileGlobalConfiguration globalConfiguration) - { - _globalConfig = globalConfiguration; - } + // Assert + ThenTheResultIs(actual, expected); + } - private void WhenICreate() + private static void ThenTheResultIs(SecurityOptions actual, SecurityOptions expected) + { + for (var i = 0; i < expected.IPAllowedList.Count; i++) { - _result = _creator.Create(_fileSecurityOptions, _globalConfig); + actual.IPAllowedList[i].ShouldBe(expected.IPAllowedList[i]); } - private void ThenTheResultIs(SecurityOptions expected) + for (var i = 0; i < expected.IPBlockedList.Count; i++) { - for (var i = 0; i < expected.IPAllowedList.Count; i++) - { - _result.IPAllowedList[i].ShouldBe(expected.IPAllowedList[i]); - } - - for (var i = 0; i < expected.IPBlockedList.Count; i++) - { - _result.IPBlockedList[i].ShouldBe(expected.IPBlockedList[i]); - } + actual.IPBlockedList[i].ShouldBe(expected.IPBlockedList[i]); } } } diff --git a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs index e8aa3b5f3..bd7b9cd3d 100644 --- a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs +++ b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs @@ -8,403 +8,391 @@ using Ocelot.Responses; using Ocelot.Security.IPSecurity; -namespace Ocelot.UnitTests.Security +namespace Ocelot.UnitTests.Security; + +public sealed class IPSecurityPolicyTests : UnitTest { - public class IPSecurityPolicyTests : UnitTest + private readonly DownstreamRouteBuilder _downstreamRouteBuilder; + private readonly IPSecurityPolicy _policy; + private readonly HttpContext _context; + private readonly SecurityOptionsCreator _securityOptionsCreator; + private static readonly FileGlobalConfiguration Empty = new(); + + public IPSecurityPolicyTests() { - private readonly DownstreamRouteBuilder _downstreamRouteBuilder; - private readonly IPSecurityPolicy _ipSecurityPolicy; - private Response response; - private readonly HttpContext _httpContext; - private readonly SecurityOptionsCreator _securityOptionsCreator; - private readonly FileGlobalConfiguration _emptyFileGlobalConfiguration; + _context = new DefaultHttpContext(); + _context.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + _downstreamRouteBuilder = new DownstreamRouteBuilder(); + _policy = new IPSecurityPolicy(); + _securityOptionsCreator = new SecurityOptionsCreator(); + } - public IPSecurityPolicyTests() - { - _httpContext = new DefaultHttpContext(); - _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; - _downstreamRouteBuilder = new DownstreamRouteBuilder(); - _ipSecurityPolicy = new IPSecurityPolicy(); - _securityOptionsCreator = new SecurityOptionsCreator(); - _emptyFileGlobalConfiguration = new FileGlobalConfiguration(); - } - - [Fact] - public void Should_No_blocked_Ip_and_allowed_Ip() - { - this.Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } + [Fact] + public void Should_No_blocked_Ip_and_allowed_Ip() + { + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - [Fact] - public void Should_blockedIp_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; - this.Given(x => x.GivenSetBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_blockedIp_clientIp_Not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; - this.Given(x => x.GivenSetBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_allowedIp_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; - this.Given(x => x.GivenSetAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_allowedIp_clientIp_Not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; - this.Given(x => x.GivenSetAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_cidrNotation_allowed24_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.5")[0]; - this.Given(x => x.GivenCidr24AllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_cidrNotation_allowed24_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; - this.Given(x => x.GivenCidr24AllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_cidrNotation_allowed29_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; - this.Given(x => x.GivenCidr29AllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_cidrNotation_blocked24_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; - this.Given(x => x.GivenCidr24BlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_cidrNotation_blocked24_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.1")[0]; - this.Given(x => x.GivenCidr24BlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_range_allowed_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; - this.Given(x => x.GivenRangeAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_range_allowed_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.8")[0]; - this.Given(x => x.GivenRangeAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_range_blocked_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; - this.Given(x => x.GivenRangeBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_range_blocked_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; - this.Given(x => x.GivenRangeBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_shortRange_allowed_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; - this.Given(x => x.GivenShortRangeAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_shortRange_allowed_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.8")[0]; - this.Given(x => x.GivenShortRangeAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_shortRange_blocked_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; - this.Given(x => x.GivenShortRangeBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_shortRange_blocked_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; - this.Given(x => x.GivenShortRangeBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_ipSubnet_allowed_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.15")[0]; - this.Given(x => x.GivenIpSubnetAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_ipSubnet_allowed_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; - this.Given(x => x.GivenIpSubnetAllowedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_ipSubnet_blocked_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; - this.Given(x => x.GivenIpSubnetBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_ipSubnet_blocked_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.1")[0]; - this.Given(x => x.GivenIpSubnetBlockedIP()) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_exludeAllowedFromBlocked_moreAllowed_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.150")[0]; - this.Given(x => x.GivenIpMoreAllowedThanBlocked(false)) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_exludeAllowedFromBlocked_moreAllowed_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.150")[0]; - this.Given(x => x.GivenIpMoreAllowedThanBlocked(true)) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_exludeAllowedFromBlocked_moreBlocked_clientIp_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; - this.Given(x => x.GivenIpMoreBlockedThanAllowed(false)) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenNotSecurityPassing()); - } - - [Fact] - public void Should_exludeAllowedFromBlocked_moreBlocked_clientIp_not_block() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; - this.Given(x => x.GivenIpMoreBlockedThanAllowed(true)) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - [Fact] - public void Should_route_config_overrides_global_config() - { - _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; - this.Given(x => x.GivenRouteConfigAndGlobalConfig(false)) - .Given(x => x.GivenSetDownstreamRoute()) - .When(x => x.WhenTheSecurityPolicy()) - .Then(x => x.ThenSecurityPassing()); - } - - private void GivenSetAllowedIP() - { - _downstreamRouteBuilder.WithSecurityOptions(new SecurityOptions("192.168.1.1")); - } + [Fact] + public void Should_blockedIp_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + GivenSetBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenSetBlockedIP() - { - _downstreamRouteBuilder.WithSecurityOptions(new SecurityOptions(blocked: "192.168.1.1")); - } + [Fact] + public void Should_blockedIp_clientIp_Not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; + GivenSetBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - private void GivenSetDownstreamRoute() - { - _httpContext.Items.UpsertDownstreamRoute(_downstreamRouteBuilder.Build()); - } + [Fact] + public void Should_allowedIp_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + GivenSetAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - private void GivenCidr24AllowedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/24"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_allowedIp_clientIp_Not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; + GivenSetAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenCidr29AllowedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/29"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_cidrNotation_allowed24_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.5")[0]; + GivenCidr24AllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenCidr24BlockedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/24"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_cidrNotation_allowed24_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; + GivenCidr24AllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - private void GivenRangeAllowedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-192.168.1.10"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_cidrNotation_allowed29_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; + GivenCidr29AllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenRangeBlockedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-192.168.1.10"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_cidrNotation_blocked24_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + GivenCidr24BlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenShortRangeAllowedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-10"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_cidrNotation_blocked24_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.1")[0]; + GivenCidr24BlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - private void GivenShortRangeBlockedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-10"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_range_allowed_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; + GivenRangeAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenIpSubnetAllowedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/255.255.255.0"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_range_allowed_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.8")[0]; + GivenRangeAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - private void GivenIpSubnetBlockedIP() - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/255.255.255.0"), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_range_blocked_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; + GivenRangeBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenIpMoreAllowedThanBlocked(bool excludeAllowedInBlocked) - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.0.0/255.255.0.0", "192.168.1.100-200", excludeAllowedInBlocked), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_range_blocked_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; + GivenRangeBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - private void GivenIpMoreBlockedThanAllowed(bool excludeAllowedInBlocked) - { - var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.10-20", "192.168.1.0/23", excludeAllowedInBlocked), _emptyFileGlobalConfiguration); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } + [Fact] + public void Should_shortRange_allowed_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; + GivenShortRangeAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void GivenRouteConfigAndGlobalConfig(bool excludeAllowedInBlocked) - { - var globalConfig = new FileGlobalConfiguration - { - SecurityOptions = new FileSecurityOptions("192.168.1.30-50", "192.168.1.1-100", true), - }; - - var localConfig = new FileSecurityOptions("192.168.1.10", "", excludeAllowedInBlocked); - - var securityOptions = _securityOptionsCreator.Create(localConfig, globalConfig); - _downstreamRouteBuilder.WithSecurityOptions(securityOptions); - } - - private async Task WhenTheSecurityPolicy() - { - response = await _ipSecurityPolicy.Security(_httpContext.Items.DownstreamRoute(), _httpContext); - } + [Fact] + public void Should_shortRange_allowed_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.8")[0]; + GivenShortRangeAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } - private void ThenSecurityPassing() - { - Assert.False(response.IsError); - } + [Fact] + public void Should_shortRange_blocked_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.5")[0]; + GivenShortRangeBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } + + [Fact] + public void Should_shortRange_blocked_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; + GivenShortRangeBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } + + [Fact] + public void Should_ipSubnet_allowed_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.15")[0]; + GivenIpSubnetAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } + + [Fact] + public void Should_ipSubnet_allowed_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; + GivenIpSubnetAllowedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } + + [Fact] + public void Should_ipSubnet_blocked_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.15")[0]; + GivenIpSubnetBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } + + [Fact] + public void Should_ipSubnet_blocked_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.10.1")[0]; + GivenIpSubnetBlockedIP(); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } + + [Fact] + public void Should_exludeAllowedFromBlocked_moreAllowed_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.150")[0]; + GivenIpMoreAllowedThanBlocked(false); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } - private void ThenNotSecurityPassing() + [Fact] + public void Should_exludeAllowedFromBlocked_moreAllowed_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.150")[0]; + GivenIpMoreAllowedThanBlocked(true); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } + + [Fact] + public void Should_exludeAllowedFromBlocked_moreBlocked_clientIp_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; + GivenIpMoreBlockedThanAllowed(false); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.True(actual.IsError); + } + + [Fact] + public void Should_exludeAllowedFromBlocked_moreBlocked_clientIp_not_block() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; + GivenIpMoreBlockedThanAllowed(true); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } + + [Fact] + [Trait("Feat", "2170")] + public void Should_route_config_overrides_global_config() + { + _context.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.10")[0]; + GivenRouteConfigAndGlobalConfig(false); + GivenSetDownstreamRoute(); + var actual = WhenTheSecurityPolicy(); + Assert.False(actual.IsError); + } + + private void GivenSetAllowedIP() + { + _downstreamRouteBuilder.WithSecurityOptions(new SecurityOptions("192.168.1.1")); + } + + private void GivenSetBlockedIP() + { + _downstreamRouteBuilder.WithSecurityOptions(new SecurityOptions(blocked: "192.168.1.1")); + } + + private void GivenSetDownstreamRoute() + { + _context.Items.UpsertDownstreamRoute(_downstreamRouteBuilder.Build()); + } + + private void GivenCidr24AllowedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/24"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenCidr29AllowedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/29"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenCidr24BlockedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/24"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenRangeAllowedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-192.168.1.10"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenRangeBlockedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-192.168.1.10"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenShortRangeAllowedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0-10"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenShortRangeBlockedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0-10"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenIpSubnetAllowedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.0/255.255.255.0"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenIpSubnetBlockedIP() + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions(blockedIPs: "192.168.1.0/255.255.255.0"), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenIpMoreAllowedThanBlocked(bool excludeAllowedInBlocked) + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.0.0/255.255.0.0", "192.168.1.100-200", excludeAllowedInBlocked), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenIpMoreBlockedThanAllowed(bool excludeAllowedInBlocked) + { + var securityOptions = _securityOptionsCreator.Create(new FileSecurityOptions("192.168.1.10-20", "192.168.1.0/23", excludeAllowedInBlocked), Empty); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private void GivenRouteConfigAndGlobalConfig(bool excludeAllowedInBlocked) + { + var globalConfig = new FileGlobalConfiguration { - Assert.True(response.IsError); - } + SecurityOptions = new FileSecurityOptions("192.168.1.30-50", "192.168.1.1-100", true), + }; + + var localConfig = new FileSecurityOptions("192.168.1.10", "", excludeAllowedInBlocked); + + var securityOptions = _securityOptionsCreator.Create(localConfig, globalConfig); + _downstreamRouteBuilder.WithSecurityOptions(securityOptions); + } + + private Response WhenTheSecurityPolicy() + { + return _policy.Security(_context.Items.DownstreamRoute(), _context); } } diff --git a/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs b/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs index 9f493391c..60818f015 100644 --- a/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs @@ -7,83 +7,84 @@ using Ocelot.Security; using Ocelot.Security.Middleware; -namespace Ocelot.UnitTests.Security +namespace Ocelot.UnitTests.Security; + +public sealed class SecurityMiddlewareTests : UnitTest { - public class SecurityMiddlewareTests : UnitTest - { - private readonly List> _securityPolicyList; - private readonly Mock _loggerFactory; - private readonly Mock _logger; - private readonly SecurityMiddleware _middleware; - private readonly RequestDelegate _next; - private readonly HttpContext _httpContext; + private readonly List> _securityPolicyList; + private readonly Mock _loggerFactory; + private readonly Mock _logger; + private readonly SecurityMiddleware _middleware; + private readonly RequestDelegate _next; + private readonly HttpContext _httpContext; - public SecurityMiddlewareTests() + public SecurityMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _securityPolicyList = new List> { - _httpContext = new DefaultHttpContext(); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _securityPolicyList = new List> - { - new(), - new(), - }; - _next = context => Task.CompletedTask; - _middleware = new SecurityMiddleware(_next, _loggerFactory.Object, _securityPolicyList.Select(f => f.Object).ToList()); - _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); - } + new(), + new(), + }; + _next = context => Task.CompletedTask; + _middleware = new SecurityMiddleware(_next, _loggerFactory.Object, _securityPolicyList.Select(f => f.Object).ToList()); + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); + } - [Fact] - public void Should_legal_request() - { - this.Given(x => x.GivenPassingSecurityVerification()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheRequestIsPassingSecurity()) - .BDDfy(); - } + [Fact] + public async Task Should_legal_request() + { + // Arrange + GivenPassingSecurityVerification(); + + // Act + await _middleware.Invoke(_httpContext); + + // Assert: security passed + _httpContext.Items.Errors().Count.ShouldBe(0); + } + + [Fact] + public async Task Should_verification_failed_request() + { + // Arrange + GivenNotPassingSecurityVerification(); - [Fact] - public void Should_verification_failed_request() + // Act + await _middleware.Invoke(_httpContext); + + // Assert: security not passed + _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); + } + + private void GivenPassingSecurityVerification() + { + foreach (var item in _securityPolicyList) { - this.Given(x => x.GivenNotPassingSecurityVerification()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheRequestIsNotPassingSecurity()) - .BDDfy(); + Response response = new OkResponse(); + item.Setup(x => x.Security(_httpContext.Items.DownstreamRoute(), _httpContext)).Returns(response); } + } - private void GivenPassingSecurityVerification() + private void GivenNotPassingSecurityVerification() + { + for (var i = 0; i < _securityPolicyList.Count; i++) { - foreach (var item in _securityPolicyList) + var item = _securityPolicyList[i]; + if (i == 0) { - Response response = new OkResponse(); - item.Setup(x => x.Security(_httpContext.Items.DownstreamRoute(), _httpContext)).Returns(Task.FromResult(response)); + Error error = new UnauthenticatedError("Not passing security verification"); + Response response = new ErrorResponse(error); + item.Setup(x => x.Security(_httpContext.Items.DownstreamRoute(), _httpContext)).Returns(response); } - } - - private void GivenNotPassingSecurityVerification() - { - for (var i = 0; i < _securityPolicyList.Count; i++) + else { - var item = _securityPolicyList[i]; - if (i == 0) - { - Error error = new UnauthenticatedError("Not passing security verification"); - Response response = new ErrorResponse(error); - item.Setup(x => x.Security(_httpContext.Items.DownstreamRoute(), _httpContext)).Returns(Task.FromResult(response)); - } - else - { - Response response = new OkResponse(); - item.Setup(x => x.Security(_httpContext.Items.DownstreamRoute(), _httpContext)).Returns(Task.FromResult(response)); - } + Response response = new OkResponse(); + item.Setup(x => x.Security(_httpContext.Items.DownstreamRoute(), _httpContext)).Returns(response); } } - - private Task WhenICallTheMiddleware() => _middleware.Invoke(_httpContext); - - private void ThenTheRequestIsPassingSecurity() => _httpContext.Items.Errors().Count.ShouldBe(0); - - private void ThenTheRequestIsNotPassingSecurity() => _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); } }