Vault Part 9 - Deeper Look Into Tokens
--
Goal: This post aims to provide a deeper look into Vault Tokens as part of a series of posts starting from here.
What we need
- Running Vault, preferably in dev mode. See Part 1 for installation if it doesn’t exist.
Tokens
We have been throwing around tokens left and right but let’s look at tokens in a bit more depth now.
As you may have noticed by now that we do everything using the login tokens we get after we login with a variety of Vault authentication methods. From the start using our root token to login to the fancy other methods we use, we always get a token after a login. Since Vault bases every operation on token authentication in the end, it is the only auth method that cannot be disabled.
We also briefly talked about the fact that what a token can do, is decided with the policies it has attached to it. In the above case, default
and testapp-pol2
policies.
If you had a chance to follow through on our examples with policies, we used to modify and add policies to a user/pass account while trying them out.
While changing policy contents required no relog, adding a policy did, because the policies attached to our token after login are final until the token expires and addition (or removal) of policies will be applied after a new login by the user who will get a new token.
You cannot modify an active token’s permissions.
Now that we got that out of the way, we can also observe more details about our(or someone else’s) token via the token lookup command
vault token lookup SOME_TOKEN
This is also a permission based operation so you will get the below with an unauthorized user.
If you prefer, you can allow users to lookup only their own tokens with the below policy;
path "auth/token/lookup-self" {
capabilities = ["read"]
}
or just give admins permissions to lookup endpoint.
path "auth/token/lookup" {
capabilities = ["read"]
}
While all fields listed are self explanatory, one thing to note is everything we are doing and talking about applies for token types of service. There are also batch tokens which are lightweight but don’t have many capabilities of service tokens. I’ll not go into details of this right now feel free to dive in if you prefer from here.
You may have also noticed that we use VAULT_TOKEN=…
before a command. We briefly talked about this in another post but basically vault uses the VAULT_TOKEN
env var if it exists or ~/.vault-token
file created after a login for ease of use. Since I have the env var setup (also using multiple terminals with different accounts) I have to manually prefix the token to my commands.
Another thing to know is the TTL(Time to Live) for tokens is by default 768h since we didn’t change anything in our token auth configs.
Those zeros mean default and the actual values of those defaults can be reached via the below command;
vault read sys/auth/token/tune
This applies to all authentication methods too so let’s change some ttl defaults in a userpass auth and then retry the vault read for userpass
instead of token
.
While we are here we may as well talk about the max_lease_ttl
too. The value in this field is the maximum time our token can be renewed for. So, in the above case we have a lease ttl of 2 mins after which our token will expire, unless we renew the token. However, we cannot renew our token to live for longer than 5 minutes. Let’s do a quick try on a userpass token.
Now, let’s renew;
vault token renew -increment 2m OUR_TOKEN
We can’t because we need the following policy similar to lookup. Note the fun thing that we are working directly on the token now, so we need the policy to point to token
endpoint not userpass
.
path "auth/token/renew-self" {
capabilities = [ "update" ]
}
Trying the renew command for 1m
vault token renew -increment=2m
So since our default ttl is also 2m, it’s not immediately clear what is happening but what actually happened was our ttl was replaced by the value we sent in the -increment
field. If we were to send 4m, our token would live for 4minutes but you can’t make it live forever since a ticking time bomb of 5 minutes is set as we logged in the first time.
We haven’t explicitly created many tokens but I want to show you something. Add below to a policy attached to any user. I used userpass-passchange
policy attached to testuser2
.
path "auth/token/create" {
capabilities = [ "create", "update" ]
}
In the above image we created a token from testuser2
with vault token create, logged in with our new token somewhere else. Our new token had huge ttl due to token authentication having default setup and all the capabilities of testuser2
like changing their own password, creating other tokens. Better than the parent token right? We then proceeded to set 1s to the TTL of our parent token at the left which then proceeded to expire immediately. What happened was the new token also expired. Why? Well because all tokens generated are created from a parent token and unless explicitly specified, will have all capabilities of their parent but are attached to the expiry of their parent. That means if a root token were to somehow expire, every token generated from that will also be gone regardless of the ttl left.
But what about constantly running applications that will be needing token renewals? Will they need to check token duration and renew after it falls to a certain time and keep doing that until hitting max ttl
and then create a new token? Well, they should in my opinion but Vault created a QoL thing called periodic tokens just for this case.
vault token create -period=30m
Leaving out policy to test it out and apparently you can create periodic tokens of the root token as well. Moving on, let’s change the token ttls a bit to see the effects of what we are doing.
vault write sys/auth/token/tune default_lease_ttl=20m max_lease_ttl=1h
We should then create two tokens, one periodic with 30m, the other just a regular token. Now do a renew on both of them. In the below image, we have our periodic token and just under that our everyday, regular, normal token. Trying to renew them both by 122312hours makes our regular be capped at the token auth max ttl of 1h where as our periodic does not care. It can only be renewed as much as the period every time though and the duration will be capped with whatever TTL settings you have either on the system or the token authentication.
Moving on with tokens, you will most likely be wondering (or googled it yourself out of frustration) what an accessor associated with each token was. It’s basically an ID of the token that can be used to renew/revoke a token and lookup token properties along with capabilities on a path. In short, admins or a provider application need not know your token to operate on it. Thinking back now, I’ve not noticed the security implications. It would be like the bank asking for credit card number of a customer to expire it.
Trying the accessor I got from a previous login;
vault token lookup -accessor zkMcKOVaF5El4HfQJxcnnY9w
There is also the concept of tokens which are a parent token themselves so they have no parent (called orphan in Vault terms) and hence, no parent expiry to nuke them. They are the root of their token tree much like the actual root token (with less permissions of course). Looking back at a lookup we did with an accessor just a moment ago, we had the orphan=true
there. That means that login testuser2
did, created a token that was the root of it’s line. If we were to create tokens while logged in as this user, they will be created taking this token as their parent. For those tokens, orphan
field will be false
.
BUT, if you wanted to create an orphan token anyways from the testuser2 login token to inherit policies, etc. you just need to add -orphan
to the command line. I will use root for the below commands but for a token to be able to use -orphan
they will need sudo
policy perm on auth/token/create.
Doing one without -orphan
vault token create
Using -orphan
. Also notice the use of -policy
to hand out a set of policies instead of just letting it inherit root and giving it nuclear superpowers.
vault token create -orphan -policy=default
Batch Tokens
Previously we mentioned batch tokens as a lightweight token that can do somethings but are not as well equipped as a service token. Why use them at all then? Because you have many tokens running around and storage is not infinite. Batch tokens to the rescue by not requiring storage space.
“Surprisingly”, I found no data on exactly how “heavyweight” a single service token is.
Also, was difficult to find what the default storage for dev mode Vault is as well. It’s right there in the vault initialization.
I cannot bear to lose all of my testing work just yet so watching how the memory of Vault changes per token creation after a single token;
Seems manageable but let’s see how much space batch is going to take. In the below command I had to use -policy
since batch tokens cannot be root and why root you might ask? Because I closed my running Vault instance by mistake (queue tears) and restarted.
vault token create -type=batch -policy=default
So after trying that out, there was no meaningful data since Vault memory consumption changed constantly for some reason. Instead, let’s get Vault up with some storage instead of inmem
as it does by default.
Create a config.hcl
file anywhere. I just bunched mine with the vault executable even though it’s in PATH.
The contents of the config.hcl
are below. I gave an absolute path but relative works fine too. What we did was just select a storage type of file and where the dir is, also you need to create it in advance.
storage "file" {
path = "E:/tools/vault/data"
}
Our command to run Vault dev with our config. Again, path used below is absolute but there is no need.
vault server -dev -config=E:/tools/vault/config.hcl
Trying out with the mighty PowerShell, we can finally see the size difference a single token makes on the whole data folder.
"{0} KB" -f ((Get-ChildItem E:\tools\vault\data\ -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1KB)
Let’s create a batch token as before and rerun to see how much space it consumes.
So with that, we have a batch token (a rather long one for some reason), with no accessor interestingly, that our app (or us) can use.
Token Capabilities
We have so far covered secrets engine creation and policy attachment to users. Using our login token we can check the permission status of our token towards a specific endpoint (capabilities the token has).
Still making use of the previous approle token and related policy to check our perm status on the t1 secrets;
VAULT_TOKEN=s...p vault token capabilities t1/secret/data/*
Above you can see that we can access the secrets of t1 just fine but other places, even other endpoints t1 besides the data has the perm status of deny. That’s due to our policy though. We have access to all endpoints of pattern t1/ANYTHING/data/ANYTHING but nothing else.
path "t1/+/data/*"{
capabilities = ["create","update","delete","read","list"]
}
We can also check for the capabilities of other tokens like below. Tried for the previous user and the root.
vault token capabilities s...p t1/secret/data/*
That’s all for this story. Thanks for reading and see you next time.