rework server responsibilities (#18)

* rework server responsibilities
add remote configuration

* start metrics only when compiled as not debug

* add some more logging to discord bot

* fixes of some casts

* make metrics port configurable, minor fixes

* add docker bullshit

* md formatting

* adjustments to docker stuff

* fix docker json files, fix some stuff in discord bot, add /useradd for Discord bot

* adjust docker configs and fix sharded.bat

* fixes for logs, cache file provider repeat trying to open filestream

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
rootdarkarchon
2022-12-27 13:48:05 +01:00
committed by GitHub
parent 7ee7fdaf48
commit 9eb5967935
101 changed files with 2470 additions and 585 deletions

View File

@@ -1,7 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.API;
using MareSynchronosShared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
@@ -11,6 +8,7 @@ namespace MareSynchronosServer.Hubs;
public partial class MareHub
{
// TODO: remove all of this and migrate it to the discord bot eventually
private List<string> OnlineAdmins => _dbContext.Users.Where(u => (u.IsModerator || u.IsAdmin)).Select(u => u.UID).ToList();
[Authorize(Policy = "Admin")]
@@ -116,11 +114,11 @@ public partial class MareHub
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
await Clients.Users(OnlineAdmins).Client_AdminUpdateOrAddBannedUser(dto).ConfigureAwait(false);
var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
if (!string.IsNullOrEmpty(bannedUser))
{
await Clients.User(bannedUser).Client_AdminForcedReconnect().ConfigureAwait(false);
}
//var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
//if (!string.IsNullOrEmpty(bannedUser))
//{
// await Clients.User(bannedUser).Client_AdminForcedReconnect().ConfigureAwait(false);
//}
}
[Authorize(Policy = "Admin")]

View File

@@ -1,6 +1,4 @@
using MareSynchronos.API;
using System.Threading.Tasks;
using System;
namespace MareSynchronosServer.Hubs
{

View File

@@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;
using MareSynchronos.API;

View File

@@ -1,9 +1,5 @@
using MareSynchronosShared.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using MareSynchronosServer.Utils;
using System.Security.Claims;

View File

@@ -5,11 +5,7 @@ using MareSynchronosShared.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace MareSynchronosServer.Hubs;

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using MareSynchronos.API;
using MareSynchronosServer.Utils;
using MareSynchronosShared.Metrics;
@@ -72,7 +68,7 @@ public partial class MareHub
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
var usersToSendOnlineTo = await SendOnlineToAllPairedUsers(ownIdent).ConfigureAwait(false);
return usersToSendOnlineTo.Select(e => _clientIdentService.GetCharacterIdentForUid(e)).Where(t => !string.IsNullOrEmpty(t)).Distinct(System.StringComparer.Ordinal).ToList();
return usersToSendOnlineTo.Select(e => _clientIdentService.GetCharacterIdentForUid(e)).Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToList();
}
[Authorize(Policy = "Identified")]
@@ -152,8 +148,8 @@ public partial class MareHub
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, f => _clientIdentService.GetCharacterIdentForUid(f), System.StringComparer.Ordinal)
.Where(f => visibleCharacterIds.Contains(f.Value, System.StringComparer.Ordinal));
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, f => _clientIdentService.GetCharacterIdentForUid(f), StringComparer.Ordinal)
.Where(f => visibleCharacterIds.Contains(f.Value, StringComparer.Ordinal));
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
@@ -172,7 +168,7 @@ public partial class MareHub
// don't allow adding yourself or nothing
uid = uid.Trim();
if (string.Equals(uid, AuthenticatedUserId, System.StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
if (string.Equals(uid, AuthenticatedUserId, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
// grab other user, check if it exists and if a pair already exists
var otherUser = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false);
@@ -231,7 +227,7 @@ public partial class MareHub
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
// if the other user has paused the main user and there was no previous group connection don't send anything
if (!otherEntry.IsPaused && allUserPairs.Any(p => string.Equals(p.UID, uid, System.StringComparison.Ordinal) && p.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection))
if (!otherEntry.IsPaused && allUserPairs.Any(p => string.Equals(p.UID, uid, StringComparison.Ordinal) && p.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection))
{
await Clients.User(user.UID).Client_UserChangePairedPlayer(otherIdent, true).ConfigureAwait(false);
await Clients.User(otherUser.UID).Client_UserChangePairedPlayer(userIdent, true).ConfigureAwait(false);
@@ -243,7 +239,7 @@ public partial class MareHub
{
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid, isPaused));
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
if (pair == null) return;
@@ -288,7 +284,7 @@ public partial class MareHub
{
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid));
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
// check if client pair even exists
ClientPair callerPair =
@@ -331,7 +327,7 @@ public partial class MareHub
if (!callerHadPaused && otherHadPaused) return;
var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, otherUserUid, System.StringComparison.Ordinal));
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, otherUserUid, StringComparison.Ordinal));
var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection;
// if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing

View File

@@ -1,19 +1,16 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Security.Claims;
using MareSynchronos.API;
using MareSynchronosServer.Services;
using MareSynchronosServer.Utils;
using MareSynchronosShared;
using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Protos;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace MareSynchronosServer.Hubs;
@@ -23,7 +20,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
private readonly FileService.FileServiceClient _fileServiceClient;
private readonly SystemInfoService _systemInfoService;
private readonly IHttpContextAccessor _contextAccessor;
private readonly GrpcClientIdentificationService _clientIdentService;
private readonly IClientIdentificationService _clientIdentService;
private readonly MareHubLogger _logger;
private readonly MareDbContext _dbContext;
private readonly Uri _cdnFullUri;
@@ -33,18 +30,18 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
private readonly int _maxGroupUserCount;
public MareHub(MareMetrics mareMetrics, FileService.FileServiceClient fileServiceClient,
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IOptions<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor,
GrpcClientIdentificationService clientIdentService)
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService,
IConfigurationService<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor,
IClientIdentificationService clientIdentService)
{
_mareMetrics = mareMetrics;
_fileServiceClient = fileServiceClient;
_systemInfoService = systemInfoService;
var config = configuration.Value;
_cdnFullUri = config.CdnFullUrl;
_shardName = config.ShardName;
_maxExistingGroupsByUser = config.MaxExistingGroupsByUser;
_maxJoinedGroupsByUser = config.MaxJoinedGroupsByUser;
_maxGroupUserCount = config.MaxGroupUserCount;
_cdnFullUri = configuration.GetValue<Uri>(nameof(ServerConfiguration.CdnFullUrl));
_shardName = configuration.GetValue<string>(nameof(ServerConfiguration.ShardName));
_maxExistingGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxExistingGroupsByUser), 3);
_maxJoinedGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxJoinedGroupsByUser), 6);
_maxGroupUserCount = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxGroupUserCount), 100);
_contextAccessor = contextAccessor;
_clientIdentService = clientIdentService;
_logger = new MareHubLogger(this, logger);
@@ -109,14 +106,14 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
}
[Authorize(Policy = "Authenticated")]
public async Task<bool> CheckClientHealth()
public Task<bool> CheckClientHealth()
{
var needsReconnect = !_clientIdentService.IsOnCurrentServer(AuthenticatedUserId);
if (needsReconnect)
{
_logger.LogCallWarning(MareHubLogger.Args(needsReconnect));
}
return needsReconnect;
return Task.FromResult(needsReconnect);
}
public override async Task OnConnectedAsync()

View File

@@ -0,0 +1,112 @@
using System.Security.Claims;
using AspNetCoreRateLimit;
using MareSynchronosShared;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Options;
namespace MareSynchronosServer.Hubs;
public class SignalRLimitFilter : IHubFilter
{
private readonly IRateLimitProcessor _processor;
private readonly IHttpContextAccessor accessor;
private readonly ILogger<SignalRLimitFilter> logger;
private static readonly SemaphoreSlim ConnectionLimiterSemaphore = new(10);
private static readonly SemaphoreSlim DisconnectLimiterSemaphore = new(10);
public SignalRLimitFilter(
IOptions<IpRateLimitOptions> options, IProcessingStrategy processing, IIpPolicyStore policyStore, IHttpContextAccessor accessor, ILogger<SignalRLimitFilter> logger)
{
_processor = new IpRateLimitProcessor(options?.Value, policyStore, processing);
this.accessor = accessor;
this.logger = logger;
}
public async ValueTask<object> InvokeMethodAsync(
HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
{
var ip = accessor.GetIpAddress();
var client = new ClientRequestIdentity
{
ClientIp = ip,
Path = invocationContext.HubMethodName,
HttpVerb = "ws",
ClientId = invocationContext.Context.UserIdentifier
};
foreach (var rule in await _processor.GetMatchingRulesAsync(client).ConfigureAwait(false))
{
var counter = await _processor.ProcessRequestAsync(client, rule).ConfigureAwait(false);
if (counter.Count > rule.Limit)
{
var authUserId = invocationContext.Context.User.Claims?.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value ?? "Unknown";
var retry = counter.Timestamp.RetryAfterFrom(rule);
logger.LogWarning("Method rate limit triggered from {ip}/{authUserId}: {method}", ip, authUserId, invocationContext.HubMethodName);
throw new HubException($"call limit {retry}");
}
}
return await next(invocationContext).ConfigureAwait(false);
}
// Optional method
public async Task OnConnectedAsync(HubLifetimeContext context, Func<HubLifetimeContext, Task> next)
{
await ConnectionLimiterSemaphore.WaitAsync().ConfigureAwait(false);
try
{
var ip = accessor.GetIpAddress();
var client = new ClientRequestIdentity
{
ClientIp = ip,
Path = "Connect",
HttpVerb = "ws",
};
foreach (var rule in await _processor.GetMatchingRulesAsync(client).ConfigureAwait(false))
{
var counter = await _processor.ProcessRequestAsync(client, rule).ConfigureAwait(false);
if (counter.Count > rule.Limit)
{
var retry = counter.Timestamp.RetryAfterFrom(rule);
logger.LogWarning("Connection rate limit triggered from {ip}", ip);
ConnectionLimiterSemaphore.Release();
throw new HubException($"Connection rate limit {retry}");
}
}
await Task.Delay(25).ConfigureAwait(false);
await next(context).ConfigureAwait(false);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Error on OnConnectedAsync");
}
finally
{
ConnectionLimiterSemaphore.Release();
}
}
public async Task OnDisconnectedAsync(
HubLifetimeContext context, Exception exception, Func<HubLifetimeContext, Exception, Task> next)
{
await DisconnectLimiterSemaphore.WaitAsync().ConfigureAwait(false);
if (exception != null)
{
logger.LogWarning(exception, "InitialException on OnDisconnectedAsync");
}
try
{
await next(context, exception).ConfigureAwait(false);
await Task.Delay(25).ConfigureAwait(false);
}
catch (Exception e)
{
logger.LogWarning(e, "ThrownException on OnDisconnectedAsync");
}
finally
{
DisconnectLimiterSemaphore.Release();
}
}
}