1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-07 14:08:13 -05:00
bitwarden/src/Admin/HostedServices/BlockIpHostedService.cs
2022-08-29 16:06:55 -04:00

165 lines
5.5 KiB
C#

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; }
}
}
}