diff options
author | vnugent <public@vaughnnugent.com> | 2023-07-13 13:20:25 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-07-13 13:20:25 -0400 |
commit | 2f674e79d42e7d36225fa9ac7ecefbc5bc62d325 (patch) | |
tree | c58999489f5391bc044e7a9bb3e557afe2860415 /lib/VNLib.Data.Caching.Extensions/src/ApiModel | |
parent | 1a8ab1457244d15b19ddcc94958f645f5ec2abc7 (diff) |
Checkpoint, kind of working clustering
Diffstat (limited to 'lib/VNLib.Data.Caching.Extensions/src/ApiModel')
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"); + } + } + } + + } +} |