From 6e675874d72e971baca5506319466423f39e9783 Mon Sep 17 00:00:00 2001 From: Joe VanWanzeele Date: Mon, 11 Aug 2025 11:12:27 -0400 Subject: [PATCH 1/6] updated integration manifest to reflect prod release --- integration-manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-manifest.json b/integration-manifest.json index e1bd1b7..30f0c25 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -2,8 +2,8 @@ "$schema": "https://keyfactor.github.io/v2/integration-manifest-schema.json", "integration_type": "anyca-plugin", "name": "Hashicorp Vault AnyCA REST Gateway Plugin", - "status": "prototype", - "support_level": "community", + "status": "production", + "support_level": "kf-supported", "link_github": true, "update_catalog": false, "description": "Hashicorp Vault plugin for the AnyCA REST Gateway Framework", From c0588b44e3973e4df48caaeacff004e9fa9839d8 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:33:29 -0700 Subject: [PATCH 2/6] 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. --- CHANGELOG.md | 21 +++++++++++++++++++ hashicorp-vault-cagateway/Client/VaultHttp.cs | 6 ++++-- .../hashicorp-vault-caplugin.csproj | 2 +- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..df3bbf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +## [Unreleased] + +### Fixed + +- **`VaultHttp.PostAsync`: JSON double-encoding caused HTTP 400 from Vault/OpenBao** + + `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". + + 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. + +- **`ThrowOnAnyError = true` made the `BadRequest` error-parsing block dead code** + + `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. + + 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. + +### Changed + +- Dropped .NET 6.0 target (EOL). The project now targets `net8.0` and `net10.0`. diff --git a/hashicorp-vault-cagateway/Client/VaultHttp.cs b/hashicorp-vault-cagateway/Client/VaultHttp.cs index c1c5f99..fd43e91 100644 --- a/hashicorp-vault-cagateway/Client/VaultHttp.cs +++ b/hashicorp-vault-cagateway/Client/VaultHttp.cs @@ -41,7 +41,7 @@ public VaultHttp(string host, string mountPoint, string authToken, string nameSp PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace, }; - var restClientOptions = new RestClientOptions($"{host.TrimEnd('/')}/v1") { ThrowOnAnyError = true }; + var restClientOptions = new RestClientOptions($"{host.TrimEnd('/')}/v1"); _restClient = new RestClient(restClientOptions, configureSerialization: s => s.UseSystemTextJson(_serializerOptions)); _mountPoint = mountPoint.TrimStart('/').TrimEnd('/'); // remove leading and trailing slashes @@ -109,7 +109,7 @@ public async Task PostAsync(string path, dynamic parameters = default) { string serializedParams = JsonSerializer.Serialize(parameters, _serializerOptions); logger.LogTrace($"serialized parameters (from {parameters.GetType()?.Name}): {serializedParams}"); - request.AddJsonBody(serializedParams); + request.AddStringBody(serializedParams, ContentType.Json); } logger.LogTrace($"full url for the request: {_restClient.Options.BaseUrl}/{request.Resource}"); @@ -135,6 +135,8 @@ public async Task PostAsync(string path, dynamic parameters = default) logger.LogTrace($"errors: {allErrors}"); throw new Exception(allErrors); } + + response.ThrowIfError(); return response.Data; } catch (Exception ex) diff --git a/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj b/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj index 95991b8..622ca97 100644 --- a/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj +++ b/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0;net10.0 Keyfactor.Extensions.CAPlugin.HashicorpVault disable warnings From e973ff615d98cf197415c2035850ea65eff36c36 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:46:27 -0700 Subject: [PATCH 3/6] chore: target v1.0.3 in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df3bbf5..4813962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [1.0.3] ### Fixed From 8f5df3b86f94dae14c2b4c898383d96a130d4542 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:11:41 -0700 Subject: [PATCH 4/6] fix: replace unsafe dictionary indexers in ValidateCAConnectionInfo and ValidateProductInfo Direct Dictionary.get_Item calls throw KeyNotFoundException when the gateway does not populate all parameter keys before calling validation (observed on PUT/POST config/configuration). Replace with TryGetValue throughout. Also relaxes the ValidateProductInfo RoleName check: RoleName is optional since the Enroll path already falls back to ProductID when RoleName is absent, so the validator no longer rejects configs that omit it. --- .../HashicorpVaultCAConnector.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/hashicorp-vault-cagateway/HashicorpVaultCAConnector.cs b/hashicorp-vault-cagateway/HashicorpVaultCAConnector.cs index e020e9e..7bb70ac 100644 --- a/hashicorp-vault-cagateway/HashicorpVaultCAConnector.cs +++ b/hashicorp-vault-cagateway/HashicorpVaultCAConnector.cs @@ -340,19 +340,23 @@ public async Task ValidateCAConnectionInfo(Dictionary connection List errors = new List(); // then, we make sure required fields are defined.. - if (string.IsNullOrEmpty(connectionInfo[Constants.CAConfig.HOST] as string)) + connectionInfo.TryGetValue(Constants.CAConfig.HOST, out var hostVal); + if (string.IsNullOrEmpty(hostVal as string)) { errors.Add($"The '{Constants.CAConfig.HOST}' is required."); } - if (string.IsNullOrEmpty(connectionInfo[Constants.CAConfig.MOUNTPOINT] as string)) + connectionInfo.TryGetValue(Constants.CAConfig.MOUNTPOINT, out var mountVal); + if (string.IsNullOrEmpty(mountVal as string)) { errors.Add($"The '{Constants.CAConfig.MOUNTPOINT}' is required."); } // make sure an authentication mechanism is defined (either certificate or token) - var token = connectionInfo[Constants.CAConfig.TOKEN] as string; - var cert = connectionInfo[Constants.CAConfig.CLIENTCERT] as string; + connectionInfo.TryGetValue(Constants.CAConfig.TOKEN, out var tokenVal); + connectionInfo.TryGetValue(Constants.CAConfig.CLIENTCERT, out var certVal); + var token = tokenVal as string; + var cert = certVal as string; if (string.IsNullOrEmpty(token) && string.IsNullOrEmpty(cert)) { @@ -439,10 +443,11 @@ public Task ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary Date: Tue, 28 Apr 2026 14:12:06 -0700 Subject: [PATCH 5/6] chore: document dictionary indexer fix in CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4813962..fd1c7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,14 @@ 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. +- **`ValidateCAConnectionInfo` and `ValidateProductInfo`: `KeyNotFoundException` on gateway config PUT/POST** + + Both validation methods used direct `Dictionary.get_Item` indexers (`connectionInfo[key]`) to read parameters. The gateway does not always pre-populate every parameter key before calling validation, so any absent key threw `KeyNotFoundException` and surfaced as an opaque HTTP 500 from the gateway config endpoint. + + Fixed by replacing all direct indexers with `TryGetValue` calls throughout both methods. + + Additionally relaxed the `RoleName` requirement in `ValidateProductInfo`: the `Enroll` path already falls back to `ProductID` when `RoleName` is absent, so the validator no longer rejects configurations that omit it. The check now only errors if `RoleName` is explicitly present but empty. + ### Changed - Dropped .NET 6.0 target (EOL). The project now targets `net8.0` and `net10.0`. From 926e84c77e6307e89aef66d8038acae2d4e9c7b9 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Mon, 4 May 2026 14:34:43 -0700 Subject: [PATCH 6/6] fix(ci): `AppendTargetFrameworkToOutputPath=true` --- hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj b/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj index b4abe1f..4fa65db 100644 --- a/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj +++ b/hashicorp-vault-cagateway/hashicorp-vault-caplugin.csproj @@ -9,7 +9,7 @@ False True 12.0 - False + True False @@ -18,7 +18,7 @@ False True bin - False + True False