How to use TokenRequest API and TokenVolume Projection in Kubernetes?
Part 1: Kubernetes Core Concepts: Service Account
Service Accounts in Kubernetes may have either expirable or non-expirable tokens.
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:
- 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
- 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.