diff options
author | vnugent <public@vaughnnugent.com> | 2023-04-08 16:43:52 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-04-08 16:43:52 -0400 |
commit | e77477b81e5623502b19db0fb29d4ea88c26b934 (patch) | |
tree | e91be0069d011c86ea138a8561c12b5df9b0005c /lib/VNLib.Plugins.Extensions.Loading/src | |
parent | 208ae0f26408abe7f63a95bc28cb8e0bdb673efa (diff) |
Passwords singlton and user-loading
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Loading/src')
-rw-r--r-- | lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs | 18 | ||||
-rw-r--r-- | lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationValidationException.cs (renamed from lib/VNLib.Plugins.Extensions.Loading/src/ConfigrationValidationException.cs) | 12 | ||||
-rw-r--r-- | lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs | 111 | ||||
-rw-r--r-- | lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs | 211 | ||||
-rw-r--r-- | lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs | 7 |
5 files changed, 235 insertions, 124 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs index 0ae6ed6..190c153 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs @@ -221,12 +221,12 @@ namespace VNLib.Plugins.Extensions.Loading /// <summary> /// Deserialzes the configuration to the desired object and calls its /// <see cref="IOnConfigValidation.Validate"/> method. Validation exceptions - /// are wrapped in a <see cref="ConfigrationValidationException"/> + /// are wrapped in a <see cref="ConfigurationValidationException"/> /// </summary> /// <typeparam name="T"></typeparam> /// <param name="scope"></param> /// <returns></returns> - /// <exception cref="ConfigrationValidationException"></exception> + /// <exception cref="ConfigurationValidationException"></exception> public static T DeserialzeAndValidate<T>(this IConfigScope scope) where T : IOnConfigValidation { T conf = scope.Deserialze<T>(); @@ -236,7 +236,7 @@ namespace VNLib.Plugins.Extensions.Loading } catch(Exception ex) { - throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(T).Name}", ex); + throw new ConfigurationValidationException($"Configuration validation failed for type {typeof(T).Name}", ex); } return conf; } @@ -261,7 +261,7 @@ namespace VNLib.Plugins.Extensions.Loading /// and deserializes it into the desired type. /// <para> /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.Validate"/> - /// method is invoked, and exceptions are warpped in <see cref="ConfigrationValidationException"/> + /// method is invoked, and exceptions are warpped in <see cref="ConfigurationValidationException"/> /// </para> /// <para> /// If the type inherits <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync(PluginBase)"/> @@ -271,7 +271,7 @@ namespace VNLib.Plugins.Extensions.Loading /// <typeparam name="TConfig">The configuration type</typeparam> /// <param name="plugin"></param> /// <returns>The deserialzed configuration element</returns> - /// <exception cref="ConfigrationValidationException"></exception> + /// <exception cref="ConfigurationValidationException"></exception> public static TConfig GetConfigElement<TConfig>(this PluginBase plugin) { //Deserialze the element @@ -286,7 +286,7 @@ namespace VNLib.Plugins.Extensions.Loading } catch (Exception ex) { - throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex); + throw new ConfigurationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex); } } @@ -304,7 +304,7 @@ namespace VNLib.Plugins.Extensions.Loading /// and deserializes it into the desired type. /// <para> /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.Validate"/> - /// method is invoked, and exceptions are warpped in <see cref="ConfigrationValidationException"/> + /// method is invoked, and exceptions are warpped in <see cref="ConfigurationValidationException"/> /// </para> /// <para> /// If the type inherits <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync(PluginBase)"/> @@ -315,7 +315,7 @@ namespace VNLib.Plugins.Extensions.Loading /// <param name="plugin"></param> /// <param name="elementName">The configuration element name override</param> /// <returns>The deserialzed configuration element</returns> - /// <exception cref="ConfigrationValidationException"></exception> + /// <exception cref="ConfigurationValidationException"></exception> public static TConfig GetConfigElement<TConfig>(this PluginBase plugin, string elementName) { //Deserialze the element @@ -330,7 +330,7 @@ namespace VNLib.Plugins.Extensions.Loading } catch (Exception ex) { - throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex); + throw new ConfigurationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex); } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigrationValidationException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationValidationException.cs index 83ce558..ebf4d9e 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigrationValidationException.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationValidationException.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Plugins.Extensions.Loading -* File: ConfigrationValidationException.cs +* File: ConfigurationValidationException.cs * -* ConfigrationValidationException.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger +* ConfigurationValidationException.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 @@ -29,14 +29,14 @@ namespace VNLib.Plugins.Extensions.Loading /// <summary> /// An exception raised when a configuration validation exception has occured /// </summary> - public class ConfigrationValidationException : Exception + public class ConfigurationValidationException : Exception { - public ConfigrationValidationException(string message) : base(message) + public ConfigurationValidationException(string message) : base(message) {} - public ConfigrationValidationException(string message, Exception innerException) : base(message, innerException) + public ConfigurationValidationException(string message, Exception innerException) : base(message, innerException) {} - public ConfigrationValidationException() + public ConfigurationValidationException() {} } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs index 3e151b4..5511398 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs @@ -32,14 +32,11 @@ using System.Threading.Tasks; using System.Collections.Generic; using System.Runtime.CompilerServices; -using VNLib.Utils; -using VNLib.Utils.Memory; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; -using VNLib.Plugins.Essentials.Accounts; namespace VNLib.Plugins.Extensions.Loading -{ +{ /// <summary> /// Provides common loading (and unloading when required) extensions for plugins @@ -54,6 +51,7 @@ namespace VNLib.Plugins.Extensions.Loading public const string DEBUG_CONFIG_KEY = "debug"; public const string SECRETS_CONFIG_KEY = "secrets"; public const string PASSWORD_HASHING_KEY = "passwords"; + public const string CUSTOM_PASSWORD_ASM_KEY = "custom_asm"; /* * Plugin local cache used for storing singletons for a plugin instance @@ -95,23 +93,6 @@ namespace VNLib.Plugins.Extensions.Loading public static T GetOrCreateSingleton<T>(PluginBase plugin, Func<PluginBase, T> serviceFactory) => (T)GetOrCreateSingleton(plugin, typeof(T), p => serviceFactory(p)!); - - /// <summary> - /// Gets the plugins ambient <see cref="PasswordHashing"/> if loaded, or loads it if required. This class will - /// be unloaded when the plugin us unloaded. - /// </summary> - /// <param name="plugin"></param> - /// <returns>The ambient <see cref="PasswordHashing"/></returns> - /// <exception cref="OverflowException"></exception> - /// <exception cref="KeyNotFoundException"></exception> - /// <exception cref="ObjectDisposedException"></exception> - public static IPasswordHashingProvider GetPasswords(this PluginBase plugin) - { - plugin.ThrowIfUnloaded(); - //Check if a password configuration element is loaded, otherwise load with defaults - return plugin.GetOrCreateSingleton<SecretProvider>().Passwords; - } - /// <summary> /// Loads an assembly into the current plugin's load context and will unload when disposed /// or the plugin is unloaded from the host application. @@ -551,93 +532,5 @@ namespace VNLib.Plugins.Extensions.Loading return lazyFactory; } } - - [ConfigurationName(PASSWORD_HASHING_KEY, Required = false)] - private sealed class SecretProvider : VnDisposeable, ISecretProvider, IAsyncConfigurable - { - private byte[]? _pepper; - private Exception? _error; - - public SecretProvider(PluginBase plugin, IConfigScope config) - { - if(config.TryGetValue("args", out JsonElement el)) - { - //Convert to dict - IReadOnlyDictionary<string, JsonElement> hashingArgs = el.EnumerateObject().ToDictionary(static k => k.Name, static v => v.Value); - - //Get hashing arguments - uint saltLen = hashingArgs["salt_len"].GetUInt32(); - uint hashLen = hashingArgs["hash_len"].GetUInt32(); - uint timeCost = hashingArgs["time_cost"].GetUInt32(); - uint memoryCost = hashingArgs["memory_cost"].GetUInt32(); - uint parallelism = hashingArgs["parallelism"].GetUInt32(); - //Load passwords - Passwords = new(this, (int)saltLen, timeCost, memoryCost, parallelism, hashLen); - } - else - { - Passwords = new(this); - } - } - - public SecretProvider(PluginBase plugin) - { - Passwords = new(this); - } - - - public PasswordHashing Passwords { get; } - - ///<inheritdoc/> - public int BufferSize - { - get - { - Check(); - return _pepper!.Length; - } - } - - public ERRNO GetSecret(Span<byte> buffer) - { - Check(); - //Coppy pepper to buffer - _pepper.CopyTo(buffer); - //Return pepper length - return _pepper!.Length; - } - - protected override void Check() - { - base.Check(); - if(_error != null) - { - throw _error; - } - } - - protected override void Free() - { - //Clear the pepper if set - MemoryUtil.InitializeBlock(_pepper.AsSpan()); - } - - public async Task ConfigureServiceAsync(PluginBase plugin) - { - try - { - //Get the pepper from secret storage - _pepper = await plugin.TryGetSecretAsync(PASSWORD_HASHING_KEY).ToBase64Bytes(); - } - catch (Exception ex) - { - //Store exception for re-propagation - _error = ex; - - //Propagate exception to system - throw; - } - } - } } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs new file mode 100644 index 0000000..522bfae --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs @@ -0,0 +1,211 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: ManagedPasswordHashing.cs +* +* ManagedPasswordHashing.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.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using System.Collections.Generic; + +using VNLib.Utils; +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; +using VNLib.Plugins.Essentials.Accounts; + +namespace VNLib.Plugins.Extensions.Loading +{ + + /// <summary> + /// A plugin configurable <see cref="IPasswordHashingProvider"/> managed implementation. Users may load custom + /// assemblies backing instances of this class or configure the <see cref="PasswordHashing"/> implementation + /// </summary> + [ConfigurationName(LoadingExtensions.PASSWORD_HASHING_KEY, Required = false)] + public sealed class ManagedPasswordHashing : IPasswordHashingProvider + { + public ManagedPasswordHashing(PluginBase plugin, IConfigScope config) + { + //Check for custom hashing assembly + if (config.TryGetValue(LoadingExtensions.CUSTOM_PASSWORD_ASM_KEY, out JsonElement el)) + { + string customAsm = el.GetString() ?? throw new KeyNotFoundException("You must specify a string file path for your custom password hashing assembly"); + + //Load the custom assembly + AssemblyLoader<IPasswordHashingProvider> prov = plugin.LoadAssembly<IPasswordHashingProvider>(customAsm); + + //Configure async + if (prov.Resource is IAsyncConfigurable ac) + { + //Configure async + _ = plugin.ConfigureServiceAsync(ac); + } + + //Store + Passwords = new CustomPasswordHashingAsm(prov); + } + else + { + Passwords = plugin.GetOrCreateSingleton<SecretProvider>().Passwords; + } + } + + public ManagedPasswordHashing(PluginBase plugin) + { + //Only configure a default password impl + Passwords = plugin.GetOrCreateSingleton<SecretProvider>().Passwords; + } + + /// <summary> + /// The underlying <see cref="IPasswordHashingProvider"/> + /// </summary> + public IPasswordHashingProvider Passwords { get; } + + ///<inheritdoc/> + public bool Verify(ReadOnlySpan<char> passHash, ReadOnlySpan<char> password) => Passwords.Verify(passHash, password); + + ///<inheritdoc/> + public bool Verify(ReadOnlySpan<byte> passHash, ReadOnlySpan<byte> password) => Passwords.Verify(passHash, password); + + ///<inheritdoc/> + public PrivateString Hash(ReadOnlySpan<char> password) => Passwords.Hash(password); + + ///<inheritdoc/> + public PrivateString Hash(ReadOnlySpan<byte> password) => Passwords.Hash(password); + + ///<inheritdoc/> + public ERRNO Hash(ReadOnlySpan<byte> password, Span<byte> hashOutput) => Passwords.Hash(password, hashOutput); + + sealed class CustomPasswordHashingAsm : IPasswordHashingProvider + { + private readonly AssemblyLoader<IPasswordHashingProvider> _loader; + + public CustomPasswordHashingAsm(AssemblyLoader<IPasswordHashingProvider> loader) + { + _loader = loader; + } + + /* + * Password hashing isnt a super high performance system + * so adding method overhead shouldnt be a large issue for the + * asm wrapper providing unload protection + */ + + public PrivateString Hash(ReadOnlySpan<char> password) => _loader.Resource.Hash(password); + + public PrivateString Hash(ReadOnlySpan<byte> password) => _loader.Resource.Hash(password); + + public ERRNO Hash(ReadOnlySpan<byte> password, Span<byte> hashOutput) => _loader.Resource.Hash(password, hashOutput); + + public bool Verify(ReadOnlySpan<char> passHash, ReadOnlySpan<char> password) => _loader.Resource.Verify(passHash, password); + + public bool Verify(ReadOnlySpan<byte> passHash, ReadOnlySpan<byte> password) => _loader.Resource.Verify(passHash, password); + } + + private sealed class SecretProvider : VnDisposeable, ISecretProvider, IAsyncConfigurable + { + private byte[]? _pepper; + private Exception? _error; + + public SecretProvider(PluginBase plugin, IConfigScope config) + { + if (config.TryGetValue("args", out JsonElement el)) + { + //Convert to dict + IReadOnlyDictionary<string, JsonElement> hashingArgs = el.EnumerateObject().ToDictionary(static k => k.Name, static v => v.Value); + + //Get hashing arguments + uint saltLen = hashingArgs["salt_len"].GetUInt32(); + uint hashLen = hashingArgs["hash_len"].GetUInt32(); + uint timeCost = hashingArgs["time_cost"].GetUInt32(); + uint memoryCost = hashingArgs["memory_cost"].GetUInt32(); + uint parallelism = hashingArgs["parallelism"].GetUInt32(); + //Load passwords + Passwords = new(this, (int)saltLen, timeCost, memoryCost, parallelism, hashLen); + } + else + { + Passwords = new(this); + } + } + + public SecretProvider(PluginBase plugin) + { + Passwords = new(this); + } + + + public PasswordHashing Passwords { get; } + + ///<inheritdoc/> + public int BufferSize + { + get + { + Check(); + return _pepper!.Length; + } + } + + public ERRNO GetSecret(Span<byte> buffer) + { + Check(); + //Coppy pepper to buffer + _pepper.CopyTo(buffer); + //Return pepper length + return _pepper!.Length; + } + + protected override void Check() + { + base.Check(); + if (_error != null) + { + throw _error; + } + } + + protected override void Free() + { + //Clear the pepper if set + MemoryUtil.InitializeBlock(_pepper.AsSpan()); + } + + public async Task ConfigureServiceAsync(PluginBase plugin) + { + try + { + //Get the pepper from secret storage + _pepper = await plugin.TryGetSecretAsync(LoadingExtensions.PASSWORD_HASHING_KEY).ToBase64Bytes(); + } + catch (Exception ex) + { + //Store exception for re-propagation + _error = ex; + + //Propagate exception to system + throw; + } + } + } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs b/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs index 4cf1c9d..9e3c222 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs @@ -56,6 +56,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_URL_KEY = "url"; @@ -306,6 +307,12 @@ namespace VNLib.Plugins.Extensions.Loading { authMethod = new AppRoleAuthMethodInfo(roleEl.GetString(), secretEl.GetString()); } + //Try to get the token as an environment variable + else if(Environment.GetEnvironmentVariable(VAULT_TOKNE_ENV_NAME) != null) + { + string tokenValue = Environment.GetEnvironmentVariable(VAULT_TOKNE_ENV_NAME)!; + authMethod = new TokenAuthMethodInfo(tokenValue); + } else { throw new KeyNotFoundException($"Failed to load the vault authentication method from {VAULT_OBJECT_NAME}"); |