feat(secretstores/hashicorp/vault): add native Kubernetes auth - #4448
feat(secretstores/hashicorp/vault): add native Kubernetes auth#4448icmp0x8 wants to merge 15 commits into
Conversation
Replace the hand-rolled net/http client (manual TLS/cert-pool setup,
manual request building, jsoniter-based response parsing) with the
official github.com/hashicorp/vault/api SDK. This removes ~150 lines
of bespoke HTTP/TLS code in favor of well-tested SDK internals, with
no intended behavior change for existing token-based configurations.
Also fixes a latent bug this migration would otherwise introduce: a
soft-deleted or destroyed KV v2 secret version comes back from Vault
as a 404 whose body still carries {"data": {"data": null, ...}}, and
the SDK parses that into a regular (non-error) secret rather than
surfacing an error. Without an explicit nil check, GetSecret would
return a confusing type-assertion error (or a silent empty string in
text mode) instead of ErrNotFound, and BulkGetSecret would fail the
entire bulk read instead of skipping the deleted entry.
Signed-off-by: Oleg Kuznetsov <71344093+icmp0x8@users.noreply.github.com>
Add a "kubernetes" vaultAuthMethod that authenticates directly against Vault's Kubernetes Auth Method using the pod's service account token, with automatic background renewal/re-authentication for the lifetime of the component. This removes the need for a Vault Agent Injector sidecar to obtain and refresh a Vault token on Kubernetes. - kubernetes_auth.go: performs a blocking first login at Init() (so misconfiguration fails fast), then runs a background goroutine that watches the login's lease via the SDK's LifetimeWatcher and re-authenticates with exponential backoff whenever the lease can no longer be renewed. Each (re-)login builds a fresh KubernetesAuth instance, since it caches the JWT read at construction time and never re-reads it. - vault.go: new vaultAuthMethod metadata field (default "token" for backward compatibility), threads the Init() caller-supplied context into the blocking first login so the Dapr runtime's component-init timeout is honored, and a synchronous Close() that cancels the background context and waits for the renewal goroutine to exit. - metadata.yaml: documents the new fields and corrects vaultToken/vaultTokenMountPath from required:true to required:false, since neither is required (or valid) under the kubernetes method. Closes dapr#4104 Signed-off-by: Oleg Kuznetsov <71344093+icmp0x8@users.noreply.github.com>
| config := api.DefaultConfig() | ||
| if config.Error != nil { | ||
| return fmt.Errorf("couldn't build vault client config: %w", config.Error) | ||
| } | ||
| config.Address = v.vaultAddress |
There was a problem hiding this comment.
If I'm reading this right - with the sdk migration it seems like there an unintentional override of metadata set - with any configured env vars (especially the address if VAULT_AGENT_ADDR is populated which wins over the metadata address).
There was a problem hiding this comment.
Good catch — fixed in 6c4b07a: explicitly clearing config.AgentAddress after api.DefaultConfig(), since api.NewClient() prefers it over Address whenever it's set. Added a regression test (TestVaultAddressIgnoresAgentAddrEnvVar) that sets VAULT_AGENT_ADDR and asserts the metadata-configured address still wins.
…ing vaultAddr api.DefaultConfig() reads VAULT_AGENT_ADDR from the environment, and api.NewClient() prefers it over the metadata-configured Address whenever it's set. Clear it explicitly so vaultAddr always wins. Signed-off-by: Oleg Kuznetsov <71344093+icmp0x8@users.noreply.github.com>
… overriding metadata api.DefaultConfig()/api.NewClient() also read VAULT_SKIP_VERIFY (can silently disable TLS verification) and VAULT_NAMESPACE (scopes every request to a different namespace) from the environment -- same class of bug as the VAULT_AGENT_ADDR fix. Reset both so metadata stays the sole source of truth for how the client connects. Signed-off-by: Oleg Kuznetsov <71344093+icmp0x8@users.noreply.github.com>
…p-type secrets
The previous implementation decoded map-type secrets straight into a
map[string]string via encoding/json, under which a JSON null value
silently became an empty string. The interface{}-based decoding used
after the SDK migration was stricter and errored on null values
instead. Restore the original behavior.
Signed-off-by: Oleg Kuznetsov <71344093+icmp0x8@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds native Kubernetes authentication support to the HashiCorp Vault secret store (opt-in via vaultAuthMethod: kubernetes) and migrates Vault interactions to the official github.com/hashicorp/vault/api SDK, including background token renewal/re-auth.
Changes:
- Added Kubernetes auth method support (login via service account JWT + background renewal/re-authentication loop).
- Migrated KV operations and TLS configuration to the official Vault API client, including explicit ignoring of certain
VAULT_*env vars. - Added/expanded regression tests for TLS precedence, env var behavior, KV v2 deleted-version handling, and Kubernetes auth renewal behavior.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| secretstores/hashicorp/vault/vault.go | Switches to Vault SDK client, adds auth-method selection, updates KV read/list logic, and adds Close() background lifecycle handling. |
| secretstores/hashicorp/vault/kubernetes_auth.go | Implements Kubernetes auth login + background token renewal/re-auth with exponential backoff. |
| secretstores/hashicorp/vault/vault_test.go | Adds extensive tests for env var overrides, TLS precedence, Kubernetes auth, renewal, and KV deletion edge cases. |
| secretstores/hashicorp/vault/metadata.yaml | Documents new vaultAuthMethod and Kubernetes auth metadata fields; relaxes token field requirements for kubernetes mode. |
| go.mod | Adds Vault SDK deps (Vault API + Kubernetes auth helper) and updates indirect requirements. |
| go.sum | Updates dependency checksums for newly added/upgraded modules. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Oleg Kuznetsov <71344093+icmp0x8@users.noreply.github.com>
|
@mikeee please run again workflows, seems i fixed them |
|
@acroca thx for running checks. Looks like the 2 failing checks are flaky, aren't they? |
|
Right, they looked unrelated, triggered them again 🤞 |
… on skipVerify Guard Init()/Close() with a mutex to avoid a leaked renewal goroutine, return an error instead of panicking on use before Init() completes, wait for the LifetimeWatcher goroutine on Close(), pace re-logins for short-lived secrets, treat a missing "keys" list field as empty, and restore the skipVerify warning log dropped during the SDK migration. Signed-off-by: Oleg Kuznetsov <71344093+icmp0x8@users.noreply.github.com>
123f61c to
29f78a7
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4448 +/- ##
==========================================
+ Coverage 31.94% 32.63% +0.69%
==========================================
Files 353 353
Lines 47723 38033 -9690
==========================================
- Hits 15243 12413 -2830
+ Misses 31277 24405 -6872
- Partials 1203 1215 +12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
This pull request has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days if no further activity occurs. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions! |
Description
Adds native Kubernetes auth for the HashiCorp Vault secret store: instead of relying on a Vault Agent Injector sidecar to fetch and refresh a Vault token, the component now logs in directly using the pod's own service account token and keeps the session renewed in the background on its own.
vaultAuthMethod: tokenstill works exactly as before — this is purely additive, opt-in viavaultAuthMethod: kubernetes.Along the way, the component was also moved off a hand-rolled
net/httpclient onto the officialgithub.com/hashicorp/vault/apiSDK, which cut out a lot of custom TLS/HTTP plumbing. That migration turned up a real bug: a soft-deleted or destroyed KV v2 secret comes back from Vault as a 404 that still looks like a valid secret to the SDK, so without a fixGetSecretwould return a confusing error instead of "not found", andBulkGetSecretwould fail the whole batch instead of just skipping the deleted one. Fixed with a regression test.Issue reference
The issue this PR will close: #4104
Checklist