HashiCorp Vault — HCL Policy Syntax
Vault policies are written in HCL (HashiCorp Configuration Language). Each policy is a set of path rules that grant or deny capabilities.
Basic Structure
# policy-name.hcl
path "secret/data/myapp/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/myapp/*" {
capabilities = ["list", "read"]
}
Anatomy:
path— The Vault API path to matchcapabilities— The operations allowed on that path*— Glob wildcard (matches any characters)
Path Matching Rules
Exact Match
# Matches ONLY secret/data/myapp/config — nothing else
path "secret/data/myapp/config" {
capabilities = ["read"]
}
Glob Wildcard (*)
# Matches anything under secret/data/myapp/
# e.g., secret/data/myapp/config, secret/data/myapp/db
path "secret/data/myapp/*" {
capabilities = ["read", "list"]
}
Segment Wildcard (+)
# Matches ONE path segment
# e.g., secret/data/team-a/config, secret/data/team-b/config
# Does NOT match secret/data/team-a/sub/config
path "secret/data/+/config" {
capabilities = ["read"]
}
Root Path
# Matches the path itself (empty after prefix)
# e.g., secret/data/myapp (no trailing path)
path "secret/data/myapp" {
capabilities = ["read"]
}
Path Priority
When multiple paths match, Vault uses the most specific match:
# Broad rule
path "secret/data/*" {
capabilities = ["read"]
}
# More specific rule (wins for this exact path)
path "secret/data/admin/config" {
capabilities = ["deny"]
}
Priority order (highest to lowest):
- Exact path match (no wildcards)
- Longest prefix with
+segments - Longest prefix with
*glob - Shorter prefixes
Capabilities
path "secret/data/myapp/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
| Capability | HTTP Verb | Description |
|---|---|---|
create |
POST/PUT | Create new data |
read |
GET | Read data |
update |
POST/PUT | Update existing data |
delete |
DELETE | Delete data |
list |
LIST | List keys |
sudo |
— | Access sudo-protected paths |
deny |
— | Explicitly deny (overrides everything) |
Important KV v2 Paths
KV v2 has a different API structure than KV v1. Policies must account for the /data/, /metadata/, and /delete/ prefixes:
# Read and write secrets
path "secret/data/myapp/*" {
capabilities = ["create", "read", "update"]
}
# List secret keys
path "secret/metadata/myapp/*" {
capabilities = ["list", "read"]
}
# Delete secret versions
path "secret/delete/myapp/*" {
capabilities = ["update"]
}
# Undelete (restore) versions
path "secret/undelete/myapp/*" {
capabilities = ["update"]
}
# Permanently destroy versions
path "secret/destroy/myapp/*" {
capabilities = ["update"]
}
Multiple Paths in One Policy
# dev-team policy
# Read app secrets
path "secret/data/dev/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/dev/*" {
capabilities = ["list", "read"]
}
# Read shared secrets (read-only)
path "secret/data/shared/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/shared/*" {
capabilities = ["list", "read"]
}
# Access transit encryption
path "transit/encrypt/dev-key" {
capabilities = ["update"]
}
path "transit/decrypt/dev-key" {
capabilities = ["update"]
}
Quick Reference
| Syntax | Meaning |
|---|---|
path "exact/path" |
Exact match |
path "prefix/*" |
Glob — any characters after prefix |
path "prefix/+/suffix" |
Segment — matches one path segment |
capabilities = ["read"] |
Allow read |
capabilities = ["deny"] |
Explicit deny (overrides all) |
Next: Capabilities Deep Dive