Writing HCL-format access rules that determine who may access which path in OpenBao, distinguishing exact paths from prefix wildcards, understanding all capabilities, and testing privilege enforcement via policy write and token create.

Episode 6 brought you to the PKI Secrets Engine, where OpenBao issues TLS certificates automatically. But who is allowed to request those certificates? The answer is governed by policies. Episode 7 discusses Access Control Policies — the HCL language that determines which paths can be accessed and which actions are allowed. Without the right policies, a leaked token could roam through all of OpenBao. With the right policies, the impact of a leak can be narrowed as much as possible.
A policy is a declarative rule that connects paths with capabilities. Its structure is simple: a path block contains a list of allowed operations on that path. The deny-by-default principle applies — if a path is not mentioned at all in any policy owned by a token, access is denied.
Policies are written in HCL (HashiCorp Configuration Language), a plain text file with the .hcl extension:
# Minimal policy for an application
path "secret/data/app" {
capabilities = ["read"]
}
path "secret/data/app/*" {
capabilities = ["create", "read", "update", "delete"]
}The two blocks above show two different matching patterns — exact path and prefix wildcard. Both are often written side by side because they are complementary in nature.
The path pattern in a policy determines how broadly the rule applies:
| Pattern | Matches | Example operation |
|---|---|---|
secret/data/app | Exactly one path | Read the app secret |
secret/data/app/* | All sub-paths beneath it | Manage any key under app |
secret/data/app/ | Sub-paths with a trailing slash | All descendants without touching app itself |
This difference is crucial: secret/data/app only covers that exact path, while secret/data/app/* covers its descendants — for example secret/data/app/backend and secret/data/app/frontend. To cover both at once, you write two lines like in the example above.
There is also the + operator, which matches exactly one segment — useful when you want to restrict to a single level without an overly broad wildcard, for example secret/data/+/db.
The decision rule: OpenBao uses the longest prefix match. If multiple rules collide, the most specific rule wins. And if one of the capabilities is deny, deny always overrides any permission.
Capabilities are the operations allowed on a path. Here is the complete list:
| Capability | Meaning |
|---|---|
create | Create new data at the path |
read | Read existing data |
update | Update existing data |
delete | Delete data |
list | List the contents of a path |
sudo | Privileged access that requires a sudo-capable token (e.g. writing policies) |
deny | Deny all access, overrides other capabilities |
Note sudo — the capability that allows sensitive operations such as sys/policy or auth/token. This capability should only be granted to highly trusted users. Meanwhile, deny serves as a last-resort safety: placing it on a path guarantees a token will never touch that path, regardless of any other policy it holds.
Applying a policy is as simple as writing a file and uploading it to OpenBao:
bao policy write my-policy policy.hcl
bao policy read my-policy
bao policy listbao policy write my-policy policy.hcl takes the contents of policy.hcl and stores it as a policy named my-policy. An installed policy can be read back for auditing, and deleted with bao policy delete when no longer needed.
A policy only takes effect when a token bound to it is created. A token is created by attaching a policy:
bao token create -policy=my-policy -format=jsonThe resulting token only inherits the capabilities from my-policy (plus built-in policies). Test it with that token:
export VAULT_TOKEN="s.xxxxxxxxxxxx"
bao kv get secret/data/app
bao kv get secret/data/lainbao kv get secret/data/app succeeds because that path is covered by the policy. Conversely, bao kv get secret/data/lain is denied with permission denied because none of the token's policies cover that path. This is privilege enforcement in action: access follows the policy, not desire.
Warning
Always start from the smallest policy and add only what is needed. The least privilege principle applies strictly here — a token circulating in application code should not carry sudo capabilities or full access to secret/data/*.
A few patterns that make policies safer and easier to maintain:
app-backend, app-frontend, or admin-audit, instead of piling all access into one giant policy.read-only token does not need create and update.bao policy list then bao policy read on each policy, and make sure no outdated rules are left behind.A policy is not write-once-and-forget. It evolves with your architecture, and like code, it deserves regular review.
In this episode 7 you wrote HCL-format Access Control Policies, distinguished exact paths from prefix wildcards, learned all capabilities from create to deny, applied policies via bao policy write, and tested privilege enforcement with a policy-bound token.
Key takeaways:
deny always wins.+ and * deliberately — an overly broad wildcard is a risk.In the next episode, episode 8, we discuss who carries those policies: Authentication Methods — how users and machines identify themselves via tokens, userpass, AppRole, and SSO such as OIDC, GitHub, Okta, and Azure AD.