Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions src/VaultSharp/Core/Polymath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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/";

Expand Down Expand Up @@ -196,6 +197,8 @@ public Secret<T2> GetMappedSecret<T1, T2>(Secret<T1> sourceSecret, T2 destinatio

protected async Task<TResponse> MakeRequestAsync<TResponse>(string resourcePath, HttpMethod httpMethod, object requestData = null, IDictionary<string, string> headers = null, bool rawResponse = false, Action<HttpResponseMessage> postResponseAction = null) where TResponse : class
{
HttpRequestMessage httpRequestMessage = null;

try
{
var requestUri = new Uri(_httpClient.BaseAddress, new Uri(resourcePath, UriKind.Relative));
Expand All @@ -206,8 +209,6 @@ protected async Task<TResponse> MakeRequestAsync<TResponse>(string resourcePath,
? new StringContent(requestJson, Encoding.UTF8)
: null;

HttpRequestMessage httpRequestMessage = null;

switch (httpMethod.ToString().ToUpperInvariant())
{
case "LIST":
Expand Down Expand Up @@ -249,6 +250,12 @@ protected async Task<TResponse> MakeRequestAsync<TResponse>(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)
Expand Down Expand Up @@ -282,7 +289,7 @@ protected async Task<TResponse> MakeRequestAsync<TResponse>(string resourcePath,
return default(TResponse);
}

throw new VaultApiException(httpResponseMessage.StatusCode, responseText);
throw new VaultApiException(httpResponseMessage.StatusCode, httpRequestMessage.Headers, responseText);
}
catch (WebException ex)
{
Expand All @@ -298,7 +305,7 @@ protected async Task<TResponse> MakeRequestAsync<TResponse>(string resourcePath,
await stream.ReadToEndAsync().ConfigureAwait(VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
}

throw new VaultApiException(response.StatusCode, responseText);
throw new VaultApiException(response.StatusCode, httpRequestMessage?.Headers, responseText);
}

throw;
Expand Down
51 changes: 38 additions & 13 deletions src/VaultSharp/Core/VaultApiException.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -20,6 +21,16 @@ public class VaultApiException : Exception
/// The http status code returned by Api.
/// </summary>
public HttpStatusCode HttpStatusCode { get; }

/// <summary>
/// The correlation id included in the request.
/// </summary>
public string CorrelationId { get; }

/// <summary>
/// The request id returned by the Api.
/// </summary>
public string RequestId { get; }

/// <summary>
/// The list of api errors.
Expand Down Expand Up @@ -59,30 +70,44 @@ public VaultApiException(string message, Exception innerException) : base(messag
/// Status code based exception.
/// </summary>
/// <param name="httpStatusCode">Http status code.</param>
/// <param name="requestHeaders">Headers from the http request</param>
/// <param name="message">Exception message.</param>
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<Dictionary<string, IEnumerable<string>>>(message);
CorrelationId = string.Join(",", correlationIdValues);
}

if (structured.ContainsKey("errors"))
if (!string.IsNullOrWhiteSpace(message))
{
try
{
ApiErrors = structured["errors"];
var errorResponse = JsonSerializer.Deserialize<ErrorResponse>(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; }
}
}
}
9 changes: 9 additions & 0 deletions src/VaultSharp/VaultClientSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Net.Http;
using VaultSharp.V1.AuthMethods;
using VaultSharp.V1.SecretsEngines;
Expand Down Expand Up @@ -85,5 +86,13 @@ public VaultClientSettings(string vaultServerUriWithPort, IAuthMethodInfo authMe
/// See <see cref="V1.SecretsEngines.SecretsEngineMountPoints.Defaults" /> for defaults.
/// </summary>
public SecretsEngineMountPoints SecretsEngineMountPoints { get; set; } = new SecretsEngineMountPoints();

/// <summary>
/// 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 <see cref="Activity"/>.
/// </summary>
public Func<string> CorrelationIdProviderFunc { get; set; } =
() => Activity.Current?.Id;
}
}
24 changes: 24 additions & 0 deletions src/VaultSharp/VaultSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net47'">
Expand All @@ -105,6 +108,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net471'">
Expand All @@ -114,6 +120,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
Expand All @@ -123,6 +132,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net48'">
Expand All @@ -136,6 +148,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net481'">
Expand All @@ -149,6 +164,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>


Expand All @@ -160,6 +178,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
Expand All @@ -170,6 +191,9 @@ This library is built with .NET Standard 2.0, .NET Standard 2.1, 4.6.2, 4.7.2*,
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>10.0.1</Version>
</PackageReference>
</ItemGroup>

<PropertyGroup Condition="'$(TargetFramework)'=='net6.0'">
Expand Down
3 changes: 2 additions & 1 deletion test/VaultSharp.Samples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down