* Add Vault SecretRef plugin Signed-off-by: sallyom <somalley@redhat.com> * expand Vault setup to registered SecretRef targets Signed-off-by: sallyom <somalley@redhat.com> * fix(vault): use sdk secret target seam * fix(vault): preserve auth profile target paths * docs(vault): document plugin enable step * fix(vault): make status provider-alias aware * fix(vault): reject noncanonical secret ids * fix(vault): separate resolver timeout deadlines * fix(vault): forward private CA trust settings * fix(secrets): preserve plugin policy boundaries --------- Signed-off-by: sallyom <somalley@redhat.com> Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
10 KiB
| summary | read_when | title | |||
|---|---|---|---|---|---|
| Use the bundled Vault plugin to resolve SecretRefs from HashiCorp Vault |
|
Vault SecretRefs |
Vault SecretRefs
The bundled Vault plugin lets OpenClaw resolve exec SecretRefs from
HashiCorp Vault at Gateway startup and reload time. OpenClaw stores Vault
references in config, keeps resolved values in the in-memory secrets snapshot,
and does not write the resolved API keys back to openclaw.json.
Use this when you already run Vault or want model provider keys to live outside OpenClaw config files. For the SecretRef runtime model, see Secrets management.
Before you begin
You need:
- OpenClaw with the bundled
vaultplugin available - a reachable Vault server
- Vault auth that can produce a client token with read access to the secret paths OpenClaw should resolve
- the environment that starts the Gateway must include
VAULT_ADDRand eitherVAULT_TOKEN,OPENCLAW_VAULT_AUTH_METHOD=token_filewithVAULT_TOKEN_FILE, or a configured JWT/Kubernetes login
The resolver talks to Vault over HTTP from Node. The Gateway does not need the Vault CLI to resolve SecretRefs.
Enable the bundled plugin before running the openclaw vault commands:
openclaw plugins enable vault
Store a provider key in Vault
OpenClaw defaults to KV v2 mounted at secret, matching Vault dev-server
examples. For production Vault, set OPENCLAW_VAULT_KV_MOUNT to your actual KV
mount path before creating SecretRef ids. With the OpenClaw defaults, this
SecretRef id:
providers/openrouter/apiKey
reads this Vault field:
secret/data/providers/openrouter -> apiKey
One way to create it with the Vault CLI is:
export OPENROUTER_API_KEY=<openrouter-api-key>
vault kv put secret/providers/openrouter apiKey="$OPENROUTER_API_KEY"
Use a scoped client token for OpenClaw, not a root token. For the default KV v2 layout, a minimal policy for model provider keys looks like:
path "secret/data/providers/*" {
capabilities = ["read"]
}
Make Vault visible to the Gateway
For an uncontainerized local Gateway, export Vault settings in the same shell
that starts OpenClaw. The default auth method reads a Vault client token from
VAULT_TOKEN:
export VAULT_ADDR=https://vault.example.com
export VAULT_TOKEN=<vault-client-token>
If Vault Agent writes a token sink file, use token-file auth:
export VAULT_ADDR=https://vault.example.com
export OPENCLAW_VAULT_AUTH_METHOD=token_file
export VAULT_TOKEN_FILE=/vault/secrets/token
For a Vault server signed by a private CA, either install that CA in the host trust store and enable Node system trust:
export NODE_USE_SYSTEM_CA=1
Or provide a PEM bundle directly:
export NODE_EXTRA_CA_CERTS=/path/to/vault-ca.pem
These variables must be present when OpenClaw starts. The Vault plugin forwards them to its resolver process.
For non-interactive JWT auth, use a workload JWT file and a Vault role of type
jwt:
export VAULT_ADDR=https://vault.example.com
export OPENCLAW_VAULT_AUTH_METHOD=jwt
export OPENCLAW_VAULT_AUTH_MOUNT=jwt
export OPENCLAW_VAULT_AUTH_ROLE=openclaw
export OPENCLAW_VAULT_JWT_FILE=/var/run/secrets/tokens/vault
The JWT file should be a projected workload token, such as a Kubernetes service account token with an audience accepted by the Vault role. Interactive OIDC browser login is useful for humans, but Gateway runtime needs non-interactive JWT login or a token file.
For Vault's Kubernetes auth method, use kubernetes. This is intended for
Gateways running as Pods; the default mount is kubernetes, and the default JWT
file is the standard service account token path:
export VAULT_ADDR=https://vault.example.com
export OPENCLAW_VAULT_AUTH_METHOD=kubernetes
export OPENCLAW_VAULT_AUTH_ROLE=openclaw
Set OPENCLAW_VAULT_AUTH_MOUNT only when Vault mounted Kubernetes auth somewhere
other than auth/kubernetes. Set OPENCLAW_VAULT_JWT_FILE only when the service
account token is projected at a custom path.
Optional settings:
export VAULT_NAMESPACE=<namespace-name>
export OPENCLAW_VAULT_KV_MOUNT=secret
export OPENCLAW_VAULT_KV_VERSION=2
Check what the current shell can see:
openclaw vault status
When more than one Vault-backed secret provider is configured, select one by alias:
openclaw vault status --provider-alias corp-vault
openclaw vault status never prints VAULT_TOKEN; it reports only whether the
token, token file, and JWT file are set.
Generate and apply a SecretRef plan
Create a plan that maps OpenRouter's model provider API key to Vault:
openclaw vault setup \
--plan-out ./vault-secrets-plan.json \
--openrouter-id providers/openrouter/apiKey
Apply and verify the plan:
openclaw secrets apply --from ./vault-secrets-plan.json --dry-run --allow-exec
openclaw secrets apply --from ./vault-secrets-plan.json --allow-exec
openclaw secrets audit --check --allow-exec
openclaw secrets reload
Use --allow-exec because the Vault plugin resolves through an OpenClaw-managed
exec SecretRef provider.
If the Gateway is not running yet, start it normally after applying the plan
instead of running openclaw secrets reload.
Configure more provider keys
Built-in shortcuts:
openclaw vault setup --openai-id providers/openai/apiKey
openclaw vault setup --anthropic-id providers/anthropic/apiKey
openclaw vault setup --openrouter-id providers/openrouter/apiKey
Multiple provider keys in one plan:
openclaw vault setup \
--plan-out ./vault-secrets-plan.json \
--openai-id providers/openai/apiKey \
--anthropic-id providers/anthropic/apiKey \
--openrouter-id providers/openrouter/apiKey
Bundled providers without shortcuts, or already-configured OpenAI-compatible and
custom model providers, use --provider-key:
openclaw vault setup \
--plan-out ./vault-secrets-plan.json \
--provider-key local-openai=providers/local-openai/apiKey \
--provider-key groq=providers/groq/apiKey
Each --provider-key <provider=id> writes a SecretRef to
models.providers.<provider>.apiKey. For custom providers, it does not create
the provider's baseUrl, api, or models settings; configure those first.
Use --target <path=id> for any known SecretRef target path:
openclaw vault setup \
--target channels.telegram.botToken=channels/telegram/botToken \
--target models.providers.openai.headers.x-api-key=providers/openai/proxyKey \
--target auth-profiles:main:profiles.openai.key=providers/openai/apiKey
Bare target paths apply to openclaw.json. Use
auth-profiles:<agentId>:<path> for existing auth-profiles.json targets.
The target path must be a registered OpenClaw SecretRef target. The setup
command does not create arbitrary named secrets in OpenClaw; Vault remains the
secret store, and OpenClaw stores SecretRefs only on supported config fields.
SecretRef id format
Vault SecretRef ids use this convention:
<vault-secret-path>/<field>
Examples:
| SecretRef id | Default KV v2 Vault read | Returned field |
|---|---|---|
providers/openrouter/apiKey |
secret/data/providers/openrouter |
apiKey |
providers/openai/apiKey |
secret/data/providers/openai |
apiKey |
teams/agent-prod/openrouter |
secret/data/teams/agent-prod |
openrouter |
The returned Vault field must be a string.
For KV v1, set:
export OPENCLAW_VAULT_KV_VERSION=1
Then providers/openrouter/apiKey reads:
secret/providers/openrouter -> apiKey
What OpenClaw stores
Applying a Vault setup plan stores a plugin-managed provider:
{
"source": "exec",
"pluginIntegration": {
"pluginId": "vault",
"integrationId": "vault"
}
}
Credential fields point at that provider:
{ "source": "exec", "provider": "vault", "id": "providers/openrouter/apiKey" }
The resolved value lives only in the active runtime secrets snapshot.
Containers and managed deployments
Containerized Gateways still use the same plugin and SecretRef config. The container must receive:
VAULT_ADDR- one auth source:
VAULT_TOKENOPENCLAW_VAULT_AUTH_METHOD=token_fileplusVAULT_TOKEN_FILEOPENCLAW_VAULT_AUTH_METHOD=jwtplusOPENCLAW_VAULT_AUTH_MOUNT,OPENCLAW_VAULT_AUTH_ROLE, andOPENCLAW_VAULT_JWT_FILEOPENCLAW_VAULT_AUTH_METHOD=kubernetesplusOPENCLAW_VAULT_AUTH_ROLE; optionally overrideOPENCLAW_VAULT_AUTH_MOUNTorOPENCLAW_VAULT_JWT_FILE
- optional
VAULT_NAMESPACE,OPENCLAW_VAULT_KV_MOUNT, andOPENCLAW_VAULT_KV_VERSION
When using Kubernetes, prefer OPENCLAW_VAULT_AUTH_METHOD=kubernetes
when Vault has Kubernetes auth configured for the cluster. Use
OPENCLAW_VAULT_AUTH_METHOD=jwt only when Vault is configured to treat the cluster
as a generic JWT/OIDC issuer. Either option is better than a long-lived Vault
token in a Kubernetes Secret. Vault Agent sidecar or injector deployments can
use token_file instead.
For multi-tenant Vault setups, keep tenant routing in Vault policy and
deployment config. OpenClaw does not require a fixed mount, role, or path: each
Gateway environment can set its own OPENCLAW_VAULT_KV_MOUNT,
OPENCLAW_VAULT_AUTH_ROLE, and SecretRef ids. If one shared Gateway must resolve
different Vault users at the same time, use manually configured exec providers
that wrap distinct auth environments, or split tenants across Gateway
environments with separate Vault env.