1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-25 12:45:18 +01:00

amazon sqs block ip queuing

This commit is contained in:
Kyle Spearrin 2019-03-18 16:23:37 -04:00
parent 7d65f8fa76
commit 8427c23b5e
7 changed files with 262 additions and 67 deletions

View File

@ -0,0 +1,89 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Amazon;
using Amazon.SQS;
using Amazon.SQS.Model;
using Bit.Core;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Bit.Admin.HostedServices
{
public class AmazonSqsBlockIpHostedService : BlockIpHostedService
{
private AmazonSQSClient _client;
public AmazonSqsBlockIpHostedService(
ILogger<AmazonSqsBlockIpHostedService> logger,
IOptions<AdminSettings> adminSettings,
GlobalSettings globalSettings)
: base(logger, adminSettings, globalSettings)
{ }
public override void Dispose()
{
_client?.Dispose();
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
_client = new AmazonSQSClient(_globalSettings.Amazon.AccessKeyId,
_globalSettings.Amazon.AccessKeySecret, RegionEndpoint.GetBySystemName(_globalSettings.Amazon.Region));
var blockIpQueue = await _client.GetQueueUrlAsync("block-ip", cancellationToken);
var blockIpQueueUrl = blockIpQueue.QueueUrl;
var unblockIpQueue = await _client.GetQueueUrlAsync("unblock-ip", cancellationToken);
var unblockIpQueueUrl = unblockIpQueue.QueueUrl;
while(!cancellationToken.IsCancellationRequested)
{
var blockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest
{
QueueUrl = blockIpQueueUrl,
MaxNumberOfMessages = 10,
WaitTimeSeconds = 15
}, cancellationToken);
if(blockMessageResponse.Messages.Any())
{
foreach(var message in blockMessageResponse.Messages)
{
try
{
await BlockIpAsync(message.Body, cancellationToken);
}
catch(Exception e)
{
_logger.LogError(e, "Failed to block IP.");
}
await _client.DeleteMessageAsync(blockIpQueueUrl, message.ReceiptHandle, cancellationToken);
}
}
var unblockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest
{
QueueUrl = unblockIpQueueUrl,
MaxNumberOfMessages = 10,
WaitTimeSeconds = 15
}, cancellationToken);
if(unblockMessageResponse.Messages.Any())
{
foreach(var message in unblockMessageResponse.Messages)
{
try
{
await UnblockIpAsync(message.Body, cancellationToken);
}
catch(Exception e)
{
_logger.LogError(e, "Failed to unblock IP.");
}
await _client.DeleteMessageAsync(unblockIpQueueUrl, message.ReceiptHandle, cancellationToken);
}
}
await Task.Delay(TimeSpan.FromSeconds(15));
}
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
namespace Bit.Admin.HostedServices
{
public class AzureQueueBlockIpHostedService : BlockIpHostedService
{
private CloudQueue _blockQueue;
private CloudQueue _unblockQueue;
public AzureQueueBlockIpHostedService(
ILogger<AzureQueueBlockIpHostedService> logger,
IOptions<AdminSettings> adminSettings,
GlobalSettings globalSettings)
: base(logger, adminSettings, globalSettings)
{ }
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var storageAccount = CloudStorageAccount.Parse(_globalSettings.Storage.ConnectionString);
var queueClient = storageAccount.CreateCloudQueueClient();
_blockQueue = queueClient.GetQueueReference("blockip");
_unblockQueue = queueClient.GetQueueReference("unblockip");
while(!cancellationToken.IsCancellationRequested)
{
var blockMessages = await _blockQueue.GetMessagesAsync(32, TimeSpan.FromSeconds(15),
null, null, cancellationToken);
if(blockMessages.Any())
{
foreach(var message in blockMessages)
{
try
{
await BlockIpAsync(message.AsString, cancellationToken);
}
catch(Exception e)
{
_logger.LogError(e, "Failed to block IP.");
}
await _blockQueue.DeleteMessageAsync(message);
}
}
var unblockMessages = await _unblockQueue.GetMessagesAsync(32, TimeSpan.FromSeconds(15),
null, null, cancellationToken);
if(unblockMessages.Any())
{
foreach(var message in unblockMessages)
{
try
{
await UnblockIpAsync(message.AsString, cancellationToken);
}
catch(Exception e)
{
_logger.LogError(e, "Failed to unblock IP.");
}
await _unblockQueue.DeleteMessageAsync(message);
}
}
await Task.Delay(TimeSpan.FromSeconds(15));
}
}
}
}

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -9,22 +8,18 @@ using Bit.Core;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Bit.Admin.HostedServices namespace Bit.Admin.HostedServices
{ {
public class BlockIpHostedService : IHostedService, IDisposable public abstract class BlockIpHostedService : IHostedService, IDisposable
{ {
private readonly ILogger<BlockIpHostedService> _logger; protected readonly ILogger<BlockIpHostedService> _logger;
private readonly GlobalSettings _globalSettings; protected readonly GlobalSettings _globalSettings;
public readonly AdminSettings _adminSettings; private readonly AdminSettings _adminSettings;
private Task _executingTask; private Task _executingTask;
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private CloudQueue _blockQueue;
private CloudQueue _unblockQueue;
private HttpClient _httpClient = new HttpClient(); private HttpClient _httpClient = new HttpClient();
public BlockIpHostedService( public BlockIpHostedService(
@ -55,59 +50,12 @@ namespace Bit.Admin.HostedServices
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
} }
public void Dispose() public virtual void Dispose()
{ } { }
private async Task ExecuteAsync(CancellationToken cancellationToken) protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
{
var storageAccount = CloudStorageAccount.Parse(_globalSettings.Storage.ConnectionString);
var queueClient = storageAccount.CreateCloudQueueClient();
_blockQueue = queueClient.GetQueueReference("blockip");
_unblockQueue = queueClient.GetQueueReference("unblockip");
while(!cancellationToken.IsCancellationRequested) protected async Task BlockIpAsync(string message, CancellationToken cancellationToken)
{
var blockMessages = await _blockQueue.GetMessagesAsync(32, TimeSpan.FromSeconds(15),
null, null, cancellationToken);
if(blockMessages.Any())
{
foreach(var message in blockMessages)
{
try
{
await BlockIpAsync(message.AsString);
}
catch(Exception e)
{
_logger.LogError(e, "Failed to block IP.");
}
await _blockQueue.DeleteMessageAsync(message);
}
}
var unblockMessages = await _unblockQueue.GetMessagesAsync(32, TimeSpan.FromSeconds(15),
null, null, cancellationToken);
if(unblockMessages.Any())
{
foreach(var message in unblockMessages)
{
try
{
await UnblockIpAsync(message.AsString);
}
catch(Exception e)
{
_logger.LogError(e, "Failed to unblock IP.");
}
await _unblockQueue.DeleteMessageAsync(message);
}
}
await Task.Delay(TimeSpan.FromSeconds(15));
}
}
private async Task BlockIpAsync(string message)
{ {
var request = new HttpRequestMessage(); var request = new HttpRequestMessage();
request.Headers.Accept.Clear(); request.Headers.Accept.Clear();
@ -129,7 +77,7 @@ namespace Bit.Admin.HostedServices
}); });
request.Content = new StringContent(bodyContent, Encoding.UTF8, "application/json"); request.Content = new StringContent(bodyContent, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request); var response = await _httpClient.SendAsync(request, cancellationToken);
if(!response.IsSuccessStatusCode) if(!response.IsSuccessStatusCode)
{ {
return; return;
@ -145,7 +93,7 @@ namespace Bit.Admin.HostedServices
// TODO: Send `accessRuleResponse.Result?.Id` message to unblock queue // TODO: Send `accessRuleResponse.Result?.Id` message to unblock queue
} }
private async Task UnblockIpAsync(string message) protected async Task UnblockIpAsync(string message, CancellationToken cancellationToken)
{ {
if(string.IsNullOrWhiteSpace(message)) if(string.IsNullOrWhiteSpace(message))
{ {
@ -164,7 +112,7 @@ namespace Bit.Admin.HostedServices
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules?" + $"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules?" +
$"configuration_target=ip&configuration_value={message}"); $"configuration_target=ip&configuration_value={message}");
var response = await _httpClient.SendAsync(request); var response = await _httpClient.SendAsync(request, cancellationToken);
if(!response.IsSuccessStatusCode) if(!response.IsSuccessStatusCode)
{ {
return; return;
@ -179,17 +127,17 @@ namespace Bit.Admin.HostedServices
foreach(var rule in listResponse.Result) foreach(var rule in listResponse.Result)
{ {
await DeleteAccessRuleAsync(rule.Id); await DeleteAccessRuleAsync(rule.Id, cancellationToken);
} }
} }
else else
{ {
// Rule Id messages // Rule Id messages
await DeleteAccessRuleAsync(message); await DeleteAccessRuleAsync(message, cancellationToken);
} }
} }
private async Task DeleteAccessRuleAsync(string ruleId) protected async Task DeleteAccessRuleAsync(string ruleId, CancellationToken cancellationToken)
{ {
var request = new HttpRequestMessage(); var request = new HttpRequestMessage();
request.Headers.Accept.Clear(); request.Headers.Accept.Clear();
@ -198,7 +146,7 @@ namespace Bit.Admin.HostedServices
request.Method = HttpMethod.Delete; request.Method = HttpMethod.Delete;
request.RequestUri = new Uri("https://api.cloudflare.com/" + request.RequestUri = new Uri("https://api.cloudflare.com/" +
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules/{ruleId}"); $"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules/{ruleId}");
await _httpClient.SendAsync(request); await _httpClient.SendAsync(request, cancellationToken);
} }
public class ListResponse public class ListResponse

View File

@ -77,7 +77,14 @@ namespace Bit.Admin
services.AddHostedService<Jobs.JobsHostedService>(); services.AddHostedService<Jobs.JobsHostedService>();
if(!globalSettings.SelfHosted) if(!globalSettings.SelfHosted)
{ {
services.AddHostedService<HostedServices.BlockIpHostedService>(); if(CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
{
services.AddHostedService<HostedServices.AzureQueueBlockIpHostedService>();
}
else if(CoreHelpers.SettingHasValue(globalSettings.Amazon?.AccessKeySecret))
{
services.AddHostedService<HostedServices.AmazonSqsBlockIpHostedService>();
}
} }
} }

View File

@ -22,6 +22,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.7.38" /> <PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.7.38" />
<PackageReference Include="AWSSDK.SQS" Version="3.3.3.62" />
<PackageReference Include="Handlebars.Net" Version="1.9.5" /> <PackageReference Include="Handlebars.Net" Version="1.9.5" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.7.0" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.7.0" />
<PackageReference Include="MailKit" Version="2.1.3" /> <PackageReference Include="MailKit" Version="2.1.3" />

View File

@ -0,0 +1,72 @@
using System.Threading.Tasks;
using System;
using Amazon.SQS;
using Amazon;
namespace Bit.Core.Services
{
public class AmazonSqsBlockIpService : IBlockIpService, IDisposable
{
private readonly AmazonSQSClient _client;
private string _blockIpQueueUrl;
private string _unblockIpQueueUrl;
private bool _didInit = false;
private Tuple<string, bool, DateTime> _lastBlock;
public AmazonSqsBlockIpService(
GlobalSettings globalSettings)
{
if(string.IsNullOrWhiteSpace(globalSettings.Amazon?.AccessKeyId))
{
throw new ArgumentNullException(nameof(globalSettings.Amazon.AccessKeyId));
}
if(string.IsNullOrWhiteSpace(globalSettings.Amazon?.AccessKeySecret))
{
throw new ArgumentNullException(nameof(globalSettings.Amazon.AccessKeySecret));
}
if(string.IsNullOrWhiteSpace(globalSettings.Amazon?.Region))
{
throw new ArgumentNullException(nameof(globalSettings.Amazon.Region));
}
_client = new AmazonSQSClient(globalSettings.Amazon.AccessKeyId,
globalSettings.Amazon.AccessKeySecret, RegionEndpoint.GetBySystemName(globalSettings.Amazon.Region));
}
public void Dispose()
{
_client?.Dispose();
}
public async Task BlockIpAsync(string ipAddress, bool permanentBlock)
{
var now = DateTime.UtcNow;
if(_lastBlock != null && _lastBlock.Item1 == ipAddress && _lastBlock.Item2 == permanentBlock &&
(now - _lastBlock.Item3) < TimeSpan.FromMinutes(1))
{
// Already blocked this IP recently.
return;
}
_lastBlock = new Tuple<string, bool, DateTime>(ipAddress, permanentBlock, now);
await _client.SendMessageAsync(_blockIpQueueUrl, ipAddress);
if(!permanentBlock)
{
await _client.SendMessageAsync(_unblockIpQueueUrl, ipAddress);
}
}
private async Task InitAsync()
{
if(_didInit)
{
return;
}
var blockIpQueue = await _client.GetQueueUrlAsync("block-ip");
_blockIpQueueUrl = blockIpQueue.QueueUrl;
var unblockIpQueue = await _client.GetQueueUrlAsync("unblock-ip");
_unblockIpQueueUrl = unblockIpQueue.QueueUrl;
_didInit = true;
}
}
}

View File

@ -121,6 +121,10 @@ namespace Bit.Core.Utilities
{ {
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>(); services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
} }
else if(!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Amazon?.AccessKeySecret))
{
services.AddSingleton<IBlockIpService, AmazonSqsBlockIpService>();
}
else else
{ {
services.AddSingleton<IBlockIpService, NoopBlockIpService>(); services.AddSingleton<IBlockIpService, NoopBlockIpService>();