diff --git a/src/Tgstation.Server.Host/Controllers/SwarmController.cs b/src/Tgstation.Server.Host/Controllers/SwarmController.cs index be171b19d8..cdaa834d46 100644 --- a/src/Tgstation.Server.Host/Controllers/SwarmController.cs +++ b/src/Tgstation.Server.Host/Controllers/SwarmController.cs @@ -96,9 +96,9 @@ namespace Tgstation.Server.Host.Controllers return StatusCode((int)HttpStatusCode.UpgradeRequired); var registrationResult = await swarmOperations.RegisterNode(registrationRequest, RequestRegistrationId, cancellationToken); - if (!registrationResult) + if (registrationResult == null) return Conflict(); - return NoContent(); + return Json(registrationResult); } /// diff --git a/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs b/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs index 5a0889b7e9..c8ae31031a 100644 --- a/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs +++ b/src/Tgstation.Server.Host/Swarm/ISwarmOperations.cs @@ -39,8 +39,8 @@ namespace Tgstation.Server.Host.Swarm /// The that is registering. /// The registration . /// The for the operation. - /// A resulting in if the registration was successful, otherwise. - ValueTask RegisterNode(SwarmServer node, Guid registrationId, CancellationToken cancellationToken); + /// A resulting in a if the registration was successful, otherwise. + ValueTask RegisterNode(SwarmServer node, Guid registrationId, CancellationToken cancellationToken); /// /// Attempt to unregister a node with a given with the controller. diff --git a/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResponse.cs b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResponse.cs new file mode 100644 index 0000000000..a31858fa29 --- /dev/null +++ b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResponse.cs @@ -0,0 +1,13 @@ +namespace Tgstation.Server.Host.Swarm +{ + /// + /// Response for a . + /// + public sealed class SwarmRegistrationResponse + { + /// + /// The base64 encoded token signing key. + /// + public required string TokenSigningKeyBase64 { get; init; } + } +} diff --git a/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs index 87508af508..a754aa52cd 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmRegistrationResult.cs @@ -24,5 +24,10 @@ /// A communication error occurred. /// CommunicationFailure, + + /// + /// Response could not be deserialized. + /// + PayloadFailure, } } diff --git a/src/Tgstation.Server.Host/Swarm/SwarmService.cs b/src/Tgstation.Server.Host/Swarm/SwarmService.cs index ea77de6a3c..c62148b4be 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmService.cs @@ -24,6 +24,7 @@ using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Security; using Tgstation.Server.Host.System; using Tgstation.Server.Host.Transfer; using Tgstation.Server.Host.Utils; @@ -89,6 +90,11 @@ namespace Tgstation.Server.Host.Swarm /// readonly IFileTransferTicketProvider transferService; + /// + /// The for the . + /// + readonly ITokenFactory tokenFactory; + /// /// The for the . /// @@ -159,6 +165,7 @@ namespace Tgstation.Server.Host.Swarm /// The value of . /// The value of . /// The value of . + /// The value of . /// The containing the value of . /// The value of . public SwarmService( @@ -169,6 +176,7 @@ namespace Tgstation.Server.Host.Swarm IAsyncDelayer asyncDelayer, IServerUpdater serverUpdater, IFileTransferTicketProvider transferService, + ITokenFactory tokenFactory, IOptions swarmConfigurationOptions, ILogger logger) { @@ -179,6 +187,7 @@ namespace Tgstation.Server.Host.Swarm this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.transferService = transferService ?? throw new ArgumentNullException(nameof(transferService)); + this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -540,7 +549,7 @@ namespace Tgstation.Server.Host.Swarm } /// - public async ValueTask RegisterNode(SwarmServer node, Guid registrationId, CancellationToken cancellationToken) + public async ValueTask RegisterNode(SwarmServer node, Guid registrationId, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(node); @@ -560,6 +569,11 @@ namespace Tgstation.Server.Host.Swarm await AbortUpdate(); + SwarmRegistrationResponse CreateResponse() => new() + { + TokenSigningKeyBase64 = Convert.ToBase64String(tokenFactory.SigningKey), + }; + var registrationIdsAndTimes = this.registrationIdsAndTimes!; lock (swarmServers) { @@ -569,7 +583,7 @@ namespace Tgstation.Server.Host.Swarm if (preExistingRegistrationKvp.Key == node.Identifier) { logger.LogWarning("Node {nodeId} has already registered!", node.Identifier); - return true; + return CreateResponse(); } logger.LogWarning( @@ -577,7 +591,7 @@ namespace Tgstation.Server.Host.Swarm node.Identifier, preExistingRegistrationKvp.Key, registrationId); - return false; + return null; } if (registrationIdsAndTimes.TryGetValue(node.Identifier, out var oldRegistration)) @@ -599,7 +613,7 @@ namespace Tgstation.Server.Host.Swarm logger.LogInformation("Registered node {nodeId} ({nodeIP}) with ID {registrationId}", node.Identifier, node.Address, registrationId); MarkServersDirty(); - return true; + return CreateResponse(); } /// @@ -1281,6 +1295,36 @@ namespace Tgstation.Server.Host.Swarm using var response = await httpClient.SendAsync(registrationRequest, HttpCompletionOption.ResponseContentRead, cancellationToken); if (response.IsSuccessStatusCode) { + try + { + var json = await response.Content.ReadAsStringAsync(cancellationToken); + if (json == null) + { + logger.LogDebug("Error reading registration response content stream! Text was null!"); + return SwarmRegistrationResult.PayloadFailure; + } + + var registrationResponse = JsonConvert.DeserializeObject(json); + if (registrationResponse == null) + { + logger.LogDebug("Error reading registration response content stream! Payload was null!"); + return SwarmRegistrationResult.PayloadFailure; + } + + if (registrationResponse.TokenSigningKeyBase64 == null) + { + logger.LogDebug("Error reading registration response content stream! SigningKey was null!"); + return SwarmRegistrationResult.PayloadFailure; + } + + tokenFactory.SigningKey = Convert.FromBase64String(registrationResponse.TokenSigningKeyBase64); + } + catch (Exception ex) + { + logger.LogDebug(ex, "Error reading registration response content stream!"); + return SwarmRegistrationResult.PayloadFailure; + } + logger.LogInformation("Sucessfully registered with ID {registrationId}", requestedRegistrationId); controllerRegistration = requestedRegistrationId; lastControllerHealthCheck = DateTimeOffset.UtcNow;