@varlock/hashicorp-vault-plugin
v1.1.0
Published
Varlock plugin to load secrets from HashiCorp Vault (KV v2 secrets engine) / OpenBao
Readme
@varlock/hashicorp-vault-plugin
This package is a Varlock plugin that enables loading data from HashiCorp Vault (KV v2 secrets engine) / OpenBao into your configuration.
Features
- Zero-config authentication - Automatically uses Vault token from environment or CLI
- AppRole authentication - For automated and CI/CD workflows
- JWT/OIDC authentication - Authenticate from Vercel, GitHub Actions, and other platforms without long-lived credentials
- Vault CLI integration - Works seamlessly with
vault loginfor local development - Auto-infer secret keys from environment variable names
- JSON key extraction from secrets using
#syntax or namedkeyparameter - Path prefixing with
pathPrefixoption for organized secret management - Default path support for sharing a common secret path across items
- Support for Vault Enterprise namespaces
- Support for multiple Vault instances
- Automatic AppRole token caching and renewal
- Lightweight implementation using REST API (no heavy Vault SDK dependencies)
Installation
If you are in a JavaScript based project and have a package.json file, you can either install the plugin explicitly
npm install @varlock/hashicorp-vault-pluginAnd then register the plugin without any version number
# @plugin(@varlock/hashicorp-vault-plugin)Otherwise just set the explicit version number when you register it
# @plugin(@varlock/[email protected])See our Plugin Guide for more details.
Setup + Auth
After registering the plugin, you must initialize it with the @initHcpVault root decorator.
Automatic auth
For most use cases, you only need to provide the Vault URL:
# @plugin(@varlock/hashicorp-vault-plugin)
# @initHcpVault(url="https://vault.example.com:8200")How this works:
- Local development: Run
vault login→ automatically uses the token from~/.vault-token - CI/CD pipelines: Wire up a token explicitly via
token=$VAULT_TOKEN - Works everywhere with zero configuration beyond the URL!
AppRole auth (For automated workflows)
For CI/CD or server environments, use AppRole authentication:
# @plugin(@varlock/hashicorp-vault-plugin)
# @initHcpVault(
# url="https://vault.example.com:8200",
# roleId=$VAULT_ROLE_ID,
# secretId=$VAULT_SECRET_ID
# )
# ---
VAULT_ROLE_ID=
# @sensitive
VAULT_SECRET_ID=You would then need to inject these env vars using your CI/CD system.
Explicit token
You can also provide a token directly:
# @initHcpVault(
# url="https://vault.example.com:8200",
# token=$VAULT_TOKEN
# )
# ---
# @type=vaultToken @sensitive
VAULT_TOKEN=JWT/OIDC auth (For Vercel, GitHub Actions, etc.)
If you're deploying on a platform that supports OIDC, you can authenticate using Vault's JWT auth method:
# @plugin(@varlock/hashicorp-vault-plugin)
# @initHcpVault(url="https://vault.example.com:8200", jwtRole="varlock-role")The plugin auto-detects the OIDC token from your platform and exchanges it for a Vault token via the JWT auth method. You need to configure the JWT auth backend in Vault with your platform's OIDC issuer.
See the OIDC Workload Identity guide for full setup instructions.
Authentication Priority
The plugin tries authentication methods in this order:
- Explicit token - If
tokenis provided in@initHcpVault() - AppRole - If both
roleIdandsecretIdare provided - JWT/OIDC - If
jwtRoleis provided, exchanges a platform OIDC token for a Vault token - CLI token file - From
~/.vault-token(created byvault login) or~/.bao-token(created bybao loginfor OpenBao)
Vault Enterprise namespaces
For Vault Enterprise, specify the namespace:
# @initHcpVault(url="https://vault.example.com:8200", namespace="admin/team-a")Multiple instances
If you need to connect to multiple Vault instances, register named instances:
# @initHcpVault(id=prod, url="https://vault-prod.example.com:8200")
# @initHcpVault(id=dev, url="https://vault-dev.example.com:8200")Reading secrets
This plugin introduces the vaultSecret() function to fetch secret values from Vault's KV v2 secrets engine.
Since Vault KV v2 always stores key/value pairs, the item key (variable name) is automatically used as the JSON key to extract from the secret. You can override this with #KEY syntax or the key parameter.
# @plugin(@varlock/hashicorp-vault-plugin)
# @initHcpVault(url="https://vault.example.com:8200")
# ---
# Fetches "secret/db/config" and extracts "DB_HOST" key
DB_HOST=vaultSecret("secret/db/config")
# Override the extracted key with # syntax
DB_PASSWORD=vaultSecret("secret/db/config#password")
# Or use named "key" parameter
DB_PORT=vaultSecret("secret/db/config", key="PORT")
# Fetch entire secret as JSON blob
DB_CONFIG=vaultSecret("secret/db/config", raw=true)
# If using multiple instances
PROD_KEY=vaultSecret(prod, "secret/api/keys")
DEV_KEY=vaultSecret(dev, "secret/api/keys")Default path
Use defaultPath to set a common path for secrets when no path argument is provided:
# @initHcpVault(url="https://vault.example.com:8200", defaultPath=secret/myapp/config)
# ---
# Both fetch from "secret/myapp/config" extracting item key
DB_PASSWORD=vaultSecret()
API_KEY=vaultSecret()
# Override the inferred key using # syntax
STRIPE_KEY=vaultSecret("#stripe_api_key")
# Explicit path still extracts item key by default
OTHER_SECRET=vaultSecret("secret/other/path")
# Or override key on explicit path
OTHER_KEY=vaultSecret("secret/other/path#SPECIFIC_KEY")Path prefixing
Use pathPrefix to automatically prefix all secret paths:
# @initHcpVault(url="https://vault.example.com:8200", pathPrefix="secret/myapp")
# ---
# Fetches from "secret/myapp/db/config"
DB_HOST=vaultSecret("db/config#HOST")You can even use dynamic prefixes:
# @initHcpVault(url="https://vault.example.com:8200", pathPrefix="secret/${ENV}")
# In prod: fetches from "secret/prod/..."
# In dev: fetches from "secret/dev/..."
DB_HOST=vaultSecret("db/config#HOST")Bulk loading secrets
Use raw=true with @setValuesBulk to load all key/value pairs from a Vault path at once, instead of wiring up each secret individually:
# @initHcpVault(url="https://vault.example.com:8200")
# @setValuesBulk(vaultSecret("secret/myapp/config", raw=true))
# ---
DB_HOST=
DB_PASSWORD=
API_KEY=This fetches all keys from secret/myapp/config and maps them to matching item keys.
Reference
Root decorators
@initHcpVault()
Initialize a HashiCorp Vault plugin instance.
Parameters:
url: string(required) - Vault server URL (e.g.,https://vault.example.com:8200)token?: string- Explicit Vault authentication tokenroleId?: string- AppRole role ID for automated authenticationsecretId?: string- AppRole secret ID for automated authenticationnamespace?: string- Vault Enterprise namespacedefaultPath?: string- Default secret path when no path argument is given tovaultSecret()pathPrefix?: string- Prefix automatically prepended to all secret pathsjwtRole?: string- JWT auth method role name (enables OIDC workload identity)jwtAuthPath?: string- JWT auth method mount path (defaults tojwt)oidcToken?: string- Explicit OIDC JWT token (auto-detected from platform if not provided)id?: string- Instance identifier for multiple instances (defaults to_default)
Functions
vaultSecret()
Fetch a secret from HashiCorp Vault's KV v2 secrets engine.
Signatures:
vaultSecret()- UsesdefaultPath, extracts item keyvaultSecret(secretRef)- Fetch by explicit path, extracts item keyvaultSecret(secretRef, key="jsonKey")- Fetch and extract a specific keyvaultSecret(secretRef, raw=true)- Fetch all key/value pairs as JSON blob (useful with@setValuesBulk)vaultSecret(instanceId, secretRef)- Fetch from a specific Vault instance
Key extraction:
By default, the item key (variable name) is used as the JSON key to extract from the secret. You can override this with #KEY syntax in the path or the named key parameter, or use raw=true to get the full key/value blob.
Secret Ref Formats:
- Path only:
"secret/myapp/config"(extracts item key from the secret) - Path with key override:
"secret/myapp/config#DB_PASSWORD"(extracts specific key)
How paths work:
Vault KV v2 stores key/value pairs at a path. Given a path like secret/myapp/config, the plugin calls GET /v1/secret/data/myapp/config (the first path segment is the mount point, and /data/ is inserted for the KV v2 API).
Data Types
vaultToken- HashiCorp Vault authentication token (sensitive)
Vault Setup
Enable KV v2 Secrets Engine
# KV v2 is enabled by default at "secret/" in dev mode
# For production, enable it explicitly:
vault secrets enable -version=2 -path=secret kvCreate a Policy
# policy.hcl - Allow reading secrets
path "secret/data/*" {
capabilities = ["read"]
}vault policy write varlock-reader policy.hclSet Up AppRole Auth (Recommended for CI/CD)
AppRole is the recommended auth method for automated workflows:
# Enable AppRole auth method
vault auth enable approle
# Create a role
vault write auth/approle/role/varlock-role \
secret_id_ttl=24h \
token_ttl=1h \
token_max_ttl=4h \
token_policies=varlock-reader
# Get the role ID
vault read auth/approle/role/varlock-role/role-id
# Generate a secret ID
vault write -f auth/approle/role/varlock-role/secret-idSave the role_id and secret_id from the output for your CI/CD configuration.
Create a Token (For simple setups)
# Create a token with the reader policy
vault token create -policy=varlock-reader -ttl=24hStore Secrets
# Store a single key/value
vault kv put secret/myapp/config DB_PASSWORD=supersecret
# Store multiple keys
vault kv put secret/myapp/config \
DB_HOST=db.example.com \
DB_PASSWORD=supersecret \
API_KEY=abc123Troubleshooting
Secret not found
- Verify the secret exists:
vault kv get secret/myapp/config - Check the mount point is correct (first path segment, typically
secret) - Ensure you're using KV v2, not KV v1 (different API format)
Permission denied
- Check your token's policies:
vault token lookup - Ensure your policy includes
readcapability onsecret/data/*(note the/data/prefix for KV v2) - For AppRole: verify the role has the correct policies attached
Authentication failed
- Local dev: Run
vault login(orbao loginfor OpenBao) and ensureVAULT_ADDRis set correctly - CI/CD: Verify your token or AppRole credentials are properly wired up in
@initHcpVault() - Check if the token has expired:
vault token lookup - For AppRole: verify the secret ID hasn't expired and generate a new one if needed
