ServiceAccount provides an identity for processes running in a Pod, and processes within the Pod can use the identity of their associated service account to authenticate to the APIServer in the cluster.

When the Pod is created, there is a spec.serviceAccount property under the specification that specifies which ServiceAccount the Pod uses, or the default sa if it is not specified, and then by projecting the volume, there is a token token file under the Pod’s directory /run/secrets/ kubernetes.io/serviceaccount/, there is a token token file under the Pod’s directory /run/secrets/. If we grant any privileges to the sa via RBAC, then the application in the container will have the corresponding privileges when it takes the token.

However, it is important to note that different versions of K8s use the token file differently, so we will briefly explain each of them here.

<= version 1.20

Use kind to quickly create a cluster with a version less than or equal to v1.20.

1
2
3
4
☸ ➜ kind create cluster --name kind120 --image kindest/node:v1.20.15
☸ ➜ kubectl get nodes
NAME                    STATUS   ROLES                  AGE   VERSION
kind120-control-plane   Ready    control-plane,master   33s   v1.20.15

We first create a ServiceAccount object with the word sa-demo.

1
2
3
4
5
6
7
8
9
☸ ➜ kubectl create sa sa-demo
☸ ➜ kubectl get sa
NAME      SECRETS   AGE
default   1         43s
sa-demo   1         6s
☸ ➜ kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-dv78w   kubernetes.io/service-account-token   3      46s
sa-demo-token-4gvbw   kubernetes.io/service-account-token   3      8s

We can see that after creating a sa, a secret is automatically generated in the format <saname>-token-xxxx, for example, if we create a sa with the name sa-demo, a secret with the name sa-demo-token-4gvbw is automatically created. This secret contains a token.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
☸ ➜ kubectl describe secrets sa-demo-token-4gvbw
Name:         sa-demo-token-4gvbw
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: sa-demo
              kubernetes.io/service-account.uid: 1ae8eea9-acc6-4e3d-b378-07feb9146ac4

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1066 bytes
namespace:  7 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6ImhQNmFMNjAyaDZ5OElyMmtTNGdPUWxRdHVDU1A4aGFfVkJiNHdHMkZjQlUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNhLWRlbW8tdG9rZW4tNGd2YnciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2EtZGVtbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjFhZThlZWE5LWFjYzYtNGUzZC1iMzc4LTA3ZmViOTE0NmFjNCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnNhLWRlbW8ifQ.j0DQmzTeSfagKYGc2dMUuzhYqVQh2puJAoQS0EMKeiAKD6rC4bUHWXWBrCu5Ttvpch6ZTEYwyCdRof1lDGWiLa3pJ1R1RwUNVQTCmVTZPs7tTuoGLRW0KGfEd0jyi4LU6uw4kA_6kwEsz4q2quWcB_fiH_Z3iKVfh1JolYTVAWTBMWnVn6gBvIrlXV5ny2oyvcPQeVfIek8aPQqhbsct_qOxrjqpZY8mpBz0ETR_EELjmcZxVVPLvomOdCqEqbV-FF5KRiFxizB3Xoh6NHz3EcsxpCZNRYdand-UFHaBQC9IPwJKzxhANGmuZuWJUCqCVGGRZTo9c6eoyVz831sZ0A

You can see that the automatically generated secret object contains a token, which we can also get with the following command.

1
☸ ➜ kubectl get secrets sa-demo-token-4gvbw -o jsonpath='{.data.token}' | base64 -d

This token is a JWT structure, and we can copy this token to the jwt.io website for decoding.

JWT

The right part shows the contents of the token after it is decoded, where the PAYLOAD part is the information of sa-demo contained in the token, and you can see that there is no expiration time in it, which means the token will never expire.

Now let’s run a Pod using the sa we created above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  serviceAccount: sa-demo
  containers:
    - name: demo
      image: nginx:1.7.9
      ports:
        - containerPort: 80

Just create the Pod directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
☸ ➜ kubectl apply -f demo-pod.yaml
☸ ➜ kubectl get pods
NAME   READY   STATUS    RESTARTS   AGE
demo   1/1     Running   0          81s
☸ ➜ kubectl get pod demo -oyaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: nginx:1.7.9
    imagePullPolicy: IfNotPresent
    name: demo
# ......
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: sa-demo-token-4gvbw
      readOnly: true
# ......
  volumes:
  - name: sa-demo-token-4gvbw
    secret:
      defaultMode: 420
      secretName: sa-demo-token-4gvbw

After the pod is created, we can see that the secret corresponding to the specified sa is automatically mounted to the /var/run/secrets/kubernetes.io/serviceaccount directory of the container, so the directory must now contain the corresponding token file, which we can verify by looking at its contents.

1
2
☸ ➜ kubectl exec -it demo -- cat /run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6ImhQNmFMNjAyaDZ5OElyMmtTNGdPUWxRdHVDU1A4aGFfVkJiNHdHMkZjQlUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNhLWRlbW8tdG9rZW4tNGd2YnciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2EtZGVtbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjFhZThlZWE5LWFjYzYtNGUzZC1iMzc4LTA3ZmViOTE0NmFjNCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnNhLWRlbW8ifQ.j0DQmzTeSfagKYGc2dMUuzhYqVQh2puJAoQS0EMKeiAKD6rC4bUHWXWBrCu5Ttvpch6ZTEYwyCdRof1lDGWiLa3pJ1R1RwUNVQTCmVTZPs7tTuoGLRW0KGfEd0jyi4LU6uw4kA_6kwEsz4q2quWcB_fiH_Z3iKVfh1JolYTVAWTBMWnVn6gBvIrlXV5ny2oyvcPQeVfIek8aPQqhbsct_qOxrjqpZY8mpBz0ETR_EELjmcZxVVPLvomOdCqEqbV-FF5KRiFxizB3Xoh6NHz3EcsxpCZNRYdand-UFHaBQC9IPwJKzxhANGmuZuWJUCqCVGGRZTo9c6eoyVz831sZ0A

You can see that the token mounted in the Pod by the projected volume is exactly the same as the token contained in the secret of sa-demo. This token never expires, so even if you delete the Pod and recreate it, the token in the Pod remains the same, because the token data in the secret object does not change.

If you need to access K8s cluster resource objects in the Pod, you can now bind the corresponding permissions to the sa used, and then use the corresponding token to communicate with the APIServer in the Pod application, and the token will be able to identify the corresponding permissions at this time.

>= version 1.21 && <= version 1.23

Next we test the K8s cluster based on >= 1.21 && <= 1.23 versions.

Here we use kind to quickly create a v1.22.15 version of the cluster.

1
2
3
4
☸ ➜ kind create cluster --name kind122 --image kindest/node:v1.22.15
☸ ➜ kubectl get nodes
NAME                    STATUS   ROLES                  AGE    VERSION
kind122-control-plane   Ready    control-plane,master   115s   v1.22.15

Again, first create a ServiceAccount object named sa-demo.

1
2
3
4
5
6
7
8
9
☸ ➜ kubectl create sa sa-demo
☸ ➜ kubectl get sa
NAME      SECRETS   AGE
default   1         43s
sa-demo   1         6s
☸ ➜ kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-9w9bp   kubernetes.io/service-account-token   3      116s
sa-demo-token-g7d2g   kubernetes.io/service-account-token   3      8s

We can also see that the system automatically creates a corresponding secret object after creating sa, which is no different from the previous version, and we can also get the token value contained in the secret object with the following command.

1
2
☸ ➜ kubectl get secrets sa-demo-token-g7d2g -o jsonpath='{.data.token}' | base64 -d
eyJhbGciOiJSUzI1NiIsImtpZCI6Im1ERkhnQ3Y3b1oxUmNHbWVhN210SDEwNXY2dVNkc0QzdXJjTkhsY21FRVEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNhLWRlbW8tdG9rZW4tZzdkMmciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic2EtZGVtbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjI3ZGI0M2FjLTdjYjItNDQ2Yi05N2Q1LWU0MGUzOWRjZTg4YyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnNhLWRlbW8ifQ.fnSaqrZKolTfz2pi9t32X38Er60WSzUoRHArte6qVmQ1NTaMis4F6rESWekeJvGW26szTJdll6vK8KtL_IRO2m6sp_fEAYfNMQMXL4CuaRByXeAavDqLgMHhodf4k4Yg-Mj4LCQ3aHOxojbAbPT1i_h17Ewivc39fmzp-dAXbHhhWhCW2Vl_CkM-F-UtzLyDwThvJedkeetrfyOOjE7K6HpzWfqIQyMUdCJog3WnFO_4kHXacFCgYg_gNPMYyViQAsTsxB8FplGdEzRuWKnQO9cDE55V4l55IxmE0er-dSSdG8085PzxaM_lMCtRI8YtjRjxcbxS5QkTm5R_ps0IsA

Also copy the token value to the jwt.io website for decoding.

JWT

From the decoded value, we can see that the token value also does not contain any expiration time, which means the token will never expire after we create the sa.

Again, let’s use the above sa to create a Pod, as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  serviceAccount: sa-demo
  containers:
    - name: demo
      image: nginx:1.7.9
      ports:
        - containerPort: 80

Create the Pod directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
☸ ➜ kubectl apply -f demo-pod.yaml
☸ ➜ kubectl get pods
NAME   READY   STATUS    RESTARTS   AGE
demo   1/1     Running   0          81s
☸ ➜ kubectl get pod demo -oyaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: nginx:1.7.9
    imagePullPolicy: IfNotPresent
    name: demo
    ports:
    - containerPort: 80
      protocol: TCP
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-6wmfb
      readOnly: true
# ......
  volumes:
  - name: kube-api-access-6wmfb
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

When you look at the resource object after the Pod is created, you can see that there is a big difference from the previous version, instead of mounting the secret automatically created above to the container’s /var/run/secrets/kubernetes.io/serviceaccount directory. We can look at the token value in the Pod to compare it with the token value contained in the secret.

1
2
☸ ➜ kubectl exec -it demo -- cat /run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6Im1ERkhnQ3Y3b1oxUmNHbWVhN210SDEwNXY2dVNkc0QzdXJjTkhsY21FRVEifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzA1MDI1NDU4LCJpYXQiOjE2NzM0ODk0NTgsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJkZW1vIiwidWlkIjoiNzY1ODRmODAtZjU1My00Mzk2LWIxOTUtMDEwOTBhMzM4MWYyIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJzYS1kZW1vIiwidWlkIjoiMjdkYjQzYWMtN2NiMi00NDZiLTk3ZDUtZTQwZTM5ZGNlODhjIn0sIndhcm5hZnRlciI6MTY3MzQ5MzA2NX0sIm5iZiI6MTY3MzQ4OTQ1OCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6c2EtZGVtbyJ9.TAoe1eCHCXUoHh6oM4uySp8kzRaLQ44GZdU02Ir8m_dzYpdFSw4nwsNyqPggrZdDL3BMH4zceudBEdQuyxiSsrpVDeQKww2wTGhXAr2hWujrJq4ycmu6aMywyv2iRX9Vn-Las1giWK_bFuzCxiR10Lcgyd5N7VjB2WcT7K8rN7dAeUWgiH2s9lMOzoaIorUDXzlnSTcmxkhz1h7RXYKVGaqZBbd5wJsRnINZPGxqsS-wi21Aw2FFmIeeK8GGlnAqnS0f3VS1N2jm03gKPii-sMt0GARse4HsmhGAhyJnt9za6ZNpBgcybd7uEBjgIVrRFTkqBJOjPrAnMvRucVtwww

You can clearly see that the token value in the Pod is now different from the token value of the automatically created secret. Also decode the token value in jwt.io.

JWT

You can see that the decoded PAYLOAD data of the token contains many different data, and the exp field indicates the expiration time of the token, and you can see that the expiration time is 1 year.

Here we can summarize that in v1.21 to v1.23 K8s clusters, when creating a ServiceAccount object, the system will still automatically create a secret object, the token contained in the secret object is still never expired, but the token value of the secret will not be used in the Pod.

As you can see from the Pod manifest above after creation, when a Pod is created, the Kubernetes control plane now automatically adds a projected volume to the Pod that includes the token to access the Kubernetes API, and the manifest fragment defines a projected volume consisting of three data sources, which are as follows.

  • serviceAccountToken datasource: contains the token obtained by the kubelet from the kube-apiserver. kubelet uses the TokenRequest API to obtain a time-limited token. This token for the TokenRequest service expires after the Pod has been deleted or the defined lifecycle (default is 1 hour) has expired. The token is bound to a specific Pod and its audience is set to match the audience of the kube-apiserver. This mechanism replaces the previous mechanism of adding volumes based on Secret, which represented the ServiceAccount for the Pod but did not expire.
  • configMap data source: ConfigMap contains a set of certificate authority data that Pods can use to ensure they connect to the cluster’s kube-apiserver (and not to a middleware or accidentally misconfigured peer).
  • downwardAPI Data Source: Used to find the name of the namespace containing the Pod and make that name information available to the application code running within the Pod.

So instead of using the token in the secret object that is automatically associated with the ServiceAccount, we should specify that the Pod created by the current version of the K8s cluster contains a token that the kubelet will send a request to the TokenRequest API for a new token to be placed in the Pod’s / run/secrets/kubernetes.io/serviceaccount/token in the Pod. This token will be re-claimed by the kubelet after 1 hour, so if you check the token again after 1 hour, you will see that the contents of the token have changed. If you delete the pod and recreate it, the token will be re-claimed and the token in the deleted pod will expire immediately.

And we can also manually use the kubectl create token <sa> command to request a ServiceAccount token, which can specify the expiration date, etc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
☸ ➜ kubectl create token -h
Request a service account token.

Examples:
  # Request a token to authenticate to the kube-apiserver as the service account "myapp" in the current namespace
  kubectl create token myapp

  # Request a token for a service account in a custom namespace
  kubectl create token myapp --namespace myns

  # Request a token with a custom expiration
  kubectl create token myapp --duration 10m

  # Request a token with a custom audience
  kubectl create token myapp --audience https://example.com

  # Request a token bound to an instance of a Secret object
  kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret

  # Request a token bound to an instance of a Secret object with a specific uid
  kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret --bound-object-uid
0d4691ed-659b-4935-a832-355f77ee47cc

Options:
# ......

>= version 1.24

Now let’s take a look at how the ServiceAccount token works in a K8s cluster from v1.24 onwards. Here we use kind to quickly create a v1.25.3 version of the cluster.

1
2
3
4
☸ ➜ kind create cluster --name kind125 --image kindest/node:v1.25.3
☸ ➜ kubectl get nodes
NAME                    STATUS   ROLES                  AGE    VERSION
kind125-control-plane   Ready    control-plane,master   115s   v1.25.3

Also create a ServiceAccount named sa-demo.

1
2
3
4
5
6
7
☸ ➜ kubectl create sa sa-demo
☸ ➜ kubectl get sa
NAME      SECRETS   AGE
default   0         39d
sa-demo   0         5s
☸ ➜ kubectl get secrets
No resources found in ns1 namespace

We can see that the ServiceAccount is not created with a corresponding Secret object. Also create a Pod as shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  serviceAccount: sa-demo
  containers:
    - name: demo
      image: nginx:1.7.9
      ports:
        - containerPort: 80

Check out the details after creating the Pod above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
☸ ➜ kubectl apply -f demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
  namespace: default
spec:
  containers:
  - image: nginx:1.7.9
    imagePullPolicy: IfNotPresent
    name: demo
    ports:
    - containerPort: 80
      protocol: TCP
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-pftqd
      readOnly: true
# ......
  volumes:
  - name: kube-api-access-pftqd
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

You can see that the creation of a Pod also automatically adds a projected volume to the Pod, which includes a token to access the Kubernetes API, consistent with >=1.21 release && <= 1.23 release. Again, we can verify this by looking at the token values in the Pod.

1
2
☸ ➜ kubectl exec -it demo -- cat /run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6IndnazJLZENQTktiZkxVejhnMnhmTHJYRTlkZ2ZnOHJGQmgwVW4td3BWd0kifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzA0ODg0MDg0LCJpYXQiOjE2NzMzNDgwODQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJkZW1vIiwidWlkIjoiMTY0ZTIwZTYtYjNjMi00ZmQ5LWI3ZTUtMDZjYTExZWIyOWM4In0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJzYS1kZW1vIiwidWlkIjoiYjJlNWM3ZmYtNjlhNy00NzYyLTkxMDctM2UxNzZhYmQ3NTdiIn0sIndhcm5hZnRlciI6MTY3MzM1MTY5MX0sIm5iZiI6MTY3MzM0ODA4NCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6c2EtZGVtbyJ9.lhYscyn_d9Y3GZSipSqGj4Jtsu8qsIyz34L18lv37HxjjGU_bQmUFCXYf_CRom8DfadHppmlaskZS18KmyTV1Z09BeujJd8viUnnYCWb9K6VJB5uPBYWLB0FETfgQy7Kqu8Gvk8qBKLjdCkl8U2vr2Oqd2qSEDyvqhNBQXnckQRH6wyypBUc7EXSGAJf6dPVE3c6XqnbXMJ7SRZb5svE-hv0lZKmJrouz9Ia4qxUXUtpzDlMPnHOym2x9d1TSSZ1Lp7BOsqTnxlUQVueh9w869jAajrP1G9e5zhZwZBfzRfARqCVqoLid_hOQP-mo4MLfHbn61SWItlCBd75nl2WLQ

We can copy the token value output above to jwt.io for decoding.

JWT

As you can see from the data above, the token here is also valid for 1 year, and the token is updated every 1 hour in the Pod. If the Pod is deleted and rebuilt, then a new token will be claimed, and the token in the deleted Pod will expire immediately.

Note that there is no specific mechanism to invalidate tokens issued via TokenRequest. If you no longer trust the ServiceAccount token bound to a Pod, you can delete the Pod, and deleting the Pod will cause the token bound to it to expire.

Summary

We can briefly summarize how the ServiceAccount Token works under different versions of K8s clusters.

  • Versions before 1.20 (including 1.20), a secret is automatically created when creating a sa, and then this secret is mounted to the pod via a projected volume, and the token contained in this secret is persistent.
  • In versions 1.21~1.23, a secret is also created automatically when creating a sa, but the token in the secret is not used in the pod, instead, the kubelet goes to the TokenRequest API to request a token, which is valid for one year by default, but the pod will update the token every hour. The token is valid for one year by default, but the pod will update the token every hour.
  • In version 1.24 and above, the secret is no longer created automatically when creating a sa, only the token is requested from the kubelet to the TokenRequest API.

Of course we can still manually create a Secret to hold a ServiceAccount token, for example if you need a token that never expires. Once you manually create a Secret and associate it with a ServiceAccount, the Kubernetes control plane will automatically populate the token with that Secret.

Although there are mechanisms to manually create long-lasting ServiceAccount tokens, it is recommended to use TokenRequest to obtain short-term API access tokens.