The zhi-store-vault-manager meta-plugin wraps the standard zhi-store-vault store with automatic credential lifecycle management for deployed applications. It generates least-privilege Vault policies, creates AppRole roles, and injects ephemeral credentials into the configuration tree – all driven by metadata labels on your config values.
Without the vault-manager, applications that consume secrets from Vault need manually provisioned AppRoles, hand-written policies, and a separate credential delivery pipeline. The vault-manager automates this:
vault.app.<name> metadata labels declaring which apps need access and at what privilege levelvault/credentials/<app>/ paths – visible in exports but never persisted to the storeThe admin token used by the meta-plugin never leaves the plugin process. Child operations against zhi-store-vault receive short-lived scoped tokens.
zhi-store-vault plugin binary installed (the meta-plugin launches it as a child process)zhi-store-vault-manager plugin binary installed alongside zhi-store-vaultInstall both plugins:
zhi plugin install oci://ghcr.io/mrwong99/zhi/zhi-store-vault:latest
zhi plugin install oci://ghcr.io/mrwong99/zhi/zhi-store-vault-manager:latest
Use vault-manager as your store provider instead of vault:
store:
provider: vault-manager
options:
addr: "http://127.0.0.1:8200"
mount: "secret"
prefix: "zhi"
workspace: "production"
# auth:
# method: token
# credentials:
# token: "hvs.xxxxx"
| Option | Env Fallback | Default | Description |
|---|---|---|---|
addr |
VAULT_ADDR |
http://127.0.0.1:8200 |
Vault server address |
mount |
ZHI_VAULT_MOUNT |
secret |
KV v2 mount path |
prefix |
ZHI_VAULT_PREFIX |
zhi |
Path prefix for all zhi secrets |
namespace |
VAULT_NAMESPACE |
(empty) | Vault Enterprise namespace |
workspace |
default |
Workspace name for policy/role naming | |
apps |
(none) | List of application credential configs (see below) |
The apps list declares which applications should receive credentials. Each entry specifies the auth method and its parameters:
store:
provider: vault-manager
options:
addr: "http://127.0.0.1:8200"
mount: "secret"
apps:
- name: web-api
auth: approle
wrapped: true
wrap_ttl: "120s"
- name: batch-worker
auth: token
token_ttl: "1h"
token_policies:
- extra-read-policy
| Field | Default | Description |
|---|---|---|
name |
(required) | Application name, used in policy and path naming |
auth |
approle |
Auth method: approle or token |
wrapped |
true |
(approle only) Use response-wrapping for the secret_id |
wrap_ttl |
120s |
(approle only) Wrapping token TTL |
token_ttl |
1h |
(token only) Token lifetime |
token_policies |
(none) | (token only) Additional policies beyond the auto-generated one |
The vault-manager uses vault.app.<name> metadata labels on config values to determine access policies. The label value is a comma-separated list of capabilities: read, write, delete.
Set labels on config values in your config provider:
# config/database/host.yaml
value: "db.example.com"
metadata:
vault.app.web-api: "read"
vault.app.batch-worker: "read,write"
The meta-plugin translates these into Vault ACL policies:
| Label Capability | Vault Capabilities |
|---|---|
read |
read on data path, read + list on metadata path |
write |
create, update on data path |
delete |
delete on data path |
Policies follow the naming convention zhi-<workspace>-<app> (e.g. zhi-production-web-api).
Generated credentials appear at vault/credentials/<app>/ paths in the configuration tree. These values are marked with the store.ephemeral label and are never written to the Vault backend.
For an AppRole app, the injected paths are:
| Path | Description |
|---|---|
vault/credentials/<app>/auth-method |
Always approle |
vault/credentials/<app>/role-id |
The AppRole role_id |
vault/credentials/<app>/wrapped-secret-id |
Response-wrapped secret_id (single-use) |
For a token app:
| Path | Description |
|---|---|
vault/credentials/<app>/auth-method |
Always token |
vault/credentials/<app>/token |
A short-lived Vault token |
All credential values carry these labels: ui.hidden, store.writeonly, store.ephemeral, ui.readonly, and a core.description.
Before using the vault-manager, create a Vault policy for the admin token. The zhi vault-credentials bootstrap command generates the required HCL:
# Preview the policy
zhi vault-credentials bootstrap --dry-run
# Apply it to Vault
vault policy write zhi-vault-manager - <<'EOF'
path "secret/data/zhi/*" {
capabilities = ["read", "create", "update", "delete", "list"]
}
path "secret/metadata/zhi/*" {
capabilities = ["read", "list", "delete"]
}
path "sys/policies/acl/zhi-*" {
capabilities = ["read", "create", "update", "delete", "list"]
}
path "auth/approle/role/zhi-*" {
capabilities = ["read", "create", "update", "delete", "list"]
}
path "auth/approle/role/zhi-*/secret-id" {
capabilities = ["create", "update"]
}
path "auth/token/create-orphan" {
capabilities = ["create", "update"]
}
EOF
Then create a token with this policy and configure it in your workspace (or log in interactively):
vault token create -policy=zhi-vault-manager -ttl=720h
Use zhi vault-credentials refresh to generate fresh credentials without a full export/apply cycle. This is useful during application restarts or rolling deployments:
# All apps, JSON output
zhi vault-credentials refresh
# Single app, env-var format (for shell eval)
zhi vault-credentials refresh --app web-api --output env
# Quiet mode (just the count)
zhi vault-credentials refresh --output quiet
See the CLI Reference for the full flag list.
zhi-store-vault receives scoped one-time tokens per operation via WithIsolatedEnv.vault.app.<name> label.store.ephemeral) are filtered out during PutValues, so credentials exist only in-memory during a session.The vault-manager is a meta-plugin built on the SDK’s delegate pattern:
zhi engine
└── zhi-store-vault-manager (meta-plugin)
├── adminClient (Vault HTTP API)
├── credManager (policies, roles, credentials)
└── zhi-store-vault (child plugin, launched via launch.LaunchStore)
All 28 store interface methods are forwarded to the child via store.DelegatingPlugin. The meta-plugin overrides three:
vault/credentials/ paths are requestedstore.ephemeral values before delegatingzhi vault-credentials commands