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.
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.
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 grantread
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
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?
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.
Let’s skim through the default policy and check for the path. We are looking for the sys/mounts
path and get
permission.
path "sys/mounts" {
capabilities=["read"]
}
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.
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.
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
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.
Now let’s try to list policies.
vault policy list
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.
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.
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"]
}
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.
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**
Even the UI immediately shows the change.
Now we can modify our policy as below to only allow access to t1 apps db secrets.
path "t1/+/data/db*" {
capabilities = ["read"]
}
The other places we shouldn’t be reaching;
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
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"]
}
Still can’t access app secrets though, which is correct.
Thanks for reading and hope to see you again on another fun journey.