1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-08 06:28:14 -05:00

BlockIpHostedService to replace func

This commit is contained in:
Kyle Spearrin 2019-03-04 23:41:46 -05:00
parent 3c9c1a2ab7
commit 2a49824ab7
4 changed files with 252 additions and 1 deletions

View File

@ -3,5 +3,13 @@
public class AdminSettings
{
public virtual string Admins { get; set; }
public virtual CloudflareSettings Cloudflare { get; set; }
public class CloudflareSettings
{
public string ZoneId { get; set; }
public string AuthEmail { get; set; }
public string AuthKey { get; set; }
}
}
}

View File

@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;
namespace Bit.Admin.HostedServices
{
public class BlockIpHostedService : IHostedService, IDisposable
{
private readonly ILogger<BlockIpHostedService> _logger;
private readonly GlobalSettings _globalSettings;
public readonly AdminSettings _adminSettings;
private Task _executingTask;
private CancellationTokenSource _cts;
private CloudQueue _blockQueue;
private CloudQueue _unblockQueue;
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 void Dispose()
{ }
private 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);
}
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(5));
}
}
private async Task BlockIpAsync(string message)
{
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");
var bodyContent = JsonConvert.SerializeObject(new
{
mode = "block",
configuration = new
{
target = "ip",
value = message
},
notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}."
});
request.Content = new StringContent(bodyContent, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request);
if(!response.IsSuccessStatusCode)
{
return;
}
var responseString = await response.Content.ReadAsStringAsync();
var accessRuleResponse = JsonConvert.DeserializeObject<AccessRuleResponse>(responseString);
if(!accessRuleResponse.Success)
{
return;
}
// TODO: Send `accessRuleResponse.Result?.Id` message to unblock queue
}
private async Task UnblockIpAsync(string message)
{
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);
if(!response.IsSuccessStatusCode)
{
return;
}
var responseString = await response.Content.ReadAsStringAsync();
var listResponse = JsonConvert.DeserializeObject<ListResponse>(responseString);
if(!listResponse.Success)
{
return;
}
foreach(var rule in listResponse.Result)
{
if(rule.Configuration?.Value != message)
{
continue;
}
await DeleteAccessRuleAsync(rule.Id);
}
}
else
{
// Rule Id messages
await DeleteAccessRuleAsync(message);
}
}
private async Task DeleteAccessRuleAsync(string ruleId)
{
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);
}
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; }
}
}
}
}

View File

@ -75,6 +75,10 @@ namespace Bit.Admin
// Jobs service
Jobs.JobsHostedService.AddJobsServices(services);
services.AddHostedService<Jobs.JobsHostedService>();
if(!globalSettings.SelfHosted)
{
services.AddHostedService<HostedServices.BlockIpHostedService>();
}
}
public void Configure(

View File

@ -45,7 +45,12 @@
}
},
"adminSettings": {
"admins": ""
"admins": "",
"cloudflare": {
"zoneId": "SECRET",
"authEmail": "SECRET",
"authKey": "SECRET"
}
},
"braintree": {
"production": false,