HashiCorp Vault — Templated Policies

2 minute read

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