rework authentication

This commit is contained in:
rootdarkarchon
2022-12-18 14:53:44 +01:00
parent f9d9e2608e
commit f278c5a762
17 changed files with 137 additions and 401 deletions

View File

@@ -1,16 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MareSynchronosServices.Authentication;
internal class FailedAuthorization
{
private int failedAttempts = 1;
public int FailedAttempts => failedAttempts;
public Task ResetTask { get; set; }
public void IncreaseFailedAttempts()
{
Interlocked.Increment(ref failedAttempts);
}
}

View File

@@ -1,146 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Protos;
using MareSynchronosShared.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace MareSynchronosServices.Authentication;
public class SecretKeyAuthenticationHandler
{
private readonly ILogger<SecretKeyAuthenticationHandler> _logger;
private readonly MareMetrics _metrics;
private const string Unauthorized = "Unauthorized";
private readonly ConcurrentDictionary<string, string> _cachedAuthorizations = new();
private readonly ConcurrentDictionary<string, FailedAuthorization?> _failedAuthorizations = new();
private readonly int _failedAttemptsForTempBan;
private readonly int _tempBanMinutes;
private readonly List<string> _whitelistedIps = new();
public void ClearUnauthorizedUsers()
{
foreach (var item in _cachedAuthorizations.ToArray())
{
if (item.Value == Unauthorized)
{
_cachedAuthorizations.TryRemove(item.Key, out _);
}
}
}
public void RemoveAuthentication(string uid)
{
var authorization = _cachedAuthorizations.Where(u => u.Value == uid);
if (authorization.Any())
{
_cachedAuthorizations.Remove(authorization.First().Key, out _);
}
}
public async Task<AuthReply> AuthenticateAsync(MareDbContext mareDbContext, string ip, string secretKey)
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationRequests);
if (string.IsNullOrEmpty(secretKey))
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
}
if (_failedAuthorizations.TryGetValue(ip, out var existingFailedAuthorization) && existingFailedAuthorization.FailedAttempts > _failedAttemptsForTempBan)
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
if (existingFailedAuthorization.ResetTask == null)
{
_logger.LogWarning("TempBan {ip} for authorization spam", ip);
existingFailedAuthorization.ResetTask = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMinutes(_tempBanMinutes)).ConfigureAwait(false);
}).ContinueWith((t) =>
{
_failedAuthorizations.Remove(ip, out _);
});
}
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
}
var hashedHeader = StringUtils.Sha256String(secretKey);
bool fromCache = _cachedAuthorizations.TryGetValue(hashedHeader, out string uid);
if (fromCache && !string.IsNullOrEmpty(uid))
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits);
if (uid == Unauthorized)
{
return AuthenticationFailure(ip);
}
}
else
{
uid = (await mareDbContext.Auth.AsNoTracking()
.FirstOrDefaultAsync(m => m.HashedKey == hashedHeader).ConfigureAwait(false))?.UserUID;
if (string.IsNullOrEmpty(uid))
{
_cachedAuthorizations[hashedHeader] = Unauthorized;
return AuthenticationFailure(ip);
}
_cachedAuthorizations[hashedHeader] = uid;
}
_metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses);
return new AuthReply() { Success = true, Uid = new UidMessage() { Uid = uid } };
}
private AuthReply AuthenticationFailure(string ip)
{
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
_logger.LogWarning("Failed authorization from {ip}", ip);
if (!_whitelistedIps.Any(w => ip.Contains(w)))
{
if (_failedAuthorizations.TryGetValue(ip, out var auth))
{
auth.IncreaseFailedAttempts();
}
else
{
_failedAuthorizations[ip] = new FailedAuthorization();
}
}
return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } };
}
public SecretKeyAuthenticationHandler(IConfiguration configuration, ILogger<SecretKeyAuthenticationHandler> logger, MareMetrics metrics)
{
_logger = logger;
_metrics = metrics;
var config = configuration.GetRequiredSection("MareSynchronos");
_failedAttemptsForTempBan = config.GetValue<int>("FailedAuthForTempBan", 5);
logger.LogInformation("FailedAuthForTempBan: {num}", _failedAttemptsForTempBan);
_tempBanMinutes = config.GetValue<int>("TempBanDurationInMinutes", 30);
logger.LogInformation("TempBanMinutes: {num}", _tempBanMinutes);
_whitelistedIps = config.GetSection("WhitelistedIps").Get<List<string>>();
foreach (var ip in _whitelistedIps)
{
logger.LogInformation("Whitelisted IP: " + ip);
}
}
}

View File

@@ -1,5 +1,4 @@
using MareSynchronosServices.Authentication;
using MareSynchronosShared.Data;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models;
using MareSynchronosShared.Utils;
@@ -19,16 +18,14 @@ namespace MareSynchronosServices;
public class CleanupService : IHostedService, IDisposable
{
private readonly MareMetrics metrics;
private readonly SecretKeyAuthenticationHandler _authService;
private readonly ILogger<CleanupService> _logger;
private readonly IServiceProvider _services;
private readonly IConfiguration _configuration;
private Timer? _timer;
public CleanupService(MareMetrics metrics, SecretKeyAuthenticationHandler authService, ILogger<CleanupService> logger, IServiceProvider services, IConfiguration configuration)
public CleanupService(MareMetrics metrics, ILogger<CleanupService> logger, IServiceProvider services, IConfiguration configuration)
{
this.metrics = metrics;
_authService = authService;
_logger = logger;
_services = services;
_configuration = configuration.GetRequiredSection("MareSynchronos");
@@ -119,8 +116,6 @@ public class CleanupService : IHostedService, IDisposable
_logger.LogWarning(ex, "Error during Temp Invite purge");
}
_authService.ClearUnauthorizedUsers();
_logger.LogInformation($"Cleanup complete");
dbContext.SaveChanges();
@@ -137,8 +132,6 @@ public class CleanupService : IHostedService, IDisposable
dbContext.Remove(lodestone);
}
_authService.RemoveAuthentication(user.UID);
var auth = dbContext.Auth.Single(a => a.UserUID == user.UID);
var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList();

View File

@@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore;
using Discord.WebSocket;
using System.Linq;
using Prometheus;
using MareSynchronosServices.Authentication;
using MareSynchronosShared.Models;
using MareSynchronosServices.Identity;
using MareSynchronosShared.Metrics;
@@ -304,9 +303,6 @@ public class MareModule : InteractionModuleBase
await db.Auth.AddAsync(auth).ConfigureAwait(false);
await db.SaveChangesAsync().ConfigureAwait(false);
var authHandler = scope.ServiceProvider.GetService<SecretKeyAuthenticationHandler>();
authHandler.RemoveAuthentication(existingLodestoneAuth.User.UID);
}
}

View File

@@ -1,45 +0,0 @@
using Grpc.Core;
using MareSynchronosServices.Authentication;
using MareSynchronosShared.Data;
using MareSynchronosShared.Protos;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace MareSynchronosServices.Services;
internal class AuthenticationService : AuthService.AuthServiceBase
{
private readonly ILogger<AuthenticationService> _logger;
private readonly MareDbContext _dbContext;
private readonly SecretKeyAuthenticationHandler _authHandler;
public AuthenticationService(ILogger<AuthenticationService> logger, MareDbContext dbContext, SecretKeyAuthenticationHandler authHandler)
{
_logger = logger;
_dbContext = dbContext;
_authHandler = authHandler;
}
public override async Task Authorize(IAsyncStreamReader<AuthRequest> requestStream, IServerStreamWriter<AuthReply> responseStream, ServerCallContext context)
{
await foreach (var input in requestStream.ReadAllAsync(context.CancellationToken).ConfigureAwait(false))
{
var response = await _authHandler.AuthenticateAsync(_dbContext, input.Ip, input.SecretKey).ConfigureAwait(false);
await responseStream.WriteAsync(response, context.CancellationToken).ConfigureAwait(false);
}
}
public override Task<Empty> RemoveAuth(UidMessage request, ServerCallContext context)
{
_logger.LogInformation("Removing Authentication for {uid}", request.Uid);
_authHandler.RemoveAuthentication(request.Uid);
return Task.FromResult(new Empty());
}
public override Task<Empty> ClearUnauthorized(Empty request, ServerCallContext context)
{
_logger.LogInformation("Clearing unauthorized users");
_authHandler.ClearUnauthorizedUsers();
return Task.FromResult(new Empty());
}
}

View File

@@ -1,6 +1,4 @@
using MareSynchronosServices.Authentication;
using MareSynchronosServices.Discord;
using MareSynchronosServices.Services;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using Microsoft.AspNetCore.Builder;
@@ -45,7 +43,6 @@ public class Startup
}));
services.AddTransient(_ => Configuration);
services.AddSingleton<SecretKeyAuthenticationHandler>();
services.AddSingleton<DiscordBotServices>();
services.AddSingleton<IdentityHandler>();
services.AddSingleton<CleanupService>();
@@ -63,7 +60,6 @@ public class Startup
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<AuthenticationService>();
endpoints.MapGrpcService<IdentityService>();
});
}