From d3b9edaefaf049a0e90366d5463aa4988f190441 Mon Sep 17 00:00:00 2001 From: Thomas <37584838+tomfp@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:04:37 +0100 Subject: [PATCH 1/2] Allow Client or IP rate limiting to be disabled, without removing configuration options --- .../Middleware/RateLimitMiddleware.cs | 2 +- .../Models/RateLimitOptions.cs | 5 + .../appsettings.EnabledFalse.json | 205 ++++++++++++++++++ .../appsettings.Regex.json | 2 + .../AspNetCoreRateLimit.Demo/appsettings.json | 2 + .../BaseClassFixture.cs | 20 ++ .../ClientRateLimitTests.cs | 25 +++ .../Enums/ClientType.cs | 3 +- .../IpRateLimitTests.cs | 24 ++ 9 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json diff --git a/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs b/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs index 3b6f63fc..33a2bff3 100644 --- a/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs +++ b/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs @@ -31,7 +31,7 @@ protected RateLimitMiddleware( public async Task Invoke(HttpContext context) { // check if rate limiting is enabled - if (_options == null) + if (_options == null || _options.RateLimitingEnabled == false) { await _next.Invoke(context); return; diff --git a/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs b/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs index 89d2cf73..29b8a9f0 100644 --- a/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs +++ b/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs @@ -7,6 +7,11 @@ namespace AspNetCoreRateLimit { public class RateLimitOptions { + /// + /// Gets or sets whether Rate Limiting is enabled + /// + public bool RateLimitingEnabled { get; set; } = true; // default for backward compatibility + public List GeneralRules { get; set; } public List EndpointWhitelist { get; set; } diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json new file mode 100644 index 00000000..b774037e --- /dev/null +++ b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json @@ -0,0 +1,205 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + + "IpRateLimiting": { + "EnableEndpointRateLimiting": false, + "EnableRegexRuleMatching": true, + "StackBlockedRequests": false, + "RealIpHeader": "X-Real-IP", + "ClientIdHeader": "X-ClientId", + "HttpStatusCode": 429, + "IpWhitelist": [ "::1/10", "192.168.0.0/24" ], + "EndpointWhitelist": [ "delete:/api/values", ":/api/clients", ":/api/ClientRateLimit", ":/api/IpRateLimit", "get:/" ], + "ClientWhitelist": [ "cl-key-1", "cl-key-2" ], + "QuotaExceededResponse": { + "Content": "{{ \"message\": \"Whoa! Calm down, cowboy!\", \"details\": \"Quota exceeded. Maximum allowed: {0} per {1}. Please try again in {2} second(s).\" }}", + "ContentType": "application/json" + }, + "GeneralRules": [ + { + "Endpoint": ".+", + "Period": "1s", + "Limit": 2 + }, + { + "Endpoint": ".+", + "Period": "1m", + "Limit": 5 + } + ] + }, + + "IpRateLimitPolicies": { + "IpRules": [ + { + "Ip": "84.247.85.224", + "Rules": [ + { + "Endpoint": ".+", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": ".+", + "Period": "1m", + "Limit": 2 + }, + { + "Endpoint": "post:/api/values", + "Period": "1m", + "Limit": 5 + } + ] + }, + { + "Ip": "84.247.85.225", + "Rules": [ + { + "Endpoint": ".+", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": ".+", + "Period": "1m", + "Limit": 5 + }, + { + "Endpoint": ".+", + "Period": "1h", + "Limit": 2 + } + ] + }, + { + "Ip": "84.247.85.226", + "Rules": [ + { + "Endpoint": ".+", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": ".+", + "Period": "1m", + "Limit": 5 + }, + { + "Endpoint": ".+", + "Period": "1d", + "Limit": 2 + } + ] + }, + { + "Ip": "84.247.85.231", + "Rules": [ + { + "Endpoint": ".+", + "Period": "1m", + "Limit": 0 + } + ] + }, + { + "Ip": "84.247.85.232", + "Rules": [ + { + "Endpoint": ".+", + "Period": "1m", + "Limit": 1, + "MonitorMode": true + } + ] + } + ] + }, + + "ClientRateLimiting": { + "EnableEndpointRateLimiting": false, + "EnableRegexRuleMatching": true, + "ClientIdHeader": "X-ClientId", + "HttpStatusCode": 429, + "EndpointWhitelist": [ "((post)|(put)|(get)|(delete)):/api/values", "delete:/api/clients" ], + "ClientWhitelist": [ "cl-key-a", "cl-key-b" ], + "GeneralRules": [ + { + "Endpoint": ".+", + "Period": "1s", + "Limit": 2 + }, + { + "Endpoint": ".+", + "Period": "1m", + "Limit": 5 + }, + { + "Endpoint": "post:/api/clients", + "Period": "5m", + "Limit": 3 + } + ] + }, + + "ClientRateLimitPolicies": { + "ClientRules": [ + { + "ClientId": "cl-key-1", + "Rules": [ + { + "Endpoint": ".+", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": "get:/api/clients", + "Period": "1m", + "Limit": 2 + }, + { + "Endpoint": "put:/api/clients", + "Period": "5m", + "Limit": 2 + } + ] + }, + { + "ClientId": "cl-key-2", + "Rules": [ + { + "Endpoint": ".+", + "Period": "1s", + "Limit": 10 + }, + { + "Endpoint": "get:/api/clients", + "Period": "1m", + "Limit": 0 + }, + { + "Endpoint": "post:/api/clients", + "Period": "5m", + "Limit": 50 + } + ] + }, + { + "ClientId": "cl-key-3", + "Rules": [ + { + "Endpoint": "post:/api/clients", + "Period": "1s", + "Limit": 3 + } + ] + } + ] + } +} diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json b/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json index 0a6b3597..edd1190c 100644 --- a/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json +++ b/test/AspNetCoreRateLimit.Demo/appsettings.Regex.json @@ -9,6 +9,7 @@ }, "IpRateLimiting": { + "RateLimitingEnabled": true, "EnableEndpointRateLimiting": true, "EnableRegexRuleMatching": true, "StackBlockedRequests": false, @@ -123,6 +124,7 @@ }, "ClientRateLimiting": { + "RateLimitingEnabled": true, "EnableEndpointRateLimiting": true, "EnableRegexRuleMatching": true, "ClientIdHeader": "X-ClientId", diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.json b/test/AspNetCoreRateLimit.Demo/appsettings.json index 8c587347..b07ee6da 100644 --- a/test/AspNetCoreRateLimit.Demo/appsettings.json +++ b/test/AspNetCoreRateLimit.Demo/appsettings.json @@ -9,6 +9,7 @@ }, "IpRateLimiting": { + "RateLimitingEnabled": true, "EnableEndpointRateLimiting": true, "StackBlockedRequests": false, "RealIpHeader": "X-Real-IP", @@ -130,6 +131,7 @@ }, "ClientRateLimiting": { + "RateLimitingEnabled": true, "EnableEndpointRateLimiting": true, "ClientIdHeader": "X-ClientId", "HttpStatusCode": 429, diff --git a/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs b/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs index 15dc1afe..8b7893d2 100644 --- a/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs +++ b/test/AspNetCoreRateLimit.Tests/BaseClassFixture.cs @@ -11,6 +11,7 @@ public abstract class BaseClassFixture : IClassFixture + { + builder.ConfigureAppConfiguration((context, conf) => + { + conf.AddJsonFile("appsettings.EnabledFalse.json"); + }); + }).CreateClient(options: new WebApplicationFactoryClientOptions + { + BaseAddress = new System.Uri("https://localhost:44304"), + + }); } /// @@ -43,9 +56,16 @@ protected HttpClient GetClient(ClientType clientType) return _wildcardClient; case ClientType.Regex: return _regexClient; + case ClientType.EnabledFalse: + return _enabledFalseClient; default: throw new ArgumentOutOfRangeException(nameof(clientType), clientType, "Unexpected client type."); } } + + protected void SetOptions(RateLimitOptions options) + { + + } } } \ No newline at end of file diff --git a/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs b/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs index 0b26c325..a7128347 100644 --- a/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs +++ b/test/AspNetCoreRateLimit.Tests/ClientRateLimitTests.cs @@ -1,5 +1,6 @@ using AspNetCoreRateLimit.Tests.Enums; using System.Net.Http; +using System.Text; using System.Threading.Tasks; using Xunit; @@ -224,5 +225,29 @@ public async Task UpdateOptions(ClientType clientType) // Assert Assert.Contains(keyword, content); } + + [Theory] + [InlineData(ClientType.EnabledFalse)] + public async Task RulesDisabled(ClientType clientType) + { + // Arrange + var clientId = "cl-key-3"; + int responseStatusCode = 0; + + // Act + for (int i = 0; i < 4; i++) + { + var request = new HttpRequestMessage(HttpMethod.Post, apiPath); + request.Headers.Add("X-ClientId", clientId); + request.Headers.Add("X-Real-IP", ip); + request.Content = new StringContent("{}", Encoding.UTF8, "application/json"); + + var response = await GetClient(clientType).SendAsync(request); + responseStatusCode = (int)response.StatusCode; + } + + // Assert + Assert.Equal(200, responseStatusCode); + } } } \ No newline at end of file diff --git a/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs b/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs index b95d09ef..7b84cf12 100644 --- a/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs +++ b/test/AspNetCoreRateLimit.Tests/Enums/ClientType.cs @@ -3,6 +3,7 @@ public enum ClientType { Wildcard, - Regex + Regex, + EnabledFalse } } \ No newline at end of file diff --git a/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs b/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs index 8400c675..01170c3c 100644 --- a/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs +++ b/test/AspNetCoreRateLimit.Tests/IpRateLimitTests.cs @@ -1,5 +1,6 @@ using AspNetCoreRateLimit.Tests.Enums; using System.Net.Http; +using System.Text; using System.Threading.Tasks; using Xunit; @@ -265,5 +266,28 @@ public async Task SpecificIpRuleMonitorActive(ClientType clientType, string ip) // Assert Assert.Equal(200, responseStatusCode); } + + [Theory] + [InlineData(ClientType.EnabledFalse, "84.247.85.232")] + public async Task RulesDisabled(ClientType clientType, string ip) + { + // Arrange + int responseStatusCode = 0; + + // Act + for (int i = 0; i < 2; i++) + { + var request = new HttpRequestMessage(HttpMethod.Get, apiValuesPath); + request.Headers.Add("X-Real-IP", ip); + request.Content = new StringContent("{}", Encoding.UTF8, "application/json"); + + var response = await GetClient(clientType).SendAsync(request); + responseStatusCode = (int)response.StatusCode; + } + + // Assert + Assert.Equal(200, responseStatusCode); + } + } } \ No newline at end of file From 92cc030b9032f11972a9e5b0c43f9cd57b06b10c Mon Sep 17 00:00:00 2001 From: Thomas <37584838+tomfp@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:14:07 +0100 Subject: [PATCH 2/2] false setting --- test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json index b774037e..6e7c157b 100644 --- a/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json +++ b/test/AspNetCoreRateLimit.Demo/appsettings.EnabledFalse.json @@ -9,7 +9,8 @@ }, "IpRateLimiting": { - "EnableEndpointRateLimiting": false, + "RateLimitingEnabled": false, + "EnableEndpointRateLimiting": true, "EnableRegexRuleMatching": true, "StackBlockedRequests": false, "RealIpHeader": "X-Real-IP", @@ -123,7 +124,8 @@ }, "ClientRateLimiting": { - "EnableEndpointRateLimiting": false, + "RateLimitingEnabled": false, + "EnableEndpointRateLimiting": true, "EnableRegexRuleMatching": true, "ClientIdHeader": "X-ClientId", "HttpStatusCode": 429,