aboutsummaryrefslogtreecommitdiff
path: root/Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs
diff options
context:
space:
mode:
authorLibravatar vman <public@vaughnnugent.com>2022-10-30 02:28:12 -0400
committerLibravatar vman <public@vaughnnugent.com>2022-10-30 02:28:12 -0400
commita8510fb835dcc5e1142d700164ce5a4bd44e1a25 (patch)
tree28caab320f777a384cb6883b68dd999cdc8c0a3f /Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs
Add project files.
Diffstat (limited to 'Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs')
-rw-r--r--Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs111
1 files changed, 111 insertions, 0 deletions
diff --git a/Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs b/Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs
new file mode 100644
index 0000000..e80be77
--- /dev/null
+++ b/Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Net;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+
+using VNLib.Data.Caching.Extensions;
+using VNLib.Hashing.IdentityUtility;
+using VNLib.Plugins.Essentials.Extensions;
+using VNLib.Plugins.Extensions.Loading;
+
+#nullable enable
+
+namespace VNLib.Plugins.Essentials.Sessions.Server.Endpoints
+{
+ internal class BrokerHeartBeat : ResourceEndpointBase
+ {
+ public override string Path => "/heartbeat";
+
+ private readonly Func<string> Token;
+ private readonly ManualResetEvent KeepaliveSet;
+ private readonly Task<IPAddress[]> BrokerIpList;
+ private readonly PluginBase Pbase;
+
+ protected override ProtectionSettings EndpointProtectionSettings { get; }
+
+ public BrokerHeartBeat(Func<string> token, ManualResetEvent keepaliveSet, Uri brokerUri, PluginBase pbase)
+ {
+ Token = token;
+ KeepaliveSet = keepaliveSet;
+ BrokerIpList = Dns.GetHostAddressesAsync(brokerUri.DnsSafeHost);
+
+ this.Pbase = pbase;
+
+ EndpointProtectionSettings = new()
+ {
+ BrowsersOnly = false,
+ SessionsRequired = false,
+ VerifySessionCors = false,
+ };
+ }
+
+ private async Task<byte[]> GetBrokerPubAsync()
+ {
+ string? brokerPubKey = await Pbase.TryGetSecretAsync("broker_public_key") ?? throw new KeyNotFoundException("Missing required secret : broker_public_key");
+
+ return Convert.FromBase64String(brokerPubKey);
+ }
+
+ 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))
+ {
+ //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);
+ //Init signature alg
+ using (ECDsa alg = ECDsa.Create(FBMDataCacheExtensions.CacheCurve))
+ {
+ //Get pub key
+ byte[] key = await GetBrokerPubAsync();
+
+ alg.ImportSubjectPublicKeyInfo(key, out _);
+ //Verify the jwt
+ if (!jwt.Verify(alg, FBMDataCacheExtensions.CacheJwtAlgorithm))
+ {
+ //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();
+ }
+ //Verify token
+ if(Token().Equals(auth, StringComparison.Ordinal))
+ {
+ //Signal keepalive
+ KeepaliveSet.Set();
+ entity.CloseResponse(HttpStatusCode.OK);
+ return VfReturnType.VirtualSkip;
+ }
+ //Token invalid
+ entity.CloseResponse(HttpStatusCode.Forbidden);
+ return VfReturnType.VirtualSkip;
+ }
+ }
+}