HashiCorp Vault — Templated Policies
Templated policies use identity variables to create dynamic, reusable policies that scope access based on the authenticated entity’s metadata.
Why Templated Policies?
Without templates, you need a separate policy for every user/app:
# Without templates — one policy per user
# alice-policy.hcl
path "secret/data/users/alice/*" {
capabilities = ["create", "read", "update", "delete"]
}
# bob-policy.hcl
path "secret/data/users/bob/*" {
capabilities = ["create", "read", "update", "delete"]
}
With templates, one policy covers everyone:
# user-workspace.hcl — ONE policy for all users
path "secret/data/users/{{identity.entity.name}}/*" {
capabilities = ["create", "read", "update", "delete"]
}
Available Template Variables
| Variable | Value |
|---|---|
{{identity.entity.id}} |
Entity’s unique ID |
{{identity.entity.name}} |
Entity name |
{{identity.entity.metadata.<KEY>}} |
Custom metadata on the entity |
{{identity.entity.aliases.<mount_accessor>.id}} |
Alias ID for a specific auth mount |
{{identity.entity.aliases.<mount_accessor>.name}} |
Alias name (e.g., username) |
{{identity.entity.aliases.<mount_accessor>.metadata.<KEY>}} |
Alias metadata |
{{identity.groups.ids.<GROUP_ID>.name}} |
Group name by ID |
{{identity.groups.names.<GROUP_NAME>.id}} |
Group ID by name |
Common Patterns
Per-User Workspace
# Each user gets their own secret space
path "secret/data/users/{{identity.entity.name}}/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/users/{{identity.entity.name}}/*" {
capabilities = ["list", "read"]
}
Per-Application Secrets
# Entity metadata includes "app_name" — set when creating the entity
path "secret/data/apps/{{identity.entity.metadata.app_name}}/*" {
capabilities = ["read"]
}
path "secret/metadata/apps/{{identity.entity.metadata.app_name}}/*" {
capabilities = ["list"]
}
Per-Environment Access
# Entity metadata includes "environment" (dev, staging, prod)
path "secret/data/{{identity.entity.metadata.environment}}/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
Group-Based Paths
# Access scoped to the user's group
path "secret/data/teams/{{identity.groups.names.default.name}}/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
Setting Up Entity Metadata
For templated policies to work, entities need metadata:
# Find the entity (after user logs in)
vault read identity/entity/name/alice
# Update entity metadata
vault write identity/entity/name/alice \
metadata=app_name="payment-service" \
metadata=environment="production" \
metadata=team="backend"
# Or create an entity with metadata
vault write identity/entity \
name="webapp" \
metadata=app_name="webapp" \
metadata=environment="staging"
Complete Example
Goal: Each team gets its own secret namespace.
# team-workspace.hcl
# Scoped by the "team" metadata on each entity
path "secret/data/teams/{{identity.entity.metadata.team}}/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/teams/{{identity.entity.metadata.team}}/*" {
capabilities = ["list", "read"]
}
# Everyone can read shared secrets
path "secret/data/shared/*" {
capabilities = ["read"]
}
path "secret/metadata/shared/*" {
capabilities = ["list"]
}
# Write the policy
vault policy write team-workspace team-workspace.hcl
# Create users with team metadata
vault write auth/userpass/users/alice password="pass" policies="team-workspace"
vault write auth/userpass/users/bob password="pass" policies="team-workspace"
# Set entity metadata after first login
# Alice logs in → entity created automatically
vault login -method=userpass username=alice password="pass"
# Find entity ID
ENTITY_ID=$(vault read -field=id identity/entity/name/alice)
vault write identity/entity/id/$ENTITY_ID metadata=team="backend"
# Bob logs in
vault login -method=userpass username=bob password="pass"
ENTITY_ID=$(vault read -field=id identity/entity/name/bob)
vault write identity/entity/id/$ENTITY_ID metadata=team="frontend"
# Now Alice can read/write secret/data/teams/backend/*
# Bob can read/write secret/data/teams/frontend/*
# Neither can access the other's path
Quick Reference
| Template Variable | Example |
|---|---|
{{identity.entity.name}} |
Per-user paths |
{{identity.entity.metadata.KEY}} |
Per-app, per-team, per-env |
{{identity.groups.names.GROUP.name}} |
Group-based paths |
Next: Policy Management