Ludovic Alarcon

Ludovic Alarcon .

Kubernetes and Cloud technologies enthusiast, DevOps believer - Golang and Dotnet Developer

Service Account Token: breaking change k8s 1.24

This article aims to help people who did not migrate to kubernetes 1.24 or greater. For others, you might have encounter that change already.

Since Kubernetes version 1.24 , ServiceAccount don’t generate tokens as secrets by default anymore.

> kubectl create sa example-sa

Before kubernetes 1.24

> kubectl describe sa example-sa

Name:                example-sa
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   example-sa-token-rev86
Tokens:              example-sa-token-rev86
Events:              <none>

After kubernetes 1.24

> kubectl describe sa example-sa

Name:                example-sa
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>

We can see that the token is not generated anymore.

Create a token

Using kubectl

> kubectl create token example-sa

eyJhbGciOiJSU....SLoBbhiafJnw

It’s possible to define an expiration period with --duration= option.
As of today, the default value is 3600s.

Old behavior

If we need to have a secret with a non-expiring token, we can do so.
We have to create a secret of type kubernetes.io/service-account-token with an annotation kubernetes.io/sevice-account.name.

apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: example-sa-token
  annotations:
    kubernetes.io/service-account.name: "example-sa"

The secret will be populated with the token and the ca certificates.

When deploying with Helm, a good practice will be to verify the Kubernetes version and deploy the secret only for version >= 1.24.
To do so, we can use helm capabilities and helm semverCompare function


apiVersion: v1
kind: ServiceAccount
metadata:
  name: example-sa
---
{{- if semverCompare ">=1.24-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: example-sa-token
  annotations:
    kubernetes.io/service-account.name: "example-ca"
{{- end -}}

Keep in mind that token generation behavior change for security reasons, so avoid as much as possible to create a long live token and prefer generated one with the TokenRequestAPI.

Mounting token

Automatically

Token are still mounted automatically when ServiceAccountName property is used in pod spec.
The token will be available under /var/run/secrets/kubernetes.io/serviceaccount/token, it is valid for 1h and is automatically renewed.

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: example
    image: nginx
    ports:
    - containerPort: 80
  serviceAccountName: example-sa

Unless you specified the SA should not automount the token by setting automountServiceAccountToken to false.

apiVersion: v1
automountServiceAccountToken: false
kind: ServiceAccount
metadata:
  name: example-sa

Manually

For SA auto-mounting in pod, a projected volume is used under the hood.
We can leverage it by creating one manually. We can then mount it in a different location and even change the expiry.

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: example
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: /var/run/secrets/token
      name: token
      readOnly: true
  serviceAccountName: example-sa
  volumes:
  - name: token
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 600
          path: token

If you need to keep the old behavior using the token store into a secret, you can still mount it into a volume. But once again, keep in mind it should be avoid as possible for security reasons.

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: example
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: /var/run/secrets/token
      name: token
      readOnly: true
  volumes:
  - name: token
    secret:
    	defaultMode: 420
    	secretName: example-sa-token