From 9c11ef461bfd010c4c3b2ec861868b9f1c134c0c Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 22 Dec 2025 13:28:42 -0600 Subject: [PATCH 1/3] Exposed CorrelationId and RequestId on VaultApiException --- src/VaultSharp/Core/Polymath.cs | 8 ++-- src/VaultSharp/Core/VaultApiException.cs | 51 ++++++++++++++++++------ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/VaultSharp/Core/Polymath.cs b/src/VaultSharp/Core/Polymath.cs index 0a4b208f..26650372 100644 --- a/src/VaultSharp/Core/Polymath.cs +++ b/src/VaultSharp/Core/Polymath.cs @@ -196,6 +196,8 @@ public Secret GetMappedSecret(Secret sourceSecret, T2 destinatio protected async Task MakeRequestAsync(string resourcePath, HttpMethod httpMethod, object requestData = null, IDictionary headers = null, bool rawResponse = false, Action postResponseAction = null) where TResponse : class { + HttpRequestMessage httpRequestMessage = null; + try { var requestUri = new Uri(_httpClient.BaseAddress, new Uri(resourcePath, UriKind.Relative)); @@ -206,8 +208,6 @@ protected async Task MakeRequestAsync(string resourcePath, ? new StringContent(requestJson, Encoding.UTF8) : null; - HttpRequestMessage httpRequestMessage = null; - switch (httpMethod.ToString().ToUpperInvariant()) { case "LIST": @@ -282,7 +282,7 @@ protected async Task MakeRequestAsync(string resourcePath, return default(TResponse); } - throw new VaultApiException(httpResponseMessage.StatusCode, responseText); + throw new VaultApiException(httpResponseMessage.StatusCode, httpRequestMessage.Headers, responseText); } catch (WebException ex) { @@ -298,7 +298,7 @@ protected async Task MakeRequestAsync(string resourcePath, await stream.ReadToEndAsync().ConfigureAwait(VaultClientSettings.ContinueAsyncTasksOnCapturedContext); } - throw new VaultApiException(response.StatusCode, responseText); + throw new VaultApiException(response.StatusCode, httpRequestMessage?.Headers, responseText); } throw; diff --git a/src/VaultSharp/Core/VaultApiException.cs b/src/VaultSharp/Core/VaultApiException.cs index aa4d48f7..4829cf8c 100644 --- a/src/VaultSharp/Core/VaultApiException.cs +++ b/src/VaultSharp/Core/VaultApiException.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Http.Headers; using System.Text.Json; using System.Text.Json.Serialization; @@ -20,6 +21,16 @@ public class VaultApiException : Exception /// The http status code returned by Api. /// public HttpStatusCode HttpStatusCode { get; } + + /// + /// The correlation id included in the request. + /// + public string CorrelationId { get; } + + /// + /// The request id returned by the Api. + /// + public string RequestId { get; } /// /// The list of api errors. @@ -59,30 +70,44 @@ public VaultApiException(string message, Exception innerException) : base(messag /// Status code based exception. /// /// Http status code. + /// Headers from the http request /// Exception message. - public VaultApiException(HttpStatusCode httpStatusCode, string message) : base(message) + public VaultApiException(HttpStatusCode httpStatusCode, HttpRequestHeaders requestHeaders, string message) : base(message) { HttpStatusCode = httpStatusCode; - StatusCode = (int) HttpStatusCode; + StatusCode = (int) httpStatusCode; - try + if (requestHeaders != null && ( + requestHeaders.TryGetValues("x-correlation-id", out var correlationIdValues) || + requestHeaders.TryGetValues("correlation-id", out correlationIdValues))) { - var structured = JsonSerializer.Deserialize>>(message); + CorrelationId = string.Join(",", correlationIdValues); + } - if (structured.ContainsKey("errors")) + if (!string.IsNullOrWhiteSpace(message)) + { + try { - ApiErrors = structured["errors"]; + var errorResponse = JsonSerializer.Deserialize(message); + RequestId = errorResponse?.RequestId; + ApiErrors = errorResponse?.Errors; + ApiWarnings = errorResponse?.Warnings; } - - if (structured.ContainsKey("warnings")) + catch { - ApiWarnings = structured["warnings"]; + // nothing to do. } } - catch - { - // nothing to do. - } + } + + private class ErrorResponse + { + [JsonPropertyName("request_id")] + public string RequestId { get; set; } + [JsonPropertyName("errors")] + public string[] Errors { get; set; } + [JsonPropertyName("warnings")] + public string[] Warnings { get; set; } } } } From d183a1001081fadf27e32ace102c5bdc7c08adb9 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 22 Dec 2025 13:30:13 -0600 Subject: [PATCH 2/3] Added mechanism to set the correlation id on each request --- src/VaultSharp/Core/Polymath.cs | 7 +++++++ src/VaultSharp/VaultClientSettings.cs | 9 +++++++++ src/VaultSharp/VaultSharp.csproj | 24 ++++++++++++++++++++++++ test/VaultSharp.Samples/Program.cs | 3 ++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/VaultSharp/Core/Polymath.cs b/src/VaultSharp/Core/Polymath.cs index 26650372..1ce63b47 100644 --- a/src/VaultSharp/Core/Polymath.cs +++ b/src/VaultSharp/Core/Polymath.cs @@ -22,6 +22,7 @@ internal class Polymath private const string AuthorizationHeaderKey = "Authorization"; private const string VaultTokenHeaderKey = "X-Vault-Token"; private const string VaultWrapTimeToLiveHeaderKey = "X-Vault-Wrap-TTL"; + private const string VaultCorrelationIdHeaderKey = "X-Correlation-Id"; private const string VaultSharpV1Path = "v1/"; @@ -249,6 +250,12 @@ protected async Task MakeRequestAsync(string resourcePath, httpRequestMessage.Headers.Add(VaultRequestHeaderKey, "true"); + var correlationId = VaultClientSettings.CorrelationIdProviderFunc(); + if (!string.IsNullOrWhiteSpace(correlationId)) + { + httpRequestMessage.Headers.Add(VaultCorrelationIdHeaderKey, correlationId); + } + if (headers != null) { foreach (var kv in headers) diff --git a/src/VaultSharp/VaultClientSettings.cs b/src/VaultSharp/VaultClientSettings.cs index 768fe5b1..05efbddb 100644 --- a/src/VaultSharp/VaultClientSettings.cs +++ b/src/VaultSharp/VaultClientSettings.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Net.Http; using VaultSharp.V1.AuthMethods; using VaultSharp.V1.SecretsEngines; @@ -85,5 +86,13 @@ public VaultClientSettings(string vaultServerUriWithPort, IAuthMethodInfo authMe /// See for defaults. /// public SecretsEngineMountPoints SecretsEngineMountPoints { get; set; } = new SecretsEngineMountPoints(); + + /// + /// Provider of a correlation id to include in the Vault request headers. + /// See also: https://developer.hashicorp.com/vault/docs/audit#logging-request-headers + /// Defaults to the id of the current . + /// + public Func CorrelationIdProviderFunc { get; set; } = + () => Activity.Current?.Id; } } diff --git a/src/VaultSharp/VaultSharp.csproj b/src/VaultSharp/VaultSharp.csproj index 1e0b3d6c..0de85fbe 100644 --- a/src/VaultSharp/VaultSharp.csproj +++ b/src/VaultSharp/VaultSharp.csproj @@ -96,6 +96,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + @@ -105,6 +108,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + @@ -114,6 +120,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + @@ -123,6 +132,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + @@ -136,6 +148,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + @@ -149,6 +164,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + @@ -160,6 +178,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + @@ -170,6 +191,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*, 8.0.5 + + 10.0.1 + diff --git a/test/VaultSharp.Samples/Program.cs b/test/VaultSharp.Samples/Program.cs index f8d01b80..fd7caba2 100644 --- a/test/VaultSharp.Samples/Program.cs +++ b/test/VaultSharp.Samples/Program.cs @@ -104,7 +104,8 @@ private static VaultClientSettings GetVaultClientSettings(IAuthMethodInfo authMe } }, Namespace = "bhjk", - MyHttpClientProviderFunc = handler => new HttpClient(handler) + MyHttpClientProviderFunc = handler => new HttpClient(handler), + CorrelationIdProviderFunc = () => Guid.NewGuid().ToString() }; return settings; From 18e6c3f652c6a36839af35d8db8c7b688fdf6fa5 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 22 Dec 2025 14:04:30 -0600 Subject: [PATCH 3/3] Updated documentation --- CHANGELOG.md | 6 ++++++ README.md | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1476e1fb..4d1ed4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ * Helper code to get Kerberos Negotiation Token using keytab and krb5 config file: https://gist.github.com/rajanadar/28c86d967695262bfe1f17ae82fb3d3d * Helper code to generate on-demand CloudFoundry signature in .NET Applications: https://gist.github.com/rajanadar/84769efeca64e0128d7a8a627b7bb4db +## 1.17.5.4 (TBD) + +**IMROVEMENTS:** + + * [GH-391](https://github.com/rajanadar/VaultSharp/issues/391) Automatically set audit logging correlation id and expose on VaultApiException + ## 1.17.5.3 (May 06, 2025) **BUG FIXES:** diff --git a/README.md b/README.md index 50fb5c62..7ae9a1db 100644 --- a/README.md +++ b/README.md @@ -1808,6 +1808,21 @@ var settings = new VaultClientSettings("http://localhost:8200", authMethodInfo) }; ``` +### Can I provide my own correlation id for audit logs + +- Yes you can. +- The ```VaultClientSettings``` object takes a ```CorrelationIdProviderFunc``` delegate that can be as follows. +- The default delegate retrieves the correlation id from the current .NET activity +- More information on audit logging: https://developer.hashicorp.com/vault/docs/audit#logging-request-headers + +```cs +var settings = new VaultClientSettings("http://localhost:8200", authMethodInfo) + { + Namespace = "mynamespace", + CorrelationIdProviderFunc = () => Guid.NewGuid().ToString() + }; +``` + ### What is the deal with the Versioning of VaultSharp? - This library is written for Hashicorp's Vault Service