Skip to content

Commit c0588b4

Browse files
committed
fix: correct JSON serialization and error handling in VaultHttp
AddJsonBody(string) in RestSharp re-serializes the argument, wrapping a pre-serialized JSON string in quotes and escaping it. Vault receives a JSON string literal instead of a JSON object and returns HTTP 400. Fixed by using AddStringBody(string, ContentType.Json) which sends the raw body without re-encoding. ThrowOnAnyError = true caused RestSharp to throw before the explicit BadRequest handler ran, hiding the Vault error body from logs. Removed ThrowOnAnyError and added response.ThrowIfError() after the BadRequest block so all other non-2xx responses still surface as exceptions. Also drops the EOL net6.0 target; project now multi-targets net8.0 and net10.0.
1 parent 6e67587 commit c0588b4

3 files changed

Lines changed: 26 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Changelog
2+
3+
## [Unreleased]
4+
5+
### Fixed
6+
7+
- **`VaultHttp.PostAsync`: JSON double-encoding caused HTTP 400 from Vault/OpenBao**
8+
9+
`PostAsync` manually serialized the request body to a JSON string using `JsonSerializer.Serialize`, then passed that string to `request.AddJsonBody()`. In RestSharp ≥ 106, `AddJsonBody` re-serializes whatever object it receives — when the argument is a `string`, it encodes it as a JSON string literal, wrapping the content in quotes and escaping the inner characters. Vault/OpenBao received `"{\\"csr\\":\\"...\\"}"` (a JSON-encoded string) instead of `{"csr":"..."}` (a JSON object) and returned HTTP 400 "error parsing JSON".
10+
11+
Fixed by replacing `request.AddJsonBody(serializedParams)` with `request.AddStringBody(serializedParams, ContentType.Json)`. `AddStringBody` sends the string as the raw request body without re-encoding it.
12+
13+
- **`ThrowOnAnyError = true` made the `BadRequest` error-parsing block dead code**
14+
15+
`RestClientOptions` was constructed with `ThrowOnAnyError = true`, which causes RestSharp to throw an exception on any non-2xx response before returning to the caller. The `PostAsync` method had explicit handling for `HttpStatusCode.BadRequest` that deserialized Vault error messages and threw a descriptive exception — but that block was never reached because RestSharp threw first, and the actual Vault error body was lost.
16+
17+
Fixed by removing `ThrowOnAnyError = true` from `RestClientOptions` and adding `response.ThrowIfError()` after the explicit `BadRequest` handler, so non-2xx responses that are not `BadRequest` still surface as exceptions while `BadRequest` responses are handled with full Vault error body parsing.
18+
19+
### Changed
20+
21+
- Dropped .NET 6.0 target (EOL). The project now targets `net8.0` and `net10.0`.

hashicorp-vault-cagateway/Client/VaultHttp.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public VaultHttp(string host, string mountPoint, string authToken, string nameSp
4141
PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace,
4242
};
4343

44-
var restClientOptions = new RestClientOptions($"{host.TrimEnd('/')}/v1") { ThrowOnAnyError = true };
44+
var restClientOptions = new RestClientOptions($"{host.TrimEnd('/')}/v1");
4545
_restClient = new RestClient(restClientOptions, configureSerialization: s => s.UseSystemTextJson(_serializerOptions));
4646

4747
_mountPoint = mountPoint.TrimStart('/').TrimEnd('/'); // remove leading and trailing slashes
@@ -109,7 +109,7 @@ public async Task<T> PostAsync<T>(string path, dynamic parameters = default)
109109
{
110110
string serializedParams = JsonSerializer.Serialize(parameters, _serializerOptions);
111111
logger.LogTrace($"serialized parameters (from {parameters.GetType()?.Name}): {serializedParams}");
112-
request.AddJsonBody(serializedParams);
112+
request.AddStringBody(serializedParams, ContentType.Json);
113113
}
114114

115115
logger.LogTrace($"full url for the request: {_restClient.Options.BaseUrl}/{request.Resource}");
@@ -135,6 +135,8 @@ public async Task<T> PostAsync<T>(string path, dynamic parameters = default)
135135
logger.LogTrace($"errors: {allErrors}");
136136
throw new Exception(allErrors);
137137
}
138+
139+
response.ThrowIfError();
138140
return response.Data;
139141
}
140142
catch (Exception ex)

hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
55
<RootNamespace>Keyfactor.Extensions.CAPlugin.HashicorpVault</RootNamespace>
66
<ImplicitUsings>disable</ImplicitUsings>
77
<Nullable>warnings</Nullable>

0 commit comments

Comments
 (0)