From 1350c983c371fdd6a93596c8474345f9168284e1 Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 22 May 2024 15:28:54 -0400 Subject: Squashed commit of the following: commit 27fb5382d80d9bcfb4c65974bbae20c5e7b8ccbc Author: vnugent Date: Wed May 22 00:57:34 2024 -0400 feat: Vault environment vars commit 69f13e43dfdd8069459800ccc3039f45fc884814 Author: vnugent Date: Wed May 15 22:04:43 2024 -0400 fix: #3 Defer vault loading until a secret actually needs it commit c848787d4830a73e9ba93898897282be2f3752f2 Author: vnugent Date: Wed May 15 22:02:02 2024 -0400 package updates commit 21c6c85f540740ac29536a7091346a731aa85148 Author: vnugent Date: Wed May 15 22:01:16 2024 -0400 fix: #3 Error raised when managed password type disposed commit 8e77289041349b16536497f48f0c0a4ec6fe30f5 Author: vnugent Date: Thu May 2 15:44:42 2024 -0400 feat: #2 Middleware helpers, proj cleanup, fix sync secrets, vault client commit e0a5c85297516188e57b54d9b530b2482cb03eb0 Merge: a977dab 5ad520e Author: vnugent Date: Sat Apr 27 17:44:09 2024 -0400 Merge branch 'master' into develop commit a977dabef1dec915e00f755cb3ee3363aa9985f1 Author: vnugent Date: Sat Apr 27 17:26:35 2024 -0400 chore: package updates commit a2e2c3c4152d000b8df25c3c3fee14d491aab2c6 Merge: f03b727 87bfa83 Author: vnugent Date: Sat Apr 20 12:11:45 2024 -0400 Merge branch 'master' into develop commit f03b727d8f8e52f1dbd6293ea5c5a492c6d8e2da Author: vnugent Date: Sat Apr 20 12:02:07 2024 -0400 chore: Package updates --- .../src/VNLib.Plugins.Extensions.Data.csproj | 13 +- .../VNLib.Plugins.Extensions.Loading.Sql.csproj | 14 +- .../src/IAsyncLazy.cs | 9 ++ .../src/ManagedPasswordHashing.cs | 29 +++- .../src/Routing/MiddlewareHelpers.cs | 70 ++++++++++ .../src/Secrets/HCVaultClient.cs | 151 ++++++++------------- .../src/Secrets/IHCVaultClient.cs | 64 --------- .../src/Secrets/IKvVaultClient.cs | 64 +++++++++ .../src/Secrets/OnDemandSecret.cs | 15 +- .../src/Secrets/PluginSecretConstants.cs | 2 +- .../src/Secrets/PluginSecretStore.cs | 23 ++-- .../src/VNLib.Plugins.Extensions.Loading.csproj | 12 +- .../src/VNLib.Plugins.Extensions.Validation.csproj | 12 +- ...Lib.Plugins.Extensions.Loading.Sql.MYSql.csproj | 8 +- ...Lib.Plugins.Extensions.Loading.Sql.MySQL.csproj | 8 +- ...ib.Plugins.Extensions.Loading.Sql.SQLite.csproj | 10 +- ...Plugins.Extensions.Loading.Sql.SQLServer.csproj | 10 +- 17 files changed, 301 insertions(+), 213 deletions(-) create mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Routing/MiddlewareHelpers.cs delete mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IHCVaultClient.cs create mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IKvVaultClient.cs diff --git a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj index 465864d..c957b26 100644 --- a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj +++ b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj @@ -2,14 +2,16 @@ net8.0 + enable VNLib.Plugins.Extensions.Data VNLib.Plugins.Extensions.Data - enable True - latest-all True + + latest-all + Vaughn Nugent @@ -20,12 +22,11 @@ Copyright © 2024 Vaughn Nugent https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Extensions https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Data - - - README.md LICENSE + True + True @@ -47,7 +48,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj index 9df509b..5292a5d 100644 --- a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj +++ b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj @@ -2,13 +2,16 @@ net8.0 + enable VNLib.Plugins.Extensions.Loading.Sql VNLib.Plugins.Extensions.Loading.Sql - enable - latest-all True True + + + latest-all + Vaughn Nugent @@ -19,12 +22,11 @@ Copyright © 2024 Vaughn Nugent https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Extensions https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Loading.Sql - - - README.md LICENSE + True + True @@ -38,7 +40,7 @@ - + diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/IAsyncLazy.cs b/lib/VNLib.Plugins.Extensions.Loading/src/IAsyncLazy.cs index 482785c..cb907b9 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/IAsyncLazy.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/IAsyncLazy.cs @@ -52,6 +52,12 @@ namespace VNLib.Plugins.Extensions.Loading /// If the operation has not completed, throws an exception. /// T Value { get; } + + /// + /// Gets or allocates a task that represents the async result + /// + /// A task that represents the asynchronous lazy result that completes with the resulting value + Task AsTask(); } /// @@ -141,6 +147,9 @@ namespace VNLib.Plugins.Extensions.Loading /// public TaskAwaiter GetAwaiter() => _task.GetAwaiter(); + + /// + public Task AsTask() => _task; } #nullable enable diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs index 28b3a08..3718ea5 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.Loading @@ -25,11 +25,13 @@ using System; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Hashing; using VNLib.Utils; using VNLib.Utils.Memory; +using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Accounts; @@ -123,7 +125,11 @@ namespace VNLib.Plugins.Extensions.Loading if(config.TryGetValue("lib_path", out JsonElement manualLibPath)) { - SafeArgon2Library lib = VnArgon2.LoadCustomLibrary(manualLibPath.GetString()!, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories); + SafeArgon2Library lib = VnArgon2.LoadCustomLibrary( + manualLibPath.GetString()!, + System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories + ); + _ = plugin.RegisterForUnload(lib.Dispose); safeLib = lib; } @@ -154,6 +160,15 @@ namespace VNLib.Plugins.Extensions.Loading //Get the pepper from secret storage _pepper = plugin.GetSecretAsync(LoadingExtensions.PASSWORD_HASHING_KEY) .ToLazy(static sr => sr.GetFromBase64()); + + _ = _pepper.AsTask() + .ContinueWith(secT => { + plugin.Log.Error("Failed to load password pepper: {reason}", secT.Exception?.Message); + }, + default, + TaskContinuationOptions.OnlyOnFaulted, //Only run if an exception occured to notify the user during startup + TaskScheduler.Default + ); } public SecretProvider(PluginBase plugin) @@ -163,7 +178,8 @@ namespace VNLib.Plugins.Extensions.Loading //Get the pepper from secret storage _pepper = plugin.GetSecretAsync(LoadingExtensions.PASSWORD_HASHING_KEY) - .ToLazy(static sr => sr.GetFromBase64()); + .ToBase64Bytes() + .AsLazy(); } /// @@ -193,9 +209,10 @@ namespace VNLib.Plugins.Extensions.Loading protected override void Free() { - if (_pepper.Completed) - { - //Clear the pepper if set + Task pepperTask = _pepper.AsTask(); + //Only zero pepper value if the pepper was retrieved successfully + if (pepperTask.IsCompletedSuccessfully) + { MemoryUtil.InitializeBlock(_pepper.Value.AsSpan()); } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/MiddlewareHelpers.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/MiddlewareHelpers.cs new file mode 100644 index 0000000..6a0d848 --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/MiddlewareHelpers.cs @@ -0,0 +1,70 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: MiddlewareHelpers.cs +* +* MiddlewareHelpers.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading 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.Plugins.Extensions.Loading 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.Collections.Generic; +using System.Runtime.CompilerServices; + +using VNLib.Utils.Extensions; +using VNLib.Plugins.Essentials.Middleware; + +namespace VNLib.Plugins.Extensions.Loading.Routing +{ + /// + /// Provides helper extensions for http middleware + /// + public static class MiddlewareHelpers + { + private static readonly ConditionalWeakTable> _pluginMiddlewareList = new(); + + /// + /// Exports a single middlware instance to the collection for the plugin. + /// + /// + /// + /// A params array of middleware instances to export to the plugin + /// + /// WARNING: Adding middleware arrays explicitly to the plugin service pool will override + /// this function. All instances must be exposed though this function + /// + public static void ExportMiddleware(this PluginBase plugin, params T[] instances) where T : IHttpMiddleware + { + /* + * The runtime accepts an enumeration of IHttpMiddleware instances, so + * a list can just be exported as an enumerable instance + */ + static List OnCreate(PluginBase plugin) + { + List collection = new(1); + plugin.ExportService>(collection); + return collection; + } + + //Get the endpoint collection for the current plugin + List middlewares = _pluginMiddlewareList.GetValue(plugin, OnCreate); + + //Add the endpoint to the collection + instances.ForEach(mw => middlewares.Add(mw)); + } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs index a06f490..885f22f 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs @@ -30,7 +30,6 @@ using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Net.Security; -using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -50,7 +49,12 @@ using VNLib.Utils.Extensions; namespace VNLib.Plugins.Extensions.Loading { - internal sealed class HCVaultClient : VnDisposeable, IHCVaultClient + + /// + /// A concret implementation of a Hashicorp Vault client instance used to + /// retrieve key-value secrets from a server + /// + public sealed class HCVaultClient : VnDisposeable, IKvVaultClient { const string VaultTokenHeaderName = "X-Vault-Token"; const long MaxErrResponseContentLength = 8192; @@ -62,7 +66,7 @@ namespace VNLib.Plugins.Extensions.Loading private readonly int _kvVersion; private readonly IUnmangedHeap _bufferHeap; - HCVaultClient(string serverAddress, string hcToken, int kvVersion, bool trustCert, IUnmangedHeap heap) + private HCVaultClient(string serverAddress, string hcToken, int kvVersion, bool trustCert, IUnmangedHeap heap) { #pragma warning disable CA2000 // Dispose objects before losing scope HttpClientHandler handler = new() @@ -99,17 +103,17 @@ namespace VNLib.Plugins.Extensions.Loading /// Creates a new Hashicorp vault client with the given server address, token, and KV storage version /// /// The vault server address - /// The vault token used to connect to the vault server + /// The vault token used to connect to the vault server /// The hc vault Key value store version (must be 1 or 2) /// A value that tells the HTTP client to trust the Vault server's certificate even if it's not valid /// Heap instance to allocate internal buffers from /// The new client instance /// /// - public static HCVaultClient Create(string serverAddress, string hcToken, int kvVersion, bool trustCert, IUnmangedHeap heap) + public static HCVaultClient Create(string serverAddress, string token, int kvVersion, bool trustCert, IUnmangedHeap heap) { ArgumentException.ThrowIfNullOrEmpty(serverAddress); - ArgumentException.ThrowIfNullOrEmpty(hcToken); + ArgumentException.ThrowIfNullOrEmpty(token); ArgumentNullException.ThrowIfNull(heap); if(kvVersion != 1 && kvVersion != 2) @@ -117,7 +121,29 @@ namespace VNLib.Plugins.Extensions.Loading throw new ArgumentException($"Unsupported vault KV storage version {kvVersion}, must be either 1 or 2"); } - return new HCVaultClient(serverAddress, hcToken, kvVersion, trustCert, heap); + return new HCVaultClient(serverAddress, token, kvVersion, trustCert, heap); + } + + /// + /// Creates a new Hashicorp vault client from the default Vault environment + /// variables VAULT_ADDR and VAULT_TOKEN. From client documentation + /// + /// The hc vault Key value store version (must be 1 or 2) + /// A value that tells the HTTP client to trust the Vault server's certificate even if it's not valid + /// Heap instance to allocate internal buffers from + /// The new client instance + /// + /// + /// + public static HCVaultClient CreateFromEnv(int kvVersion, bool trustCert, IUnmangedHeap heap) + { + string address = Environment.GetEnvironmentVariable("VAULT_ADDR") + ?? throw new KeyNotFoundException("VAULT_ADDR environment variable not found"); + + string token = Environment.GetEnvironmentVariable("VAULT_TOKEN") + ?? throw new KeyNotFoundException("VAULT_TOKEN environment variable not found"); + + return Create(address, token, kvVersion, trustCert, heap); } /// @@ -137,10 +163,10 @@ namespace VNLib.Plugins.Extensions.Loading using HttpResponseMessage response = await _client.SendAsync(ms, HttpCompletionOption.ResponseHeadersRead); //Check if an error occured in the response - await ProcessVaultErrorResponseAsync(response, true); + await ProcessVaultErrorResponseAsync(response); //Read the response async - using SecretResponse res = await ReadSecretResponse(response.Content, true); + using SecretResponse res = await ReadSecretResponse(response.Content); return FromResponse(res, secretName); } @@ -160,90 +186,42 @@ namespace VNLib.Plugins.Extensions.Loading } /// + /// public ISecretResult? ReadSecret(string path, string mountPoint, string secretName) { - string secretPath = GetSecretPathForKvVersion(_kvVersion, path, mountPoint); - using HttpRequestMessage ms = GetRequestMessageForPath(secretPath); - - try - { - //Exec the response synchronously - using HttpResponseMessage response = _client.Send(ms, HttpCompletionOption.ResponseHeadersRead); - - /* - * It is safe to await the error result here because its - * already completed when the async flag is false - */ - ValueTask errTask = ProcessVaultErrorResponseAsync(response, false); - Debug.Assert(errTask.IsCompleted); - errTask.GetAwaiter().GetResult(); - - //Did not throw, handle a secret response - - ValueTask resTask = ReadSecretResponse(response.Content, false); - Debug.Assert(resTask.IsCompleted); - - //Always wrap response in using to clean memory - using SecretResponse res = resTask.GetAwaiter().GetResult(); + /* + * Since this method will syncrhonously block the calling thread, a new + * task must be created to ignore the current async context and run the + * funciton in an new context to block safely without causing a deadlock. + */ - return FromResponse(res, secretName); - } - catch (HttpRequestException he) when (he.InnerException is SocketException se) - { - throw se.SocketErrorCode switch - { - SocketError.HostNotFound => new HCVaultException("Failed to connect to Hashicorp Vault server, because it's DNS hostname could not be resolved"), - SocketError.ConnectionRefused => new HCVaultException("Failed to establish a TCP connection to the vault server, the server refused the connection"), - _ => new HCVaultException("Failed to establish a TCP connection to the vault server, see inner exception", se), - }; - } - catch (Exception ex) + Task asAsync = Task.Run(() => ReadSecretAsync(path, mountPoint, secretName)); + + if(!asAsync.Wait(ClientDefaultTimeout)) { - throw new HCVaultException("Failed to retreive secret from Hashicorp Vault server, see inner exception", ex); + throw new TimeoutException("Failed to retreive the secret from the vault in the configured timeout period"); } + + return asAsync.Result; } - private ValueTask ReadSecretResponse(HttpContent content, bool async) + private async Task ReadSecretResponse(HttpContent content) { - SecretResponse res = new(DefaultBufferSize, _bufferHeap); + SecretResponse response = new(DefaultBufferSize, _bufferHeap); + try { - if (async) - { - return ReadStreamAsync(content, res); - } - else - { - //Read into a memory stream - content.CopyTo(res.StreamData, null, default); - res.ResetStream(); + await content.CopyToAsync(response.StreamData); - return ValueTask.FromResult(res); - } + response.ResetStream(); + + return response; } catch { - res.Dispose(); + response.Dispose(); throw; } - - async static ValueTask ReadStreamAsync(HttpContent content, SecretResponse response) - { - try - { - await content.CopyToAsync(response.StreamData); - - response.ResetStream(); - - return response; - } - catch - { - response.Dispose(); - throw; - } - } - } private static string GetSecretPathForKvVersion(int version, string path, string mount) @@ -288,7 +266,7 @@ namespace VNLib.Plugins.Extensions.Loading return null; } - private static ValueTask ProcessVaultErrorResponseAsync(HttpResponseMessage response, bool async) + private static ValueTask ProcessVaultErrorResponseAsync(HttpResponseMessage response) { if (response.IsSuccessStatusCode) { @@ -322,12 +300,12 @@ namespace VNLib.Plugins.Extensions.Loading ); } - return async ? ExceptionsFromContentAsync(response) : ExceptionsFromContent(response); + return ExceptionsFromContentAsync(response); static ValueTask ExceptionFromVaultErrors(HttpStatusCode code, VaultErrorMessage? errs) { //If the error message is null, raise an exception - if (errs == null || errs.Errors == null || errs.Errors.Length == 0) + if (errs?.Errors is null || errs.Errors.Length == 0) { return ValueTask.FromException( new HttpRequestException($"Failed to fetch secret from vault with error code {code}") @@ -352,19 +330,6 @@ namespace VNLib.Plugins.Extensions.Loading await ExceptionFromVaultErrors(response.StatusCode, errs); } - - static ValueTask ExceptionsFromContent(HttpResponseMessage response) - { -#pragma warning disable CA1849 // Call async methods when in an async method - - //Read the error content stream and deserialize - using Stream stream = response.Content.ReadAsStream(); - VaultErrorMessage? errs = JsonSerializer.Deserialize(stream); - -#pragma warning restore CA1849 // Call async methods when in an async method - - return ExceptionFromVaultErrors(response.StatusCode, errs); - } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IHCVaultClient.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IHCVaultClient.cs deleted file mode 100644 index aab2541..0000000 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IHCVaultClient.cs +++ /dev/null @@ -1,64 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.Loading -* File: IHCVaultClient.cs -* -* IHCVaultClient.cs is part of VNLib.Plugins.Extensions.Loading which is -* part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.Loading 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.Plugins.Extensions.Loading 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.Http; -using System.Threading.Tasks; - -namespace VNLib.Plugins.Extensions.Loading -{ - /// - /// A Hashicorp Vault client for reading secrets from a vault server - /// - public interface IHCVaultClient - { - /// - /// Reads a single KeyValue secret from the vault server asyncrhonously and returns the result - /// or null if the secret does not exist - /// - /// The path to the item within the store - /// The vault mount points - /// The name of the secret within the property array to retrieve - /// The secret wrapper if found, null otherwise - /// - /// - /// - /// - Task ReadSecretAsync(string path, string mountPoint, string secretName); - - /// - /// Reads a single KeyValue secret from the vault server syncrhonously and returns the result - /// or null if the secret does not exist - /// - /// The path to the item within the store - /// The vault mount points - /// The name of the secret within the property array to retrieve - /// The secret wrapper if found, null otherwise - /// - /// - /// - /// - ISecretResult? ReadSecret(string path, string mountPoint, string secretName); - } -} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IKvVaultClient.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IKvVaultClient.cs new file mode 100644 index 0000000..77579ef --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IKvVaultClient.cs @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: IKvVaultClient.cs +* +* IKvVaultClient.cs is part of VNLib.Plugins.Extensions.Loading which is +* part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading 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.Plugins.Extensions.Loading 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.Http; +using System.Threading.Tasks; + +namespace VNLib.Plugins.Extensions.Loading +{ + /// + /// A secret client interace for reading secrets from a vault server + /// + public interface IKvVaultClient + { + /// + /// Reads a single KeyValue secret from the vault server asyncrhonously and returns the result + /// or null if the secret does not exist + /// + /// The path to the item within the store + /// The vault mount points + /// The name of the secret within the property array to retrieve + /// The secret wrapper if found, null otherwise + /// + /// + /// + /// + Task ReadSecretAsync(string path, string mountPoint, string secretName); + + /// + /// Reads a single KeyValue secret from the vault server syncrhonously and returns the result + /// or null if the secret does not exist + /// + /// The path to the item within the store + /// The vault mount points + /// The name of the secret within the property array to retrieve + /// The secret wrapper if found, null otherwise + /// + /// + /// + /// + ISecretResult? ReadSecret(string path, string mountPoint, string secretName); + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs index 6e6d560..edbef8c 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs @@ -35,13 +35,20 @@ using System.Collections.Generic; using VNLib.Utils.Memory; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; +using VNLib.Utils.Resources; using static VNLib.Plugins.Extensions.Loading.PluginSecretConstants; namespace VNLib.Plugins.Extensions.Loading { - internal sealed class OnDemandSecret(PluginBase plugin, string secretName, IHCVaultClient? vault) : IOnDemandSecret + internal sealed class OnDemandSecret(PluginBase plugin, string secretName, Func vaultCb) : IOnDemandSecret { + /* + * Defer loading vault until needed by a vault secret. This avoids loading the vault client + * if no secrets are needed from the vault. + */ + private readonly LazyInitializer vault = new(vaultCb); + public string SecretName { get; } = secretName ?? throw new ArgumentNullException(nameof(secretName)); /// @@ -175,16 +182,16 @@ namespace VNLib.Plugins.Extensions.Loading string secret = path[(lastSep + 1)..].ToString(); //Try load client - _ = vault ?? throw new KeyNotFoundException("Vault client not found"); + _ = vault.Instance ?? throw new KeyNotFoundException("Vault client not found"); if (async) { - Task asTask = Task.Run(() => vault.ReadSecretAsync(secret, mount, secretTableKey)); + Task asTask = Task.Run(() => vault.Instance.ReadSecretAsync(secret, mount, secretTableKey)); return new ValueTask(asTask); } else { - ISecretResult? result = vault.ReadSecret(secret, mount, secretTableKey); + ISecretResult? result = vault.Instance.ReadSecret(secret, mount, secretTableKey); return new ValueTask(result); } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretConstants.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretConstants.cs index 5c5a644..54bfa17 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretConstants.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretConstants.cs @@ -33,7 +33,7 @@ namespace VNLib.Plugins.Extensions.Loading public const string VAULT_TOKEN_KEY = "token"; public const string VAULT_ROLE_KEY = "role"; public const string VAULT_SECRET_KEY = "secret"; - public const string VAULT_TOKNE_ENV_NAME = "VNLIB_PLUGINS_VAULT_TOKEN"; + public const string VAULT_TOKEN_ENV_NAME = "VAULT_TOKEN"; public const string VAULT_KV_VERSION_KEY = "kv_version"; public const string VAULT_URL_KEY = "url"; diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs index 6b20e30..ec3871f 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs @@ -40,16 +40,18 @@ namespace VNLib.Plugins.Extensions.Loading /// The plugin instance to get secrets from public readonly struct PluginSecretStore(PluginBase plugin) : IEquatable { + const int HCVaultDefaultKvVersion = 2; + private readonly PluginBase _plugin = plugin; /// /// Gets the ambient vault client for the current plugin /// if the configuration is loaded, null otherwise /// - /// The ambient if loaded, null otherwise + /// The ambient if loaded, null otherwise /// /// - public IHCVaultClient? GetVaultClient() => LoadingExtensions.GetOrCreateSingleton(_plugin, TryGetVaultLoader); + public IKvVaultClient? GetVaultClient() => LoadingExtensions.GetOrCreateSingleton(_plugin, TryGetVaultLoader); private static HCVaultClient? TryGetVaultLoader(PluginBase pbase) { @@ -63,21 +65,19 @@ namespace VNLib.Plugins.Extensions.Loading //try get server address creds from config string serverAddress = conf.GetRequiredProperty(VAULT_URL_KEY, p => p.GetString()!); - bool trustCert = conf.TryGetValue(VAULT_TRUST_CERT_KEY, out JsonElement trustCertEl) && trustCertEl.GetBoolean(); + bool trustCert = conf.GetValueOrDefault(VAULT_TRUST_CERT_KEY, el => el.GetBoolean(), false); - int version = 2; //Default to version 2 now string? authToken; - - //Get authentication method from config + if (conf.TryGetValue(VAULT_TOKEN_KEY, out JsonElement tokenEl)) { //Init token authToken = tokenEl.GetString(); } //Try to get the token as an environment variable - else if (Environment.GetEnvironmentVariable(VAULT_TOKNE_ENV_NAME) != null) + else if (Environment.GetEnvironmentVariable(VAULT_TOKEN_ENV_NAME) != null) { - authToken = Environment.GetEnvironmentVariable(VAULT_TOKNE_ENV_NAME)!; + authToken = Environment.GetEnvironmentVariable(VAULT_TOKEN_ENV_NAME)!; } else { @@ -87,10 +87,7 @@ namespace VNLib.Plugins.Extensions.Loading _ = authToken ?? throw new KeyNotFoundException($"Failed to load the vault authentication method from {VAULT_OBJECT_NAME}"); //Check for vault kv version, otherwise use the default - if (conf.TryGetValue(VAULT_KV_VERSION_KEY, out JsonElement kvVersionEl)) - { - version = kvVersionEl.GetInt32(); - } + int version = conf.GetValueOrDefault(VAULT_KV_VERSION_KEY, el => el.GetInt32(), HCVaultDefaultKvVersion); //create vault client, invalid or nulls will raise exceptions here return HCVaultClient.Create(serverAddress, authToken, version, trustCert, MemoryUtil.Shared); @@ -114,7 +111,7 @@ namespace VNLib.Plugins.Extensions.Loading public IOnDemandSecret GetOnDemandSecret(string secretName) { ArgumentException.ThrowIfNullOrWhiteSpace(secretName); - return new OnDemandSecret(_plugin, secretName, GetVaultClient()); + return new OnDemandSecret(_plugin, secretName, GetVaultClient); } /// diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj b/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj index be21770..8fa72fb 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj +++ b/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj @@ -2,13 +2,16 @@ net8.0 + enable VNLib.Plugins.Extensions.Loading VNLib.Plugins.Extensions.Loading True - enable - latest-all True + + + latest-all + Vaughn Nugent @@ -19,12 +22,11 @@ Copyright © 2024 Vaughn Nugent https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Extensions https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Loading - - - README.md LICENSE + True + True diff --git a/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj b/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj index ebd8910..754bec6 100644 --- a/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj +++ b/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj @@ -2,14 +2,17 @@ net8.0 + enable VNLib.Plugins.Extensions.Validation VNLib.Plugins.Extensions.Validation - enable - latest-all True True + + latest-all + + Vaughn Nugent Vaughn Nugent @@ -19,12 +22,11 @@ Copyright © 2024 Vaughn Nugent https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Sessions https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Validation - - - README.md LICENSE + True + True diff --git a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj index 9f5c2bf..3d52e85 100644 --- a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj +++ b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj @@ -2,14 +2,17 @@ net8.0 + enable VNLib.Plugins.Extensions.Sql.MySQL VNLib.Plugins.Extensions.Sql - enable - disable True true + + + latest-all + VNLib.Plugins.Extensions.Sql.MySQL @@ -22,6 +25,7 @@ A runtime asset library that provides MySQL interfaces for ADO and EFCore SQL server clients README.md LICENSE + True diff --git a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MySQL.csproj b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MySQL.csproj index 9f5c2bf..3d52e85 100644 --- a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MySQL.csproj +++ b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MySQL.csproj @@ -2,14 +2,17 @@ net8.0 + enable VNLib.Plugins.Extensions.Sql.MySQL VNLib.Plugins.Extensions.Sql - enable - disable True true + + + latest-all + VNLib.Plugins.Extensions.Sql.MySQL @@ -22,6 +25,7 @@ A runtime asset library that provides MySQL interfaces for ADO and EFCore SQL server clients README.md LICENSE + True diff --git a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj index d251923..753deaf 100644 --- a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj +++ b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj @@ -2,14 +2,17 @@ net8.0 + enable VNLib.Plugins.Extensions.Sql.SQLite VNLib.Plugins.Extensions.Sql - enable - disable True true + + + latest-all + VNLib.Plugins.Extensions.Sql.SQLite @@ -22,6 +25,7 @@ A runtime asset library that provides SQLite interfaces for ADO and EFCore SQL server clients README.md LICENSE + True @@ -45,7 +49,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj index 897f9df..97c6096 100644 --- a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj +++ b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj @@ -2,14 +2,17 @@ net8.0 + enable VNLib.Plugins.Extensions.Sql.SqlServer VNLib.Plugins.Extensions.Sql - enable - disable True true + + + latest-all + VNLib.Plugins.Extensions.Sql.SqlServer @@ -22,6 +25,7 @@ A runtime asset library that provides SqlServer interfaces for ADO and EFCore SQL server clients README.md LICENSE + True @@ -45,7 +49,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + -- cgit