aboutsummaryrefslogtreecommitdiff
path: root/plugins/ObjectCacheServer/src/Endpoints/BrokerHeartBeatEndpoint.cs
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-06-12 19:04:15 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-06-12 19:04:15 -0400
commitdc0fc53fd3c3f6c32c8b0d063922c7018fa2c48f (patch)
tree92f963014624a1016f6cb645af5afd18278c54c3 /plugins/ObjectCacheServer/src/Endpoints/BrokerHeartBeatEndpoint.cs
parent392b38a40e01f2d4dbd457da122dfaf7a1ffe00f (diff)
Baby steps for autonomous nodes
Diffstat (limited to 'plugins/ObjectCacheServer/src/Endpoints/BrokerHeartBeatEndpoint.cs')
-rw-r--r--plugins/ObjectCacheServer/src/Endpoints/BrokerHeartBeatEndpoint.cs149
1 files changed, 149 insertions, 0 deletions
diff --git a/plugins/ObjectCacheServer/src/Endpoints/BrokerHeartBeatEndpoint.cs b/plugins/ObjectCacheServer/src/Endpoints/BrokerHeartBeatEndpoint.cs
new file mode 100644
index 0000000..b9c00e6
--- /dev/null
+++ b/plugins/ObjectCacheServer/src/Endpoints/BrokerHeartBeatEndpoint.cs
@@ -0,0 +1,149 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: ObjectCacheServer
+* File: BrokerHeartBeatEndpoint.cs
+*
+* BrokerHeartBeatEndpoint.cs is part of ObjectCacheServer which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* ObjectCacheServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* ObjectCacheServer is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+
+using VNLib.Plugins;
+using VNLib.Utils.Logging;
+using VNLib.Plugins.Essentials;
+using VNLib.Hashing.IdentityUtility;
+using VNLib.Plugins.Essentials.Endpoints;
+using VNLib.Plugins.Essentials.Extensions;
+using VNLib.Plugins.Extensions.Loading;
+
+namespace VNLib.Data.Caching.ObjectCache.Server
+{
+ internal sealed class BrokerHeartBeatEndpoint : ResourceEndpointBase
+ {
+ private readonly IBrokerHeartbeatNotifier _heartBeat;
+ private readonly Task<IPAddress[]> BrokerIpList;
+ private readonly bool DebugMode;
+
+ ///<inheritdoc/>
+ protected override ProtectionSettings EndpointProtectionSettings { get; } = new()
+ {
+ DisableBrowsersOnly = true,
+ DisableSessionsRequired = true
+ };
+
+ public BrokerHeartBeatEndpoint(PluginBase plugin)
+ {
+ //Get debug flag
+ DebugMode = plugin.IsDebug();
+
+ //Get or create the current node config
+ _heartBeat = plugin.GetOrCreateSingleton<NodeConfig>();
+
+ /*
+ * Resolve the ip address of the broker and store it to verify connections
+ * later
+ */
+ BrokerIpList = Dns.GetHostAddressesAsync(_heartBeat.GetBrokerAddress().DnsSafeHost);
+
+ //Setup endpoint
+ InitPathAndLog("/heartbeat", plugin.Log);
+ }
+
+
+ protected override async ValueTask<VfReturnType> GetAsync(HttpEntity entity)
+ {
+ //If-not loopback then verify server address
+ if (!entity.Server.IsLoopBack())
+ {
+ //Load and verify the broker's ip address matches with an address we have stored
+ IPAddress[] addresses = await BrokerIpList;
+
+ if (!addresses.Contains(entity.TrustedRemoteIp))
+ {
+ if (DebugMode)
+ {
+ Log.Debug("Received connection {ip} that was not a DNS safe address for the broker server, access denied");
+ }
+
+ //Token invalid
+ entity.CloseResponse(HttpStatusCode.Forbidden);
+ return VfReturnType.VirtualSkip;
+ }
+ }
+
+ //Get the authorization jwt
+ string? jwtAuth = entity.Server.Headers[HttpRequestHeader.Authorization];
+
+ if (string.IsNullOrWhiteSpace(jwtAuth))
+ {
+ //Token invalid
+ entity.CloseResponse(HttpStatusCode.Forbidden);
+ return VfReturnType.VirtualSkip;
+ }
+
+ //Parse the jwt
+ using JsonWebToken jwt = JsonWebToken.Parse(jwtAuth);
+
+ //Verify the jwt using the broker's public key certificate
+ using (ReadOnlyJsonWebKey cert = _heartBeat.GetBrokerPublicKey())
+ {
+ //Verify the jwt
+ if (!jwt.VerifyFromJwk(cert))
+ {
+ //Token invalid
+ entity.CloseResponse(HttpStatusCode.Forbidden);
+ return VfReturnType.VirtualSkip;
+ }
+ }
+
+ string? auth;
+ //Recover the auth token from the jwt
+ using (JsonDocument doc = jwt.GetPayload())
+ {
+ auth = doc.RootElement.GetProperty("token").GetString();
+ }
+
+ //Get our stored token used for registration
+ string? selfToken = _heartBeat.GetAuthToken();
+
+ //Verify token
+ if (selfToken != null && selfToken.Equals(auth, StringComparison.Ordinal))
+ {
+ //Signal keepalive
+ _heartBeat.HearbeatReceived();
+ entity.CloseResponse(HttpStatusCode.OK);
+ return VfReturnType.VirtualSkip;
+ }
+
+ if (DebugMode)
+ {
+ Log.Debug("Invalid auth token recieved from broker sever, access denied");
+ }
+
+ //Token invalid
+ entity.CloseResponse(HttpStatusCode.Forbidden);
+ return VfReturnType.VirtualSkip;
+ }
+ }
+}