How to use TokenRequest API and TokenVolume Projection in Kubernetes?

adil
6 min readOct 14, 2023

Part 1: Kubernetes Core Concepts: Service Account

Service Accounts in Kubernetes may have either expirable or non-expirable tokens.

Photo by Nathan Dumlao on Unsplash

In Part 1, we examined tokens with no expiration date. Since they have no expiration date, Kubernetes advises against using them.

How do you create an expirable token?

There are two recommended methods for obtaining an expirable token:

  1. Projected volume: Kubernetes will generate a token for you and mount it in your container as a file. When the token expires, Kubernetes will refresh the token and update the file with the new token
  2. TokenRequest API: It’s a REST API. You make a request to the Kubernetes API and receive a token in response. If the token expires, you may need to make another HTTP request for a new token.

Let’s create a service account and bind a role to the account

00-sa.yaml

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: service-account-blog
namespace: default
automountServiceAccountToken: false

When we created a service account in Part 1, this account was automatically mounted to the pod. Set the automountServiceAccountToken key to false if you do not want tokens automatically mounted.

Apply:

➜  ~ kubectl apply -f 00-sa.yaml
serviceaccount/service-account-blog created

01-role.yaml

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
namespace: default
name: cluster-role-blog
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]

Apply:

➜  ~ kubectl apply -f 01-role.yaml
clusterrole.rbac.authorization.k8s.io/cluster-role-blog created

02-cluster-role-binding.yaml

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-role-binding-blog
namespace: default
subjects:
- kind: ServiceAccount
name: service-account-blog
namespace: default
roleRef:
kind: ClusterRole
name: cluster-role-blog
apiGroup: rbac.authorization.k8s.io

Apply:

➜  ~ kubectl apply -f 02-cluster-role-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io/cluster-role-binding-blog created

Using Service Accounts with the Projected Volumes

03-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
serviceAccountName: service-account-blog
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /tmp/secrets
name: projected-volume-for-service-account-blog
volumes:
- name: projected-volume-for-service-account-blog
projected:
sources:
- serviceAccountToken:
path: service-account-token-as-file
expirationSeconds: 600

Apply:

➜  ~ kubectl apply -f 03-pod.yaml
pod/nginx created

In 03-pod.yaml, we attach the Service Account (service-account-blog) to the nginx pod as a Projected Volume. We set an expiration time for the Service Account in the volumes section (600 seconds, 10 minutes).

The service-account-blog token will expire in 600 seconds and be automatically renewed by Kubernetes.

Let’s test it:

At 16:38, I created the pod and sent the following request to the Kubernetes API:

I used the following commands:

token=`cat /tmp/secrets/service-account-token-as-file`
curl -k --header "Authorization: Bearer ${token}" https://kubernetes.default.svc/api/v1/namespaces/default/pods

At 16:49 I sent another request:

I’m getting an “Unauthorized” error because the token has expired.

I must read the token from the filesystem again and send another request.

So, the token expires every 600 seconds, and Kubernetes automatically renews it and places the new token into the Projected volume.

Using Service Accounts with the TokenRequest API

Before commencing this section, I deleted the resources I created for Projected Volumes.

➜  ~ kubectl delete -f 00-sa.yaml
serviceaccount "service-account-blog" deleted
➜ ~ kubectl delete -f 01-role.yaml
clusterrole.rbac.authorization.k8s.io "cluster-role-blog" deleted
➜ ~ kubectl delete -f 02-cluster-role-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io "cluster-role-binding-blog" deleted
➜ ~ kubectl delete -f 03-pod.yaml
pod "nginx" deleted

I will create two new service accounts:

service-account-token-requester

service-account-pod-viewer

04-service-account.yaml

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: service-account-token-requester
namespace: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: service-account-pod-viewer
namespace: default
automountServiceAccountToken: false

Attention: The service-account-token-requester does not have the automountServiceAccountToken attribute.

Because we want this service account to be automatically mounted to the container. The default value of the automountServiceAccountToken attribute is True.

Therefore, there is no need to add the attribute for service-account-token-requester

Apply:

➜  ~ kubectl apply -f 04-service-account.yaml
serviceaccount/service-account-token-requester created
serviceaccount/service-account-pod-viewer created

05-role.yaml

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
namespace: default
name: cluster-role-token-requester-blog
rules:
- apiGroups: [""]
resources: ["serviceaccounts/token"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
namespace: default
name: cluster-role-pod-viewer-blog
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]

Apply:

➜  ~ kubectl apply -f 05-role.yaml
clusterrole.rbac.authorization.k8s.io/cluster-role-token-requester-blog created
clusterrole.rbac.authorization.k8s.io/cluster-role-pod-viewer-blog created

06-cluster-role-binding.yaml

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-role-binding-token-requester-blog
namespace: default
subjects:
- kind: ServiceAccount
name: service-account-token-requester
namespace: default
roleRef:
kind: ClusterRole
name: cluster-role-token-requester-blog
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-role-binding-pod-viewer-blog
namespace: default
subjects:
- kind: ServiceAccount
name: service-account-pod-viewer
namespace: default
roleRef:
kind: ClusterRole
name: cluster-role-pod-viewer-blog
apiGroup: rbac.authorization.k8s.io

Apply:

➜  ~ kubectl apply -f 06-cluster-role-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io/cluster-role-binding-token-requester-blog created
clusterrolebinding.rbac.authorization.k8s.io/cluster-role-binding-pod-viewer-blog created

The resources I created:

The pod looks like this:

The service-account-token-requester will be utilized to request a token for the service-account-pod-viewer

I used the following commands:

token_requester_token=`cat /var/run/secrets/kubernetes.io/serviceaccount/token`
pod_viewer_account="service-account-pod-viewer"
curl -k -X POST https://kubernetes.default.svc/api/v1/namespaces/default/serviceaccounts/${pod_viewer_account}/token --header "Authorization: Bearer ${token_requester_token}" -d '{"spec": {"expirationSeconds": 600}}' -H 'Content-Type: application/json; charset=utf-8'
pod_viewer_token='blabla'
curl -k --header "Authorization: Bearer ${pod_viewer_token}" https://kubernetes.default.svc/api/v1/namespaces/default/pods

As you may have noticed in the screenshot, I read the token (generated by Kubernetes) from the file. I requested a token for service-account-pod-viewer using the service-account-token-requester’s token.

I copied the generated token from the output and pasted it into another variable. After that, I was able to list the pods via the Kubernetes API.

Let’s try to list the pods via the Token Requester’s token:

I used the following commands:

token_requester_token=`cat /var/run/secrets/kubernetes.io/serviceaccount/token`
curl -k --header "Authorization: Bearer ${token_requester_token}" https://kubernetes.default.svc/api/v1/namespaces/default/pods

It failed because in 05-role.yaml, we specified that cluster-role-token-requester-blog can only generate a token for Service Accounts. It can’t view the pods.

P.S.: As you may have noticed, all the tokens we create expire within 600 seconds. Because it is the minimum value for tokens.

--

--