Skip to content

feat(secretstores/hashicorp/vault): add native Kubernetes auth - #4448

Open
icmp0x8 wants to merge 15 commits into
dapr:mainfrom
icmp0x8:feat/vault-kubernetes-auth
Open

feat(secretstores/hashicorp/vault): add native Kubernetes auth#4448
icmp0x8 wants to merge 15 commits into
dapr:mainfrom
icmp0x8:feat/vault-kubernetes-auth

Conversation

@icmp0x8

@icmp0x8 icmp0x8 commented Jul 12, 2026

Copy link
Copy Markdown

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: token still works exactly as before — this is purely additive, opt-in via vaultAuthMethod: kubernetes.

Along the way, the component was also moved off a hand-rolled net/http client onto the official github.com/hashicorp/vault/api SDK, 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 fix GetSecret would return a confusing error instead of "not found", and BulkGetSecret would 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

icmp0x8 added 2 commits July 12, 2026 19:54
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>
@icmp0x8
icmp0x8 requested review from a team as code owners July 12, 2026 18:58
Comment on lines +168 to +172
config := api.DefaultConfig()
if config.Error != nil {
return fmt.Errorf("couldn't build vault client config: %w", config.Error)
}
config.Address = v.vaultAddress

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

icmp0x8 added 4 commits July 14, 2026 19:37
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread secretstores/hashicorp/vault/kubernetes_auth.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Comment thread secretstores/hashicorp/vault/vault.go
Comment thread secretstores/hashicorp/vault/vault.go
@icmp0x8

icmp0x8 commented Jul 15, 2026

Copy link
Copy Markdown
Author

@mikeee please run again workflows, seems i fixed them

@icmp0x8

icmp0x8 commented Jul 16, 2026

Copy link
Copy Markdown
Author

@acroca thx for running checks. Looks like the 2 failing checks are flaky, aren't they?

@acroca

acroca commented Jul 16, 2026

Copy link
Copy Markdown
Member

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>
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.37255% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.63%. Comparing base (cc03682) to head (5c83526).
⚠️ Report is 43 commits behind head on main.

Files with missing lines Patch % Lines
secretstores/hashicorp/vault/vault.go 79.69% 20 Missing and 7 partials ⚠️
secretstores/hashicorp/vault/kubernetes_auth.go 84.50% 7 Missing and 4 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dapr-bot

dapr-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

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!

@dapr-bot dapr-bot added the stale label Sep 3, 2026
@dapr-bot dapr-bot removed the stale label Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Kubernetes Auth Method for HashiCorp Vault Secret Store

5 participants