Vault Part 9 - Deeper Look Into Tokens

Yiğit İrez
10 min readFeb 16, 2022

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.
infinite joy. Image source here

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.

from a previous login using an approle

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.

the usual site

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"]
}
using an authorized user to check

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.

oh, MOUNT ACCESSOR is here as well, good to know

Those zeros mean default and the actual values of those defaults can be reached via the below command;

vault read sys/auth/token/tune
ok then

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.

noice

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.

our token

Now, let’s renew;

vault token renew -increment 2m OUR_TOKEN
great

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
increment=ttl from command execution

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.

all good tokens end eventually

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" ]
}
child tokens and their fates

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
what?

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.

“shocked pikachu face”

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
nice

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.

orphan means parent token

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.

not everyone can make a parent token

Doing one without -orphan

vault token create
this one is bound to the parent

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
nice

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.

source: https://www.vaultproject.io/docs/concepts/tokens

“Surprisingly”, I found no data on exactly how “heavyweight” a single service token is.

how heavyweight exactly?

Also, was difficult to find what the default storage for dev mode Vault is as well. It’s right there in the vault initialization.

simple once you know

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;

8KB for 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.

my directory structure is just like my desk

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
yay

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)
2.2KBs, ok.

Let’s create a batch token as before and rerun to see how much space it consumes.

well

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 bit is just the wrapping part of a looong token

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/*
our capabilities

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/*
capabilities all around

That’s all for this story. Thanks for reading and see you next time.

--

--

Yiğit İrez

Let’s talk devops, automation and architectures, everyday, all day long. https://www.linkedin.com/in/yigitirez/