aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.Loading/src/Secrets
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-05-02 15:44:42 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-05-02 15:44:42 -0400
commit8e77289041349b16536497f48f0c0a4ec6fe30f5 (patch)
treea222f4b46ce48a11f0225e9edecc058e25d3f579 /lib/VNLib.Plugins.Extensions.Loading/src/Secrets
parente0a5c85297516188e57b54d9b530b2482cb03eb0 (diff)
feat: #2 Middleware helpers, proj cleanup, fix sync secrets, vault client
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Loading/src/Secrets')
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs127
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IKvVaultClient.cs (renamed from lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IHCVaultClient.cs)8
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs2
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs2
4 files changed, 39 insertions, 100 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs
index a06f490..35530c0 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
+
+ /// <summary>
+ /// A concret implementation of a Hashicorp Vault client instance used to
+ /// retrieve key-value secrets from a server
+ /// </summary>
+ 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
/// </summary>
/// <param name="serverAddress">The vault server address</param>
- /// <param name="hcToken">The vault token used to connect to the vault server</param>
+ /// <param name="token">The vault token used to connect to the vault server</param>
/// <param name="kvVersion">The hc vault Key value store version (must be 1 or 2)</param>
/// <param name="trustCert">A value that tells the HTTP client to trust the Vault server's certificate even if it's not valid</param>
/// <param name="heap">Heap instance to allocate internal buffers from</param>
/// <returns>The new client instance</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentNullException"></exception>
- 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,7 @@ 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);
}
///<inheritdoc/>
@@ -137,10 +141,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);
}
@@ -162,88 +166,36 @@ namespace VNLib.Plugins.Extensions.Loading
///<inheritdoc/>
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<SecretResponse> resTask = ReadSecretResponse(response.Content, false);
- Debug.Assert(resTask.IsCompleted);
+ /*
+ * 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.
+ */
- //Always wrap response in using to clean memory
- using SecretResponse res = resTask.GetAwaiter().GetResult();
+ Task<ISecretResult?> asAsync = Task.Run(() => ReadSecretAsync(path, mountPoint, secretName));
+
+ asAsync.Wait(ClientDefaultTimeout);
- 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)
- {
- throw new HCVaultException("Failed to retreive secret from Hashicorp Vault server, see inner exception", ex);
- }
+ return asAsync.Result;
}
- private ValueTask<SecretResponse> ReadSecretResponse(HttpContent content, bool async)
+ private async Task<SecretResponse> 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<SecretResponse> 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 +240,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 +274,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 +304,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<VaultErrorMessage>(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/IKvVaultClient.cs
index aab2541..876d8b6 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IHCVaultClient.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/IKvVaultClient.cs
@@ -3,9 +3,9 @@
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
-* File: IHCVaultClient.cs
+* File: ISecretVaultClient.cs
*
-* IHCVaultClient.cs is part of VNLib.Plugins.Extensions.Loading which is
+* ISecretVaultClient.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,9 +29,9 @@ using System.Threading.Tasks;
namespace VNLib.Plugins.Extensions.Loading
{
/// <summary>
- /// A Hashicorp Vault client for reading secrets from a vault server
+ /// A secret client interace for reading secrets from a vault server
/// </summary>
- public interface IHCVaultClient
+ public interface IKvVaultClient
{
/// <summary>
/// Reads a single KeyValue secret from the vault server asyncrhonously and returns the result
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs
index 6e6d560..17f3523 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/OnDemandSecret.cs
@@ -40,7 +40,7 @@ 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, IKvVaultClient? vault) : IOnDemandSecret
{
public string SecretName { get; } = secretName ?? throw new ArgumentNullException(nameof(secretName));
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs
index 6b20e30..1d366b0 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/PluginSecretStore.cs
@@ -49,7 +49,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// <returns>The ambient <see cref="IVaultClient"/> if loaded, null otherwise</returns>
/// <exception cref="KeyNotFoundException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public IHCVaultClient? GetVaultClient() => LoadingExtensions.GetOrCreateSingleton(_plugin, TryGetVaultLoader);
+ public IKvVaultClient? GetVaultClient() => LoadingExtensions.GetOrCreateSingleton(_plugin, TryGetVaultLoader);
private static HCVaultClient? TryGetVaultLoader(PluginBase pbase)
{