From b87c9c1a5a16959ed6b1bf02e6de21472851c357 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 30 Nov 2016 21:51:43 -0500 Subject: [PATCH] Queue ip addresses for block whenever they exceed the rate limit too much --- .../Middleware/CustomIpRateLimitMiddleware.cs | 36 +++++++++++++++++- src/Api/Startup.cs | 1 + src/Api/settings.json | 3 ++ src/Core/GlobalSettings.cs | 13 +++---- src/Core/Services/AzureBlockIpService.cs | 37 +++++++++++++++++++ src/Core/Services/IBlockIpService.cs | 10 +++++ src/Core/project.json | 3 +- 7 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 src/Core/Services/AzureBlockIpService.cs create mode 100644 src/Core/Services/IBlockIpService.cs diff --git a/src/Api/Middleware/CustomIpRateLimitMiddleware.cs b/src/Api/Middleware/CustomIpRateLimitMiddleware.cs index 0b5544ecad..1b5c1f75f7 100644 --- a/src/Api/Middleware/CustomIpRateLimitMiddleware.cs +++ b/src/Api/Middleware/CustomIpRateLimitMiddleware.cs @@ -1,6 +1,8 @@ using AspNetCoreRateLimit; using Bit.Api.Models.Response; +using Bit.Core.Services; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -11,17 +13,25 @@ namespace Bit.Api.Middleware public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware { private readonly IpRateLimitOptions _options; + private readonly IMemoryCache _memoryCache; + private readonly IBlockIpService _blockIpService; + private readonly ILogger _logger; public CustomIpRateLimitMiddleware( + IMemoryCache memoryCache, + IBlockIpService blockIpService, RequestDelegate next, IOptions options, IRateLimitCounterStore counterStore, IIpPolicyStore policyStore, ILogger logger, - IIpAddressParser ipParser = null - ) : base(next, options, counterStore, policyStore, logger, ipParser) + IIpAddressParser ipParser = null) + : base(next, options, counterStore, policyStore, logger, ipParser) { + _memoryCache = memoryCache; + _blockIpService = blockIpService; _options = options.Value; + _logger = logger; } public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter) @@ -35,5 +45,27 @@ namespace Bit.Api.Middleware var errorModel = new ErrorResponseModel { Message = message }; return httpContext.Response.WriteAsync(JsonConvert.SerializeObject(errorModel)); } + + public override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, + RateLimitCounter counter, RateLimitRule rule) + { + base.LogBlockedRequest(httpContext, identity, counter, rule); + var key = $"blockedIp_{identity.ClientIp}"; + + int blockedCount; + _memoryCache.TryGetValue(key, out blockedCount); + + blockedCount++; + if(blockedCount > 10) + { + _blockIpService.BlockIpAsync(identity.ClientIp, false); + _logger.LogDebug("Blocked " + identity.ClientIp); + } + else + { + _memoryCache.Set(key, blockedCount, + new MemoryCacheEntryOptions().SetSlidingExpiration(new System.TimeSpan(0, 5, 0))); + } + } } } diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index fb9f856e11..59086f448f 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -138,6 +138,7 @@ namespace Bit.Api services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Cors services.AddCors(config => diff --git a/src/Api/settings.json b/src/Api/settings.json index be0384f957..a8783cfc44 100644 --- a/src/Api/settings.json +++ b/src/Api/settings.json @@ -23,6 +23,9 @@ "gcmSenderId": "SECRET", "gcmApiKey": "SECRET", "gcmAppPackageName": "com.x8bit.bitwarden" + }, + "storage": { + "connectionString": "SECRET" } }, "IpRateLimitOptions": { diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index 23a20bb6ce..b3e1a45a11 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -8,14 +8,19 @@ public virtual SqlServerSettings SqlServer { get; set; } = new SqlServerSettings(); public virtual MailSettings Mail { get; set; } = new MailSettings(); public virtual LoggrSettings Loggr { get; set; } = new LoggrSettings(); - public virtual CacheSettings Cache { get; set; } = new CacheSettings(); public virtual PushSettings Push { get; set; } = new PushSettings(); + public virtual StorageSettings Storage { get; set; } = new StorageSettings(); public class SqlServerSettings { public string ConnectionString { get; set; } } + public class StorageSettings + { + public string ConnectionString { get; set; } + } + public class MailSettings { public string ApiKey { get; set; } @@ -28,12 +33,6 @@ public string ApiKey { get; set; } } - public class CacheSettings - { - public string ConnectionString { get; set; } - public int Database { get; set; } - } - public class PushSettings { public string ApnsCertificateThumbprint { get; set; } diff --git a/src/Core/Services/AzureBlockIpService.cs b/src/Core/Services/AzureBlockIpService.cs new file mode 100644 index 0000000000..3089b6ed69 --- /dev/null +++ b/src/Core/Services/AzureBlockIpService.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Queue; +using System; + +namespace Bit.Core.Services +{ + public class AzureBlockIpService : IBlockIpService + { + private CloudQueue _blockIpQueue; + private CloudQueue _unblockIpQueue; + + public AzureBlockIpService( + GlobalSettings globalSettings) + { + var storageAccount = CloudStorageAccount.Parse(globalSettings.Storage.ConnectionString); + var queueClient = storageAccount.CreateCloudQueueClient(); + + _blockIpQueue = queueClient.GetQueueReference("blockip"); + _blockIpQueue.CreateIfNotExists(); + + _unblockIpQueue = queueClient.GetQueueReference("unblockip"); + _unblockIpQueue.CreateIfNotExists(); + } + + public async Task BlockIpAsync(string ipAddress, bool permanentBlock) + { + var message = new CloudQueueMessage(ipAddress); + await _blockIpQueue.AddMessageAsync(message); + + if(!permanentBlock) + { + await _unblockIpQueue.AddMessageAsync(message, null, new TimeSpan(12, 0, 0), null, null); + } + } + } +} diff --git a/src/Core/Services/IBlockIpService.cs b/src/Core/Services/IBlockIpService.cs new file mode 100644 index 0000000000..c1acc34250 --- /dev/null +++ b/src/Core/Services/IBlockIpService.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace Bit.Core.Services +{ + public interface IBlockIpService + { + Task BlockIpAsync(string ipAddress, bool permanentBlock); + } +} diff --git a/src/Core/project.json b/src/Core/project.json index 15f37a68c0..83bb384ecb 100644 --- a/src/Core/project.json +++ b/src/Core/project.json @@ -8,7 +8,8 @@ "DataTableProxy": "1.2.0", "Sendgrid": "6.3.4", "StackExchange.Redis": "1.0.488", - "PushSharp": "4.0.10" + "PushSharp": "4.0.10", + "WindowsAzure.Storage": "7.2.1" }, "frameworks": {