# Policy Authoring and Best Practices

> Chapter 16 of the Courseiva HASHICORP-VAULT curriculum — https://courseiva.com/learn/hashicorp-vault/vault-policy-best-practices

**Official objective:** 1.4 — Write and organize policies following security best practices

## Introduction

Vault policies are the access rules that control who can read, write, or delete secrets in your Vault server. Without policies, every user could access every secret, which defeats the whole purpose of having a secrets vault. For the VA-003 exam, policy authoring is a core skill — roughly 15% of the exam questions test your ability to write, interpret, and organise policies correctly.

## The 37-Page Apartment Building Rulebook Analogy

Your apartment building has 37 pages of rules. Rule 12 says 'No pets over 15 kilograms.' Rule 18 says 'The gym closes at 10 PM.' Rule 31 says 'Only residents with a blue key fob can enter the rooftop garden.' These rules are not suggestions — they are enforced by the building manager. If you break Rule 12 by bringing a 20-kilogram dog into the lift, the manager stops the lift and sends you back. If a courier tries the rooftop with a standard key fob, the door stays locked.

Now imagine the building manager did not have these rules written down. Instead, he just made decisions on the spot. One day he lets a cat into the gym. The next day he bans all visitors. Tenants would get confused and angry. Security would be unpredictable. The written rulebook solves this. It is consistent. It is auditable — the landlord can check whether the manager followed Rule 18 last Tuesday night.

In HashiCorp Vault, policies are exactly this rulebook. Vault holds your organisation's secrets — passwords, database credentials, API keys. A Vault policy is a written rule that says who can read which secret, at what time, and under what conditions. Without policies, everyone could read everything. With good policies, access follows the 'least privilege' principle — you give each person or machine exactly the access they need, and nothing more. The 37-page rulebook sounds tedious until a 20-kilogram dog rides the lift to the rooftop garden. Then you are glad the rules exist.

## Core explanation

A Vault policy is a set of rules written in a language called HashiCorp Configuration Language (HCL). HCL looks similar to JSON but is designed to be human-readable. Each policy contains one or more 'path' statements. A path is the location of a secret or resource inside Vault, like a folder on a computer. For example, the path 'secret/data/engineering' points to all secrets stored under the 'engineering' folder in the 'secret' secrets engine.

Every path in a policy has one or more 'capabilities'. Capabilities are the actions you are allowed to take on that path. The four main capabilities you need to know for the exam are: 'create' (write a new secret), 'read' (view an existing secret), 'update' (modify an existing secret), and 'delete' (remove a secret). There is also 'list' (show all secret names in a folder) and 'sudo' (perform privileged operations).

A simple policy looks like this:

path "secret/data/engineering/*" {
  capabilities = ["read", "list"]
}

This policy says: for any secret inside the 'engineering' folder, the user can read the secret value and list the secret names. The asterisk '*' is a wildcard — it matches any number of characters. So 'secret/data/engineering/*' matches 'secret/data/engineering/database-password' and 'secret/data/engineering/API-keys/production'. 

Wildcards are powerful but dangerous. A path like 'secret/data/*' gives access to every secret under the 'secret' mount, which breaks the least privilege principle. Least privilege means you give someone exactly the access they need to do their job, and no more. The VA-003 exam will test your understanding of wildcards and least privilege repeatedly.

Policies are evaluated using 'most specific path wins' logic. If a user has two policies attached to them, Vault looks at both policies and applies the one with the most specific path. For example, Policy A says 'read on secret/data/engineering/*', and Policy B says 'deny on secret/data/engineering/payroll'. The user can read everything in engineering except the payroll folder, because the specific deny cancels the broader allow. Deny capabilities always override allow capabilities when they conflict at the same path specificity.

Policies are attached to users through 'tokens'. Every time a user or machine authenticates to Vault, they receive a token. That token has one or more policies attached. The token is the key that the user presents to Vault with every request. Vault checks the token's policies before deciding whether to grant access.

Best practices for policy authoring include:

- Use the smallest possible scope. Instead of granting 'read' to 'secret/data/*', grant 'read' only to 'secret/data/team-name/*'.
- Prefer explicit deny rules for sensitive paths that should never be accessed by a particular group.
- Group policies by function, not by person. Create a 'developer' policy, not a 'john-smith' policy. This makes audits easier.
- Use 'list-only' policies for directories so users can see what secrets exist without reading the values.
- Never write a policy with 'sudo' unless absolutely necessary. Sudo capability allows bypassing certain access control restrictions.
- Write policies using the principle of default deny — if there is no policy rule that explicitly allows an action, that action is denied.

Vault policies are not the same as file system permissions. In Linux, if you own a file you can usually override its permissions. In Vault, policy rules are absolute — if no policy grants you 'read' on a path, you cannot read it, even if you are root. This makes Vault policies extremely secure but also means you must plan carefully.

For the VA-003 exam, you must memorise the following capabilities and their exact meanings: create, read, update, delete, list, sudo, deny. You must also understand glob patterns (the asterisk and plus sign syntax). The most common exam mistake is confusing 'update' with 'create' — update only works on existing secrets; create only works on new secrets. If a user needs to both create and update, you must list both capabilities.

## Real-world context

An IT professional at a mid-sized e-commerce company, let us call her Priya, is responsible for managing secrets with Vault. The company has three teams: Engineering, Marketing, and DevOps. Each team stores secrets in Vault under their own path. Engineering uses 'secret/data/engineering/', Marketing uses 'secret/data/marketing/', and DevOps uses 'secret/data/devops/'.

Priya's first task on Monday morning is to grant a new engineer, Raj, access to the engineering secrets. She does not give Raj the 'root' policy — that would give him access to everything. Instead, she creates a policy called 'engineering-standard' that grants 'read' and 'list' on 'secret/data/engineering/*' and 'read' on 'secret/data/devops/deployment-keys' because Raj needs to see the deployment keys for his project.

The policy file looks like this:

path "secret/data/engineering/*" {
  capabilities = ["read", "list"]
}

path "secret/data/devops/deployment-keys" {
  capabilities = ["read"]
}

Priya attaches this policy to Raj's token. Now Raj can read his team's secrets and the specific deployment key, but he cannot see Marketing's secrets or write any secrets.

Later that week, a senior engineer, Maria, needs to rotate the production database password. Rotating a password requires the 'update' capability — she needs to change the existing secret's value. Maria also needs to see the old password before changing it, so she needs 'read' too. Priya creates a policy called 'db-admin' with 'read' and 'update' on 'secret/data/engineering/production-database'. She does not give 'delete' because Maria should never delete the production database secret.

Next, the DevOps team asks for a policy that blocks all access to the 'secret/data/devops/deployment-keys' path except for two senior engineers. Priya writes a deny policy specifically for the junior DevOps members:

path "secret/data/devops/deployment-keys" {
  capabilities = ["deny"]
}

She attaches this deny policy to the junior DevOps tokens. The deny overrides any broader allow from other policies, ensuring those junior members cannot read the deployment keys even if another policy might grant them access to the whole DevOps path.

Every quarter, Priya audits all policies. She checks if any policy uses a broad wildcard like 'secret/data/*'. She also checks if any token has more than four policies attached — too many policies can create confusing overlaps and accidental permissions. She uses the Vault command 'vault policy list' to see all policies, then 'vault policy read policy-name' to inspect each one.

Priya also documents every policy in a company wiki. Each policy entry explains what team it is for, what it allows, and who approved it. This documentation becomes critical during a security audit. The auditors ask to see the 'principle of least privilege' in action. Priya shows them the 'engineering-standard' policy and explains that it only grants read access, not write or delete. The auditors are satisfied.

In an emergency, such as a security breach, Priya can revoke all tokens that have a specific policy. She does this with the command 'vault token revoke -mode path secret/data/engineering/*'. This instantly blocks all access to that path for every token in the system, giving the team time to investigate without secrets being leaked.

The key point here is that Priya does not write policies once and forget them. Policy management is an ongoing process. Every new employee, every new project, and every new secret path requires a policy review. The exam tests whether you understand this lifecycle: create, attach, audit, revoke.

## Exam focus

The VA-003 exam tests 'Policy Authoring and Best Practices' under objective 1.4, which accounts for roughly 15-20% of the total exam. Expect 5 to 8 questions on this topic. The questions fall into three categories: policy syntax interpretation, least privilege auditing, and capabilities matching.

For policy syntax interpretation, the exam gives you a small HCL policy snippet and asks which capabilities are granted on a specific path. The trap is the asterisk wildcard. For example, if the policy says 'path "secret/data/*" ' and a question asks what access the user has to 'secret/data/engineering/db-pass', many beginners think the asterisk only matches one level. In fact, a single asterisk matches everything to the right, so a path with one asterisk matches subpaths. You need to understand that 'path "secret/data/engineering/*" ' is more specific than 'path "secret/data/*" '. The exam will test whether you recognise that the more specific path wins.

For least privilege auditing, the exam describes a scenario with a user who has multiple policies. You must identify which access the user is actually granted. The standard pattern: if one policy grants 'read' on 'secret/data/foo/*' and another policy grants 'deny' on 'secret/data/foo/bar', then the user can read everything under 'foo' except 'foo/bar'. The 'deny' capability always wins when paths are equally specific. If a deny is on a more specific path than an allow, the deny overrides the allow on that specific path.

For capabilities matching, the exam lists a set of actions and asks which capability or combination of capabilities is required. For example, 'writing a new secret' requires the 'create' capability, not 'update'. 'Changing an existing secret' requires the 'update' capability, not 'create'. The exam will also ask about 'list' capability — list lets you see the names of secrets in a path, but not their values. If a user only has 'list' on a path, they can see that 'secret/data/engineering' contains 'db-pass' and 'api-key', but they cannot read the actual password or API key.

Key definitions to memorise exactly:

- path: the location of a secret or resource in Vault, written as a string like "secret/data/team-name/*"
- capability: an allowed action on a path, such as 'create', 'read', 'update', 'delete', 'list', 'sudo', 'deny'
- HCL: HashiCorp Configuration Language, used to write policies
- token: a credential that carries one or more policies
- most specific path wins: when two policies conflict, the one with the most precise path is applied
- deny: a capability that blocks all access to a path, even if another policy allows it
- wildcard: the asterisk '*' character that matches any sequence of characters in a path

Exam traps to watch:

- 'Update' requires an existing secret to function. If the secret does not yet exist, 'update' fails, even if the user has 'update' on the path.
- 'Create' only works for new secrets. If you try to 'create' a secret that already exists, it fails. You need 'update' for existing secrets.
- 'Read' and 'list' are different. A user with 'list' can see secret names but not values.
- Deny capabilities do not appear in the 'capabilities = []' block often. If a deny exists in any policy the user has, it applies.
- A path with no matching policy means all actions are denied. There is no 'default allow' in Vault.
- Root tokens bypass all policies. If you see a question about 'root token', the policies are irrelevant.
- The path prefix 'secret/data/' is required for KV v2 secrets engines. The exam often uses KV v2, so remember the '/data/' part.

When writing policies for the exam, always think: what is the minimum set of capabilities needed? If a user needs to view and update an existing secret, the correct answer is ['read', 'update'], not ['read', 'create', 'update']. Add 'list' only if they need to enumerate secret names. Add 'deny' only when you need to block access on a specific subpath.

The exam may also present a 'best practice' question asking which approach is correct. The correct answer is almost always: use the least privilege principle, avoid broad wildcards, and use deny only for exceptions. Multiple-choice trap answers include: 'use a single broad policy for all users', 'give everyone the root token', or 'attach policies to individuals instead of roles'. None of those follow best practices.

## Step by step

1. **Define the path** — Identify the exact location of the secrets you want to control. The path must start with the secrets engine mount point, usually 'secret/data/' for KV v2. For example, 'secret/data/engineering/production/database-password'. Write the path as a string inside double quotes. Use wildcards (asterisks) only when necessary to cover multiple subpaths.
2. **Choose the capabilities** — Decide which actions the user should be allowed to perform on that path. List exactly the capabilities needed: create for new secrets, update for existing secrets, read for viewing values, delete for removing secrets, list for enumerating secret names. Do not add capabilities 'just in case'. Follow least privilege — the fewer capabilities, the safer.
3. **Write the policy block** — Write the HCL block with the path and capabilities inside curly braces. Syntax: path "your-path" { capabilities = ["cap1", "cap2"] }. Each capability is a lowercase string inside double quotes. Separate multiple capabilities with commas. The block must be properly indented for readability.
4. **Add deny rules for exceptions** — If you have a broad allow rule (like 'read on secret/data/engineering/*') but need to block access to a specific subpath (like payroll), write a separate policy block with the deny capability on that specific subpath. Deny blocks should use the most specific path possible to avoid accidentally blocking more than intended.
5. **Save and attach the policy** — Save the policy as a file in Vault using the command 'vault policy write policy-name policy-file.hcl'. The policy name should reflect its purpose, like 'engineering-readonly' or 'db-admin'. Then create a token or role that uses this policy. Verify the policy works by logging in as that user and trying to access a secret.
6. **Audit the policy regularly** — Review the policy every quarter or whenever a team member changes roles. Use 'vault policy list' to see all policies and 'vault policy read policy-name' to inspect their contents. Look for overly broad wildcards, unused capabilities, or policies that are no longer needed. Remove obsolete policies to keep the environment tidy.

## Comparisons

### Create capability vs Update capability

**Create capability:**
- Used to write a brand new secret that does not exist yet
- Fails if the secret already exists at the specified path
- Commonly used for onboarding new applications or services

**Update capability:**
- Used to modify an existing secret at a known path
- Fails if the secret does not already exist
- Commonly used for rotating passwords or regenerating API keys

### List capability vs Read capability

**List capability:**
- Returns the names of secrets in a directory-like path
- Does not reveal the actual secret values
- Useful for discovery and inventory purposes

**Read capability:**
- Returns the full contents of a specific secret, including its value
- Requires the exact path of the secret to be specified
- Grants access to sensitive data like passwords and certificates

### Allow rule vs Deny rule

**Allow rule:**
- Explicitly permits certain capabilities on a path
- Can be overridden by a more specific deny rule
- Is the standard way to grant access to users

**Deny rule:**
- Explicitly blocks all capabilities on a path
- Always wins when path specificity is equal or higher than the allow rule
- Used sparingly for exceptions to broad allow rules

### Role-based policies vs Individual-user policies

**Role-based policies:**
- Attached to a role that many users share
- Easier to audit and maintain at scale
- Follows best practice for large teams

**Individual-user policies:**
- Crafted specifically for one user's access needs
- Harder to audit and track as the number of users grows
- Leads to policy sprawl and inconsistency

## Diagram

_Flowchart showing how Vault evaluates policies using most-specific-path-wins logic and deny overrides._

```mermaid
graph TD
    A[User logs in to Vault] --> B[Token created with policies attached]
    B --> C[User makes request to read secret at path 'secret/data/engineering/*']
    C --> D{Vault checks policies on token}
    D --> E[Policy A allows 'read' on 'secret/data/engineering/*']
    E --> F[Policy B allows 'deny' on 'secret/data/engineering/payroll']
    F --> G{Which path is more specific?}
    G --> H[Path 'secret/data/engineering/payroll' is more specific]
    H --> I[Deny overrides allow. Access blocked to payroll.]
    D --> J[No matching policy path for other requests]
    J --> K[Default deny: access blocked.]
```

## Common misconceptions

- **Misconception:** If you have any policy that grants access to a path, you can read all secrets under that path. **Reality:** Policies are additive — you only get the capabilities explicitly listed on the paths you match. If a policy grants 'list' on a path, you can list secret names but not read their values. If the policy does not include 'read', you cannot read any secret content under that path. (Beginners assume 'access to a path' means full read/write access, like a folder in a file system. But Vault capabilities are granular — each action is a separate capability.)
- **Misconception:** Writing 'capabilities = ['create', 'update']' on a path means the user can both create new secrets and update existing ones, which is always correct. **Reality:** This is correct syntactically, but best practice suggests you should only give the minimum capabilities needed. If a user only needs to update existing secrets, give only 'update'. Adding 'create' opens the door to creating new secrets that might not follow naming conventions or security rules. (Beginners think 'more is safer' but in security, more capabilities mean more risk. The principle of least privilege says give exactly what is needed, nothing more.)
- **Misconception:** If a deny policy and an allow policy both match the same path, Vault blocks everything because deny overrides everything. **Reality:** Deny only overrides allow when the deny is on the same or a more specific path. If the allow is on a more specific path than the deny, the allow applies. For example, allow on 'secret/data/foo/bar' and deny on 'secret/data/foo/*' gives access to 'foo/bar' because the allow path is more specific. (The 'most specific path wins' rule confuses people. They think deny is absolute, but path specificity is checked first. Deny only wins when paths are equally specific or when the deny path is more specific.)
- **Misconception:** You can use the asterisk wildcard like a Linux shell glob, where 'secret/data/*' only matches one level deep. **Reality:** In Vault policy paths, a single asterisk matches everything to the end of the path, including subdirectories. So 'secret/data/*' matches 'secret/data/engineering', 'secret/data/engineering/db', and 'secret/data/engineering/db/password'. To match one level only, you use the 'in-path' or '+ ' syntax, but the exam rarely tests that. (Most people come from a Linux background where 'ls /secret/data/*' only shows direct children. Vault's glob is more permissive, so beginners underestimate the scope of a single asterisk.)
- **Misconception:** The 'list' capability is just a nice extra — it does not affect security because you cannot read secret values with it. **Reality:** List capability is a security risk because it reveals secret names. An attacker who can list paths learns what secrets exist and where they are stored. This information helps them target their attacks. Best practice is to give 'list' only to users who need to discover which secrets exist. (Beginners overlook that even knowing a secret exists is valuable information for an attacker. They focus only on preventing value reads, not on hiding the structure.)
- **Misconception:** You should write one policy per user to give them exactly what they need. **Reality:** Best practice is to write policies per role or team, not per user. For example, write a 'developer' policy and attach it to all developers. This is easier to audit, reduces policy count, and ensures consistency. Individual exceptions can use deny policies. (New IT professionals treat policies like individual permissions, similar to setting up accounts on a website. But Vault is designed for many users, and per-user policies become unmanageable quickly.)

## Key takeaways

- A Vault policy with no matching path grants zero access — default deny is always in effect.
- The asterisk wildcard in a policy path matches everything after it, including all subdirectories.
- Deny capability always overrides allow when the deny path is equal to or more specific than the allow path.
- Create creates new secrets only; update modifies existing secrets only — they are not interchangeable.
- List shows secret names but not secret values; read shows secret values but not necessarily all names.
- Attach policies to roles or teams, not to individuals, to simplify audits and reduce policy sprawl.
- The most specific path wins when two policies conflict on the same capability.
- Root tokens bypass all policies — if a user has a root token, policy rules do not apply to them.

## FAQ

**What is the difference between 'create' and 'update' in a Vault policy?**

Create allows you to write a new secret that does not already exist at that path. Update allows you to modify an existing secret. If a secret already exists and you try to use 'create', it fails. If you try to 'update' a secret that does not exist, it also fails. You need both if the user must handle new and existing secrets.

**Can a user with no policies attached do anything in Vault?**

No. If a user has no policies attached to their token, every action is denied by default. Vault uses a default-deny model — if no policy explicitly allows an operation, the operation is blocked. This is true even for users with valid tokens.

**How do wildcards work in Vault policy paths?**

A single asterisk '*' matches any sequence of characters, including nested subpaths. For example, 'secret/data/*' matches 'secret/data/foo', 'secret/data/foo/bar', and 'secret/data/foo/bar/baz'. There is no way to limit an asterisk to only one directory level using standard HCL — you would need to write separate paths for each level.

**What happens if two policies conflict on the same path?**

Vault uses 'most specific path wins' logic. If both policies have the same path specificity, deny overrides allow. If one policy has a more specific path (e.g., 'secret/data/foo/bar' is more specific than 'secret/data/foo/*'), the more specific policy's capabilities are applied for that path.

**Can I write a policy that only lets someone see secret names but not their values?**

Yes, use the 'list' capability. A policy with 'list' on a path allows the user to call the list operation, which returns secret names. Without the 'read' capability, they cannot view the actual values of those secrets.

**Do policies apply to root tokens?**

No. Root tokens bypass all policies. A root token can perform any operation on any path, regardless of what policies say. This is why root tokens should be used only for initial setup and emergency break-glass scenarios.

**What is the 'deny' capability and when should I use it?**

The 'deny' capability explicitly blocks all access to a path, even if another policy grants access. Use deny for exceptions — for example, if a team policy grants broad access to a folder but you need to block one specific subfolder from that team. Do not use deny as your primary access control method.

## Check your understanding

1. **What capabilities would you grant to a user who needs to view an existing secret and then change its value?**

   Answer: Grant 'read' and 'update' capabilities on the exact path of that secret. You do not need 'create' because the secret already exists.

2. **If a user has a policy that grants 'read' on 'secret/data/engineering/*' and another policy that grants 'deny' on 'secret/data/engineering/payroll', can the user read the secret at 'secret/data/engineering/payroll/salaries'?**

   Answer: No. The deny on 'secret/data/engineering/payroll' is more specific than the allow on 'secret/data/engineering/*', so the deny overrides. The user cannot read any secret under the 'payroll' path.

3. **True or false: a user with only the 'list' capability on a path can see the values of secrets stored there.**

   Answer: False. List only returns secret names, not their values. To see values, the user also needs the 'read' capability.

4. **What happens if a user makes a request to a path that is not mentioned in any of their attached policies?**

   Answer: The request is denied. Vault uses a default-deny model — if no policy explicitly allows an action, it is blocked.

5. **Which capability must you add to a policy if a user needs to create a brand new secret that does not yet exist?**

   Answer: You must add the 'create' capability. 'Update' only works on existing secrets and will fail for new ones.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/hashicorp-vault/vault-policy-best-practices
