mirror of
https://github.com/bitwarden/server
synced 2025-12-25 04:33:26 +00:00
[PM-6141] Remove rate limiting ip blocker (#3754)
* remove rate limiting ip blocker * remove using * fix tests
This commit is contained in:
@@ -3,13 +3,5 @@
|
||||
public class AdminSettings
|
||||
{
|
||||
public virtual string Admins { get; set; }
|
||||
public virtual CloudflareSettings Cloudflare { get; set; }
|
||||
public int? DeleteTrashDaysAgo { get; set; }
|
||||
|
||||
public class CloudflareSettings
|
||||
{
|
||||
public string ZoneId { get; set; }
|
||||
public string AuthEmail { get; set; }
|
||||
public string AuthKey { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
using Amazon;
|
||||
using Amazon.SQS;
|
||||
using Amazon.SQS.Model;
|
||||
using Bit.Core.Settings;
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using Azure.Storage.Queues;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Bit.Admin.HostedServices;
|
||||
|
||||
public class AzureQueueBlockIpHostedService : BlockIpHostedService
|
||||
{
|
||||
private QueueClient _blockIpQueueClient;
|
||||
private QueueClient _unblockIpQueueClient;
|
||||
|
||||
public AzureQueueBlockIpHostedService(
|
||||
ILogger<AzureQueueBlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
: base(logger, adminSettings, globalSettings)
|
||||
{ }
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_blockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "blockip");
|
||||
_unblockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "unblockip");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var blockMessages = await _blockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32);
|
||||
if (blockMessages.Value?.Any() ?? false)
|
||||
{
|
||||
foreach (var message in blockMessages.Value)
|
||||
{
|
||||
try
|
||||
{
|
||||
await BlockIpAsync(message.MessageText, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to block IP.");
|
||||
}
|
||||
await _blockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
}
|
||||
}
|
||||
|
||||
var unblockMessages = await _unblockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32);
|
||||
if (unblockMessages.Value?.Any() ?? false)
|
||||
{
|
||||
foreach (var message in unblockMessages.Value)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UnblockIpAsync(message.MessageText, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to unblock IP.");
|
||||
}
|
||||
await _unblockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Bit.Admin.HostedServices;
|
||||
|
||||
public abstract class BlockIpHostedService : IHostedService, IDisposable
|
||||
{
|
||||
protected readonly ILogger<BlockIpHostedService> _logger;
|
||||
protected readonly GlobalSettings _globalSettings;
|
||||
private readonly AdminSettings _adminSettings;
|
||||
|
||||
private Task _executingTask;
|
||||
private CancellationTokenSource _cts;
|
||||
private HttpClient _httpClient = new HttpClient();
|
||||
|
||||
public BlockIpHostedService(
|
||||
ILogger<BlockIpHostedService> logger,
|
||||
IOptions<AdminSettings> adminSettings,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
_adminSettings = adminSettings?.Value;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_executingTask = ExecuteAsync(_cts.Token);
|
||||
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_executingTask == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_cts.Cancel();
|
||||
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{ }
|
||||
|
||||
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
|
||||
|
||||
protected async Task BlockIpAsync(string message, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Post;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules");
|
||||
|
||||
request.Content = JsonContent.Create(new
|
||||
{
|
||||
mode = "block",
|
||||
configuration = new
|
||||
{
|
||||
target = "ip",
|
||||
value = message
|
||||
},
|
||||
notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}."
|
||||
});
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var accessRuleResponse = await response.Content.ReadFromJsonAsync<AccessRuleResponse>(cancellationToken: cancellationToken);
|
||||
if (!accessRuleResponse.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Send `accessRuleResponse.Result?.Id` message to unblock queue
|
||||
}
|
||||
|
||||
protected async Task UnblockIpAsync(string message, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Contains(".") || message.Contains(":"))
|
||||
{
|
||||
// IP address messages
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Get;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules?" +
|
||||
$"configuration_target=ip&configuration_value={message}");
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var listResponse = await response.Content.ReadFromJsonAsync<ListResponse>(cancellationToken: cancellationToken);
|
||||
if (!listResponse.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var rule in listResponse.Result)
|
||||
{
|
||||
await DeleteAccessRuleAsync(rule.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rule Id messages
|
||||
await DeleteAccessRuleAsync(message, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task DeleteAccessRuleAsync(string ruleId, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Accept.Clear();
|
||||
request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail);
|
||||
request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey);
|
||||
request.Method = HttpMethod.Delete;
|
||||
request.RequestUri = new Uri("https://api.cloudflare.com/" +
|
||||
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules/{ruleId}");
|
||||
await _httpClient.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
public class ListResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public List<AccessRuleResultResponse> Result { get; set; }
|
||||
}
|
||||
|
||||
public class AccessRuleResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public AccessRuleResultResponse Result { get; set; }
|
||||
}
|
||||
|
||||
public class AccessRuleResultResponse
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public ConfigurationResponse Configuration { get; set; }
|
||||
|
||||
public class ConfigurationResponse
|
||||
{
|
||||
public string Target { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,14 +118,6 @@ public class Startup
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
|
||||
{
|
||||
services.AddHostedService<HostedServices.AzureQueueBlockIpHostedService>();
|
||||
}
|
||||
else if (CoreHelpers.SettingHasValue(globalSettings.Amazon?.AccessKeySecret))
|
||||
{
|
||||
services.AddHostedService<HostedServices.AmazonSqsBlockIpHostedService>();
|
||||
}
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Mail.ConnectionString))
|
||||
{
|
||||
services.AddHostedService<HostedServices.AzureQueueMailHostedService>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"globalSettings": {
|
||||
"selfHosted": false,
|
||||
"siteName": "Bitwarden",
|
||||
@@ -48,12 +48,7 @@
|
||||
}
|
||||
},
|
||||
"adminSettings": {
|
||||
"admins": "",
|
||||
"cloudflare": {
|
||||
"zoneId": "SECRET",
|
||||
"authEmail": "SECRET",
|
||||
"authKey": "SECRET"
|
||||
}
|
||||
"admins": ""
|
||||
},
|
||||
"braintree": {
|
||||
"production": false,
|
||||
|
||||
Reference in New Issue
Block a user