Vault Part 6 - Vault Authorization: ACL(Access Control List) Policies

This is a quick and dirty (read practical for business jargon) intro to Vault Policies which determine the level of access a user will have within Vault.

vault policy defines what we can do, image source here

By default, upon creation, Vault creates a root policy which is unique in the sense that it can do everything in Vault. This allowed us to create initial setup, auth methods, secrets and now policies.

Additionally, Vault creates a default policy so that new tokens can just renew, revoke and lookup their own stuff and is attached to all tokens to provide a common ground for permissions.

Yay…more hcl

Each token created is created as a child of the currently authenticated token.

Remember that all operations we do with Vault is path based. Some examples are below;

vault kv put secret/super-secret-path cli-secret=wowomgsecrecy
vault lease revoke postgres14/creds/read-only/q2hiKZVSQgp24mBS7nImsCMw
vault write auth/approle/login role_id=1..23 secret_id=12..23

So if we were to add permissions to these, we would add policies. Such as adding below capabilities to everything under auth path

path auth/*
capabilities=["create", "read", "update", "delete", "list", "sudo"]
#there is also deny to block access to path. full list here

The wall of text that is the default policy probably makes more sense now.

There is also some other important bit that we need to see in the above link.

Note that capabilities usually map to the HTTP verb, not the underlying action taken. This can be a common source of confusion. Generating database credentials creates database credentials, but the HTTP request is a GET which corresponds to a read capability. Thus, to grant access to generate database credentials, the policy would grant read access on the appropriate path.

Anyway, without digressing, let’s see it in an example.

Create a user in the user-pass engine and login as below. Notice the policy is default even though we didn’t select anything. Note that behavior between cli and UI differentiates on some issues so always double check the policy.

vault login -method=userpass username=testuser
do lol at my security attempts on local vault test user

One error you might come across is below.


Basically we were authenticated but token was not stored in a temp place. Vault stores your token on your filesystem at ~/.vault-token for linux or above path for windows. Why?

thanks vault

Because of the highlighted bit. You can choose to not store your token locally with the -no-store flag.

vault login -no-store -method=userpass username=testuser

Let’s try out the secret engines list command, vault secrets list and see how our policy fairs.

of course

Let’s skim through the default policy and check for the path. We are looking for the sys/mountspath and get permission.

path "sys/mounts" {
a read perm should suffice

Now, go to the user-pass auth methods and find your user. Under the tokens collapsable area, there is a bit as below. Make that our new policy called poly.

assign “poly” policy to the user.

We need to login again so our new policy attachment takes effect.

Note: You need to login again if a policy is removed or a different policy is attached.

“poly” is attached

See that poly is now visible within our policies when we login. Now trying our token like below we can see current secrets in our Vault instance.

VAULT_TOKEN=s...Q vault secrets list
enabled secrets engines

You might be wondering why we had to use VAULT_TOKEN smack in the beginning. Well, if you have a token stuffed in your env as below, Vault will always use that unless you specify otherwise. You can also just delete that env var to save trouble. Don’t forget to restart any terminals open after it.

env variables

Now let’s try to list policies.

vault policy list
I would have it no other way

to fix this, going back to our poly policy, we need to give both path read and list permissions. We are giving list perm as well since the above url is trying to use list=true as a query param.

path "sys/policies/acl" {
capabilities = ["read","list"]

Running the same command will now get you your list.

available policies

This applies (and is more often used) for our secrets as well.

I created two kv secrets engine to test this out. The original kv engine (secret/) has two secrets, db_user and db_pass while the new kv engine (supersecret/) has only one called api_key. Tried accessing all of them, along with a fake path but since we have no perms, it failed.

as expected

After we add the following policy additions, we can access kv secrets under secret/ and supersecret/ root paths.

path "secret/*" {
capabilities = ["read"]
path "supersecret/*" {
capabilities = ["read"]
the secrets are ours

Let’s make this a little interesting. Suppose that we want to assign a policy to a new dbadmin and we don’t have groups yet so we want to assign directly. DB admin needs access to secrets called db_user and db pass which are in secret/ kv engine but I’ve also added them to supersecret/ as well. Think of these engines more like a project name. Additionally, secret engine now contains app_user and app_pass secrets as well, which the dbadmin should not see.

Let’s go ahead and see that we can’t access these secrets first.


We can, because our policy, poly, contains read access to all secrets under secret and supersecret root paths. We should create a new policy called dbadmin with below rule.

path "+/data/db*" {
capabilities = ["read"]

What this means is read access on any secret engine in Vault to all secrets ending with db*, where * stands for any character. A quick note here, the * wildcard works only for ends_with checks. So, no *db* or *secret/ yet.

assigned the new dbadmin policy

Let’s test this by attaching the policy to our testuser first. We should login again, see our new policy attached and test out the secret access.


But what about app_user and app_pass? We shouldn’t be able to access them.


You could also have several tiers of applications with varying levels of security clearance needed to allow access to the secrets. Our new db admin has access to all db creds for all projects now which is a little iffy. Also our projects(secrets engines) are named randomly so we can’t do much with the wildcard. However, we could move our secrets engines to another path. Let’s move secret and supersecret to t1/secret and t2/supersecret respectively with the vault move command.

vault secrets move secret/ t1/secret/
vault secrets move supersecret/ t2/supersecret/

To do this we need access to the sys/remount endpoint but let’s not go there and login with root and do our thing.

**Don’t mind the below screenshot. I moved the paths to wrong places and now moving them back**

moving secrets

Even the UI immediately shows the change.

update to see changes

Now we can modify our policy as below to only allow access to t1 apps db secrets.

path "t1/+/data/db*" {
capabilities = ["read"]
secrets available

The other places we shouldn’t be reaching;

denied secrets

We could also do some fine tuning where needed like deny access to some project db creds in a t1 app for example as follows;

path "t1/someproject/data/db*" {
capabilities = ["deny"]
orpath "t1/someproject2/data/db_user" {
capabilities = ["deny"]
or allow just the db_user from a t2 apppath "t2/supersecret/data/db_user" {
capabilities = ["read"]

I just added the last rule and I can access that specific secret. This works even if you have a deny like this:

path "t2/+/data/db*" {
capabilities = ["deny"]

Note: Some endpoints (paths) require root or sudo access additionally. Complete list is linked.

There are quite a lot of permissions and it would be difficult to list everything. However, moving case by case is possible since Vault errors are explanatory in most cases. For example, to list secrets in our t1/secret kv engine;

vault kv list t1/secret
need list access

We get a perm error since we don’t have list access to that endpoint. Below policy addition will fix that error for us.

path "t1/secret/metadata" {
capabilities = ["list"]
works now

Still can’t access app secrets though, which is correct.

no secrets for you (token)

Thanks for reading and hope to see you again on another fun journey.




Let’s talk devops, automation and architectures, everyday, all day long.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

What Is Daily Scrum?

Cross Compiling Node.js for ARM on Ubuntu

Faster Averages in the Real World

Hot Disaster Recovery for Applications on Google Cloud

Breaking up with a Monolith

New CNCF Sandbox Projects

Lower Cost with Higher Stability: How Do We Manage Test Environments at Alibaba?

Laravel Dusk: Tinker

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Yiğit İrez

Yiğit İrez

Let’s talk devops, automation and architectures, everyday, all day long.

More from Medium

Vault Part 4 - Transferring Authentication Burden to Vault

OPA as Kubernetes admission controller

Secure Docker-in-Kubernetes

Deploying Kubernetes (K3S) to an ARM based VM on Oracle with ArgoCD, Cert Manager, Gitlabs CI and…