aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Data.Caching.Extensions/src/ApiModel
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-07-13 13:20:25 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-07-13 13:20:25 -0400
commit2f674e79d42e7d36225fa9ac7ecefbc5bc62d325 (patch)
treec58999489f5391bc044e7a9bb3e557afe2860415 /lib/VNLib.Data.Caching.Extensions/src/ApiModel
parent1a8ab1457244d15b19ddcc94958f645f5ec2abc7 (diff)
Checkpoint, kind of working clustering
Diffstat (limited to 'lib/VNLib.Data.Caching.Extensions/src/ApiModel')
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/ApiModel/CacheSiteAdapter.cs65
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/ApiModel/DiscoveryRequest.cs41
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/ApiModel/GetConfigRequest.cs42
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/ApiModel/ICacheConnectionRequest.cs47
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/ApiModel/NegotationRequest.cs41
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/ApiModel/ServiceEndpoints.cs176
6 files changed, 412 insertions, 0 deletions
diff --git a/lib/VNLib.Data.Caching.Extensions/src/ApiModel/CacheSiteAdapter.cs b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/CacheSiteAdapter.cs
new file mode 100644
index 0000000..99acfd5
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/CacheSiteAdapter.cs
@@ -0,0 +1,65 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: CacheSiteAdapter.cs
+*
+* CacheSiteAdapter.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions 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.
+*
+* VNLib.Data.Caching.Extensions 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.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using RestSharp;
+
+using VNLib.Net.Rest.Client;
+using VNLib.Net.Rest.Client.Construction;
+
+namespace VNLib.Data.Caching.Extensions.ApiModel
+{
+ /// <summary>
+ /// A site adapter for cache REST api requests
+ /// </summary>
+ internal sealed class CacheSiteAdapter : RestSiteAdapterBase
+ {
+ protected override RestClientPool Pool { get; }
+
+ public CacheSiteAdapter(int maxClients)
+ {
+ //Configure connection pool
+ Pool = new(maxClients, new RestClientOptions()
+ {
+ MaxTimeout = 10 * 1000,
+ FollowRedirects = false,
+ Encoding = Encoding.UTF8,
+ AutomaticDecompression = DecompressionMethods.All,
+ ThrowOnAnyError = true
+ });
+ }
+
+ public override void OnResponse(RestResponse response)
+ { }
+
+ public override Task WaitAsync(CancellationToken cancellation = default)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/ApiModel/DiscoveryRequest.cs b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/DiscoveryRequest.cs
new file mode 100644
index 0000000..22b11aa
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/DiscoveryRequest.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: DiscoveryRequest.cs
+*
+* DiscoveryRequest.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions 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.
+*
+* VNLib.Data.Caching.Extensions 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 VNLib.Data.Caching.Extensions.Clustering;
+
+namespace VNLib.Data.Caching.Extensions.ApiModel
+{
+ /// <summary>
+ /// A request message for a discovery request
+ /// </summary>
+ /// <param name="DiscoveryUrl">The discovery endpoint to connec to</param>
+ /// <param name="Config">The local client configuration</param>
+ internal record class DiscoveryRequest(Uri DiscoveryUrl, CacheClientConfiguration Config)
+ : ICacheConnectionRequest
+ {
+ ///<inheritdoc/>
+ public string? Challenge { get; set; }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/ApiModel/GetConfigRequest.cs b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/GetConfigRequest.cs
new file mode 100644
index 0000000..43cef4c
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/GetConfigRequest.cs
@@ -0,0 +1,42 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: GetConfigRequest.cs
+*
+* GetConfigRequest.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions 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.
+*
+* VNLib.Data.Caching.Extensions 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 VNLib.Data.Caching.Extensions.Clustering;
+
+namespace VNLib.Data.Caching.Extensions.ApiModel
+{
+ /// <summary>
+ /// A request to get the cache configuration from a cache server's
+ /// well-known configuration endpoint
+ /// </summary>
+ /// <param name="WellKnownEp">The well-known configuration endpoint url</param>
+ /// <param name="Config">The client cache configuration</param>
+ internal record class GetConfigRequest(Uri WellKnownEp, CacheClientConfiguration Config)
+ : ICacheConnectionRequest
+ {
+ ///<inheritdoc/>
+ public string? Challenge { get; set; }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/ApiModel/ICacheConnectionRequest.cs b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/ICacheConnectionRequest.cs
new file mode 100644
index 0000000..5fb7ba7
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/ICacheConnectionRequest.cs
@@ -0,0 +1,47 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: ICacheConnectionRequest.cs
+*
+* ICacheConnectionRequest.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions 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.
+*
+* VNLib.Data.Caching.Extensions 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 VNLib.Data.Caching.Extensions.Clustering;
+
+namespace VNLib.Data.Caching.Extensions.ApiModel
+{
+ /// <summary>
+ /// Represents a request to connect to a cache server.
+ /// </summary>
+ internal interface ICacheConnectionRequest
+ {
+ /// <summary>
+ /// The <see cref="CacheClientConfiguration"/> used to configure, authenticate, and
+ /// verify messages sent to and received from cache servers.
+ /// </summary>
+ CacheClientConfiguration Config { get; }
+
+ /// <summary>
+ /// An optional challenge string to be used during the authentication
+ /// process. When set, is sent in the request JWT, and is expected to
+ /// be returned in the response JWT.
+ /// </summary>
+ string? Challenge { get; set; }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/ApiModel/NegotationRequest.cs b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/NegotationRequest.cs
new file mode 100644
index 0000000..5842586
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/NegotationRequest.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: NegotationRequest.cs
+*
+* NegotationRequest.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions 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.
+*
+* VNLib.Data.Caching.Extensions 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 VNLib.Data.Caching.Extensions.Clustering;
+
+namespace VNLib.Data.Caching.Extensions.ApiModel
+{
+ /// <summary>
+ /// A request to negotiate a new connection with a cache server
+ /// </summary>
+ /// <param name="ConnectUrl">The cache endpoint uri to connec to</param>
+ /// <param name="Config">The client cache configuration</param>
+ internal record class NegotationRequest(Uri ConnectUrl, CacheClientConfiguration Config)
+ : ICacheConnectionRequest
+ {
+ ///<inheritdoc/>
+ public string? Challenge { get; set; }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/ApiModel/ServiceEndpoints.cs b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/ServiceEndpoints.cs
new file mode 100644
index 0000000..c0de4a3
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/ApiModel/ServiceEndpoints.cs
@@ -0,0 +1,176 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: ServiceEndpoints.cs
+*
+* ServiceEndpoints.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions 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.
+*
+* VNLib.Data.Caching.Extensions 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.Security;
+using System.Text.Json;
+
+using RestSharp;
+
+using VNLib.Net.Http;
+using VNLib.Hashing;
+using VNLib.Hashing.IdentityUtility;
+using VNLib.Net.Rest.Client.Construction;
+using ContentType = VNLib.Net.Http.ContentType;
+using VNLib.Data.Caching.Extensions.Clustering;
+
+namespace VNLib.Data.Caching.Extensions.ApiModel
+{
+ /*
+ * Defines the cache endpoints, builds and routes request messages to the
+ * server enpoints. In effect defines the client api for cache services.
+ *
+ * This class also define methods for authentication and message verification
+ */
+
+ internal static class ServiceEndpoints
+ {
+ private static readonly TimeSpan MaxTimeDisparity = TimeSpan.FromSeconds(10);
+
+ /// <summary>
+ /// Get the endpoint definition for cache services for the site adapter
+ /// </summary>
+ internal static IRestEndpointDefinition Definition { get; } = new EndpointBuilder();
+
+ private class EndpointBuilder : IRestEndpointDefinition
+ {
+ ///<inheritdoc/>
+ public void BuildRequest(IRestSiteAdapter site, IRestEndpointBuilder builder)
+ {
+ //Define cache service endpoints/requests
+
+ builder.WithEndpoint<DiscoveryRequest>()
+ .WithUrl(e => e.DiscoveryUrl)
+ .WithMethod(Method.Get)
+ //Accept text response (it should be a jwt)
+ .WithHeader("Accept", HttpHelpers.GetContentTypeString(ContentType.Text))
+ .WithHeader("Authorization", BuildDiscoveryAuthToken)
+ //Verify jwt responses
+ .OnResponse(VerifyJwtResponse);
+
+ builder.WithEndpoint<NegotationRequest>()
+ .WithUrl(e => e.ConnectUrl)
+ .WithMethod(Method.Get)
+ //Accept text response (its should be a jwt)
+ .WithHeader("Accept", HttpHelpers.GetContentTypeString(ContentType.Text))
+ .WithHeader("Authorization", BuildDiscoveryAuthToken)
+ //Verify jwt responses
+ .OnResponse(VerifyJwtResponse);
+
+ //Well known endpoint does not require authentication
+ builder.WithEndpoint<GetConfigRequest>()
+ .WithUrl(gc => gc.WellKnownEp)
+ .WithMethod(Method.Get)
+ //Responses should be a signed jwt
+ .WithHeader("Accept", HttpHelpers.GetContentTypeString(ContentType.Text))
+ //Verify jwt responses
+ .OnResponse(VerifyJwtResponse);
+ }
+ }
+
+
+ private static string BuildDiscoveryAuthToken(ICacheConnectionRequest request)
+ {
+ request.Challenge = RandomHash.GetRandomBase32(24);
+
+ //Build request jwt
+ using JsonWebToken jwt = new();
+ jwt.WriteHeader(request.Config.AuthManager.GetJwtHeader());
+
+ //See if the supplied config is for a cache node
+ CacheNodeConfiguration? cnc = request.Config as CacheNodeConfiguration;
+
+ //Init claim
+ JwtPayload claim = jwt.InitPayloadClaim();
+
+ claim.AddClaim("chl", request.Challenge)
+ .AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
+
+ if (!string.IsNullOrWhiteSpace(cnc?.NodeId))
+ {
+ /*
+ * The unique node id so the other nodes know to load the
+ * proper event queue for the current server
+ */
+ claim.AddClaim("sub", cnc.NodeId);
+ }
+
+ claim.CommitClaims();
+
+ //sign the jwt
+ request.Config.AuthManager.SignJwt(jwt);
+
+ //Compile the jwt
+ return jwt.Compile();
+ }
+
+ private static void VerifyJwtResponse(ICacheConnectionRequest req, RestResponse response)
+ {
+ byte[] data = response.RawBytes ?? throw new ArgumentException("Server response was empty, cannot continue");
+
+ //If node config then set the is-node flag
+ bool isNode = req.Config is CacheNodeConfiguration;
+
+ //Response is jwt
+ using JsonWebToken responseJwt = JsonWebToken.ParseRaw(data);
+
+ //Verify the jwt
+ if (!req.Config.AuthManager.VerifyJwt(responseJwt, isNode))
+ {
+ throw new SecurityException("Failed to verify the discovery server's challenge, cannot continue");
+ }
+
+ //get payload as a document
+ using JsonDocument doc = responseJwt.GetPayload();
+
+ //Verify iat times
+ long iatSec = doc.RootElement.GetProperty("iat").GetInt64();
+
+ //Get dto
+ DateTimeOffset iat = DateTimeOffset.FromUnixTimeSeconds(iatSec);
+
+ DateTimeOffset now = DateTimeOffset.UtcNow;
+
+ //Verify iat is not before or after the current time with the disparity
+ if (iat.Add(MaxTimeDisparity) < now || iat.Subtract(MaxTimeDisparity) > now)
+ {
+ throw new SecurityException("Server returned a request that has expired. Please check your system clock");
+ }
+
+ //If a challenge is set, verify it
+ if (req.Challenge != null)
+ {
+ //Verify challenge
+ string challenge = doc.RootElement.GetProperty("chl").GetString()
+ ?? throw new SecurityException("Server did not return a challenge");
+
+ if (!challenge.Equals(req.Challenge, StringComparison.Ordinal))
+ {
+ throw new SecurityException("Server returned an invalid challenge");
+ }
+ }
+ }
+
+ }
+}