Vault policies are the foundation of access control in HashiCorp Vault. They determine what paths each token can access and what operations it can perform. This guide covers everything you need to write effective policies, from basic syntax to advanced patterns.
Understanding Vault Policies
Vault follows a deny-by-default model: if a policy doesn't explicitly grant access to a path, access is denied. This secure-by-default approach ensures that permissions must be consciously granted.
Key Concepts
- Path: A location in Vault's API (e.g.,
secret/data/myapp) - Capabilities: Operations allowed on a path (read, write, delete, etc.)
- Policy: A document defining path-capability mappings
- Token: Has one or more policies attached, determining its permissions
┌─────────────────┐
│ Token │
│ ┌───────────┐ │ ┌─────────────────┐ ┌─────────────────┐
│ │ Policies │──│────▶│ Policy Rules │────▶│ Path Access │
│ │ [a, b, c] │ │ │ (Capabilities) │ │ (Allow/Deny) │
│ └───────────┘ │ └─────────────────┘ └─────────────────┘
└─────────────────┘
Policy Syntax
Policies are written in HCL (HashiCorp Configuration Language) or JSON.
Basic Structure
# Policy granting read access to a specific path
path "secret/data/myapp" {
capabilities = ["read"]
}
# Policy granting full access to a path prefix
path "secret/data/team/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
Capabilities Explained
| Capability | HTTP Verb | Description |
|---|---|---|
create | POST | Create new data (path doesn't exist) |
read | GET | Read existing data |
update | POST/PUT | Modify existing data |
delete | DELETE | Remove data |
list | LIST | List keys at a path |
sudo | - | Access root-protected endpoints |
deny | - | Explicitly deny access (overrides allows) |
Note: For KV v2 secrets engine, create and update are both needed for writing secrets, as the API uses POST for both operations.
Common Capability Combinations
# Read-only access
path "secret/data/readonly/*" {
capabilities = ["read", "list"]
}
# Full CRUD access
path "secret/data/full/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
# Write-only (no read back)
path "secret/data/writeonly/*" {
capabilities = ["create", "update"]
}
# Admin access (includes sudo)
path "sys/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
Path Patterns and Wildcards
Glob Patterns (*)
The * wildcard matches any characters within a single path segment:
# Matches: secret/data/app1, secret/data/app2
# Does NOT match: secret/data/app1/config
path "secret/data/*" {
capabilities = ["read"]
}
Segment Wildcards (+)
The + wildcard matches exactly one path segment:
# Matches: secret/data/app1/config, secret/data/app2/config
# Does NOT match: secret/data/config
path "secret/data/+/config" {
capabilities = ["read"]
}
Prefix Matching
Paths without wildcards match exactly that path:
# Only matches exactly: secret/data/myapp
path "secret/data/myapp" {
capabilities = ["read"]
}
Path Examples
# All secrets under a team namespace
path "secret/data/team-alpha/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
# All metadata paths (for KV v2)
path "secret/metadata/*" {
capabilities = ["list", "read"]
}
# Specific application config
path "secret/data/+/database" {
capabilities = ["read"]
}
# All auth method configurations
path "auth/+/config" {
capabilities = ["read"]
}
Built-in Policies
Vault has two built-in policies that cannot be deleted:
Default Policy
Attached to all tokens automatically (unless -no-default-policy used):
# Allow tokens to look up their own properties
path "auth/token/lookup-self" {
capabilities = ["read"]
}
# Allow tokens to renew themselves
path "auth/token/renew-self" {
capabilities = ["update"]
}
# Allow tokens to revoke themselves
path "auth/token/revoke-self" {
capabilities = ["update"]
}
# Allow looking up own capabilities
path "sys/capabilities-self" {
capabilities = ["update"]
}
Root Policy
Grants unlimited access to everything. Only attached to root tokens:
# Implicit: allows everything
path "*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
Creating and Managing Policies
Write a Policy from File
# Create policy file
cat > readonly.hcl << 'EOF'
path "secret/data/*" {
capabilities = ["read", "list"]
}
EOF
# Write to Vault
vault policy write readonly readonly.hcl
Write a Policy Inline
vault policy write developer - << 'EOF'
# Developer policy
path "secret/data/dev/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/data/shared/*" {
capabilities = ["read", "list"]
}
EOF
List Policies
# List all policy names
vault policy list
# Output:
# default
# developer
# readonly
# root
Read a Policy
vault policy read developer
Update a Policy
# Simply write again with same name
vault policy write developer updated-developer.hcl
Changes take effect immediately for all tokens using that policy.
Delete a Policy
vault policy delete developer
Warning: Existing tokens retain the policy name but lose all associated permissions.
Attaching Policies to Tokens
Direct Token Creation
# Create token with specific policies
vault token create -policy=developer -policy=readonly
# Create token with no default policy
vault token create -policy=minimal -no-default-policy
Auth Method Configuration
# AppRole
vault write auth/approle/role/cicd \
token_policies="ci-policy,readonly"
# LDAP group mapping
vault write auth/ldap/groups/developers \
policies="developer,shared-readonly"
# Userpass
vault write auth/userpass/users/alice \
password="secret" \
policies="developer"
Testing Policies
Check Token Capabilities
# Test current token's capabilities on a path
vault token capabilities secret/data/myapp
# Output: create, read, update, delete, list
# Test specific token's capabilities
vault token capabilities <token> secret/data/myapp
Capabilities API
# Check via API
curl -H "X-Vault-Token: $VAULT_TOKEN" \
-X POST \
-d '{"paths": ["secret/data/myapp", "secret/data/other"]}' \
$VAULT_ADDR/v1/sys/capabilities-self
Policy Simulation
Test a policy before deploying:
# Create test token with the policy
vault token create -policy=test-policy -ttl=5m
# Try operations with the test token
VAULT_TOKEN=<test-token> vault kv get secret/myapp
Advanced Policy Features
Allowed/Denied Parameters
Control which parameters can be used:
path "secret/data/restricted" {
capabilities = ["create", "update"]
# Only allow specific keys
allowed_parameters = {
"data" = ["allowed_key1", "allowed_key2"]
}
}
path "secret/data/safe" {
capabilities = ["create", "update"]
# Deny dangerous parameters
denied_parameters = {
"data" = ["admin_password", "root_key"]
}
}
Required Parameters
Ensure certain parameters are always provided:
path "secret/data/audit-required" {
capabilities = ["create", "update"]
required_parameters = ["data"]
}
Min/Max Wrapping TTL
Control response wrapping:
path "secret/data/sensitive" {
capabilities = ["read"]
# Require wrapping between 1 minute and 1 hour
min_wrapping_ttl = "1m"
max_wrapping_ttl = "1h"
}
MFA Requirements (Enterprise)
path "secret/data/mfa-protected/*" {
capabilities = ["read"]
mfa_methods = ["totp"]
}
Policy Examples
Read-Only User
# read-only.hcl
# Can read secrets but not modify them
path "secret/data/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/*" {
capabilities = ["read", "list"]
}
Application-Specific Policy
# app-myservice.hcl
# Full access to own namespace, read shared config
path "secret/data/apps/myservice/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/apps/myservice/*" {
capabilities = ["read", "list", "delete"]
}
path "secret/data/shared/config" {
capabilities = ["read"]
}
path "database/creds/myservice-db" {
capabilities = ["read"]
}
CI/CD Pipeline Policy
# ci-policy.hcl
# Read deployment secrets, no write access
path "secret/data/deploy/*" {
capabilities = ["read"]
}
path "secret/data/ci/+/config" {
capabilities = ["read"]
}
# Read database credentials
path "database/creds/deploy-readonly" {
capabilities = ["read"]
}
# No access to production secrets
path "secret/data/prod/*" {
capabilities = ["deny"]
}
Team Admin Policy
# team-admin.hcl
# Manage team secrets and limited user management
# Full access to team namespace
path "secret/data/team-alpha/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/team-alpha/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
# Create tokens for team members
path "auth/token/create" {
capabilities = ["update"]
allowed_parameters = {
"policies" = ["team-alpha-member", "team-alpha-readonly"]
}
}
# Look up team tokens
path "auth/token/lookup" {
capabilities = ["update"]
}
Vault Administrator Policy
# vault-admin.hcl
# Full administrative access (use sparingly)
# Manage all secrets
path "secret/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
# Manage policies
path "sys/policies/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
# Manage auth methods
path "sys/auth/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "auth/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
# System configuration
path "sys/config/*" {
capabilities = ["create", "read", "update", "delete"]
}
# Audit logging
path "sys/audit/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# Cannot manage other admins (separation of duties)
path "auth/userpass/users/admin-*" {
capabilities = ["deny"]
}
Best Practices
1. Implement Least Privilege
# BAD: Too broad
path "secret/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
# GOOD: Specific to application
path "secret/data/apps/myapp/*" {
capabilities = ["read"]
}
2. Use Explicit Deny for Sensitive Paths
# Deny access to production even if other rules might allow
path "secret/data/prod/*" {
capabilities = ["deny"]
}
# Then allow specific production access where needed
path "secret/data/prod/public-config" {
capabilities = ["read"]
}
3. Separate Read and Write Policies
# team-reader.hcl
path "secret/data/team/*" {
capabilities = ["read", "list"]
}
# team-writer.hcl
path "secret/data/team/*" {
capabilities = ["create", "update"]
}
# Assign both to users who need write access
4. Use Descriptive Policy Names
# Good naming convention
vault policy write app-payments-readonly payments-readonly.hcl
vault policy write team-platform-admin platform-admin.hcl
vault policy write ci-deploy-staging ci-deploy-staging.hcl
5. Version Control Your Policies
# Store policies in git
git add policies/
git commit -m "Add payment service policy"
# Apply via CI/CD
for policy in policies/*.hcl; do
name=$(basename "$policy" .hcl)
vault policy write "$name" "$policy"
done
6. Audit Policy Changes
# Enable audit logging
vault audit enable file file_path=/var/log/vault/audit.log
# Policy changes appear in audit log
Troubleshooting
Permission Denied Errors
# Check what policies your token has
vault token lookup
# Check capabilities on specific path
vault token capabilities secret/data/myapp
# Read the policy to see what's allowed
vault policy read <policy-name>
Policy Not Taking Effect
- Check token policies:
vault token lookup - Verify policy content:
vault policy read <name> - Check path format: KV v2 uses
secret/data/prefix - Check for deny rules: Deny overrides allow
Debugging Path Issues
# For KV v2, remember the path structure:
# - secret/data/* for reading/writing secrets
# - secret/metadata/* for metadata operations
# - secret/delete/* for soft delete
# - secret/undelete/* for recovery
# - secret/destroy/* for permanent deletion
Command Reference
| Command | Description |
|---|---|
vault policy list | List all policies |
vault policy read <name> | Show policy content |
vault policy write <name> <file> | Create/update policy |
vault policy delete <name> | Delete policy |
vault policy fmt <file> | Format policy file |
vault token capabilities <path> | Check current token's access |
vault token capabilities <token> <path> | Check specific token's access |
Next Steps
- Set up AppRole for CI/CD with proper policies
- Learn Token Management for attaching policies
- Configure Authentication Methods with policy mappings
For more Vault security guides, explore our complete HashiCorp Vault series.


