Vault

HashiCorp Vault KV v2, with lease-aware refresh, built on hashicorp/vault/api.

Schemevault://
Modulegithub.com/xavidop/mamori/providers/vault
Sensitiveyes
Watchlease-aware poll
AuthVAULT_ADDR, VAULT_TOKEN

Install

go get github.com/xavidop/mamori/providers/vault
import _ "github.com/xavidop/mamori/providers/vault"

Using the ref

A vault:// ref points at one secret in a KV v2 engine (or a dynamic/leased secret path).

vault://<mount>/<path>[#key][?renew=true]
PartRequiredWhat it means
<mount>yesThe KV v2 mount point, e.g. secret.
<path>yesThe secret path within that mount. mamori reads the physical <mount>/data/<path> for you; a leading data/ is tolerated, so vault://secret/app and vault://secret/data/app are equivalent.
#keynoReturn one field of the secret’s data map. Without it, the whole data map is returned as JSON.
?renew=truenoRenew a renewable lease on read (dynamic secrets), so the refresh deadline follows the renewed lease.

Examples

  • vault://secret/app#password selects the password field of the KV v2 secret at secret/app.
  • vault://secret/app returns the whole secret/app data map as JSON - decode it with flatten:"json".
  • vault://database/creds/readonly#password?renew=true reads a dynamic database credential and keeps its lease renewed.
type Config struct {
	// KV v2 at mount "secret", path "app"; select the "password" field
	DBPassword secret.String `source:"vault://secret/app#password"`
	// dynamic/leased secret, renewed automatically
	Lease      secret.String `source:"vault://database/creds/readonly#password?renew=true"`
}

Values are always Sensitive, and Value.Version is the KV v2 metadata version (a content hash for reads without version metadata).

Leases and refresh

When the read secret carries a lease (LeaseDuration > 0), the provider sets Value.NotAfter = now + LeaseDuration, and mamori schedules a refresh before the lease expires rather than waiting for the poll interval. ?renew=true renews a renewable lease via Sys().Renew and derives NotAfter from the renewed lease.

Vault KV has no native push, so the provider does not implement Watch; mamori polls, and NotAfter drives lease-aware refresh.

Explicit configuration

import vaultprov "github.com/xavidop/mamori/providers/vault"

mamori.WithProvider(vaultprov.New(
	vaultprov.WithAddress("https://vault.internal:8200"),
	vaultprov.WithToken(os.Getenv("VAULT_TOKEN")),
	vaultprov.WithNamespace("team-a"),
))

Error classification

Vault responsemamori kind
404, api.ErrSecretNotFoundnot_found
403permission_denied
429rate_limited
5xx (including a sealed vault’s 503)unavailable
400invalid
Malformed ref (not <mount>/<path>)invalid
anything elseunknown

Vault answers 403 both for a policy that doesn’t cover the path and for a missing or expired token, and the status code can’t separate them, so both report permission_denied. If you’re investigating one, check token validity as well as policy.

Verified by unit tests (with/without #key, lease NotAfter) and the conformance kit against an in-memory fake; a dev-Vault integration test is provided behind //go:build integration.