Understanding service accounts and tokens in Kubernetes

alam
5 min readDec 27, 2022

--

As the name suggests, the service accounts are for the services or the non-human users in Kubernetes. It can perform all the tasks that the K8s API allows like human users. Kubernetes by default creates a service account in each namespace of a cluster and call it a default service account. These default service accounts are mounted to every pod launched.

However, the default service account has no attached permissions, and due to this, it is not of much use until we bind the service account with a role in the K8s RBAC.

In addition to the default service accounts, K8s allows us to create as many user-defined service accounts as we want. We can use the below command to create a service account.

kubectl create serviceaccount my-service-account

# Describing service account 

me@mycomp ~ % kubectl describe sa my-service-account
Name: my-service-account
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: <none>
Events: <none>

In the K8s version before 1.24, every time we would create a service account, a non-expiring secret token (Mountable secrets & Tokens) was created by default. However, from version 1.24 onwards, it was disbanded and no secret token is created by default when we create a service account. However, we can create it when need be. Now let us take a look at the service account token in a bit more depth.

Service Account Token

Kubernetes supports two types of tokens from version 1.22 onwards.

- Long-Lived Token
- Time Bound Token

Long-Lived Token

As its name indicates, a long-lived token is one that never expires. Hence, it is less secure and discouraged to use.

Creating Long-Lived Token
Before K8s version 1.24, whenever a service account was created, a secret object was also created that contains the secret token. These token would be long-lived token, which means it has no expiry. However, In Kubernetes version 1.24, it was disbanded due to security and scalability concerns.

Although not recommended, K8s allows us to create a long-lived token. It is achieved in two different steps:

  • Create a service account

kubect create serviceaccount my-service-account

  • Create a secret and specify the name of the service account as annotations within the metadata section.
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: my-long-lived-secret
annotations:
kubernetes.io/service-account.name: my-service-account
type: kubernetes.io/service-account-token
EOF

Time Bound Token

From version 1.22 onwards, Kubernetes introduced TokenRequest API. A token generated through this API is a time-bound token that expires after a time. It applies to both — the default service account and the custom-defined service accounts.

Creating a time-bound token
We can create a time-bound token using the below command:

kubectl create token my-time-bound-token

However, it is not required to create a token manually. Taking an example of the default service account — when a pod is launched with automountServiceAccountToken set to True, K8s control plane mounts a project volume to the pod. The kubelet agent running on the node provisions the token on this volume.

kubectl get pod nginx -o yaml
[truncated]
spec:
containers:
- image: nginx
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-cqslr
readOnly: true
serviceAccount: default
serviceAccountName: default
[truncated]

Token Expiration

Kubernetes is designed to expire the token in one hour, but there are many legacy applications running with the non-expiring token. To allow gradual adoption of the time-bound token, Kubernetes has allowed cluster admins to specify --service-account-extend-token-expiration=true to Kube API Server. When specified, it will allow tokens to have longer expiration (365 days) temporarily and record the usage of legacy tokens.

Click here to learn more about the token expiry.

Even if the token-expiration flag is set to true , Kubernetes allows us to create a token or mount a token that expires in one hour or whatever time we want. We can use the below definitions to achieve it.

apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /var/run/secrets/tokens
name: my-proj-vol
serviceAccountName: my-service-account #service acount
volumes:
- name: my-proj-vol
projected:
sources:
- serviceAccountToken:
path: my-proj-vol
expirationSeconds: 3600 #specify the desired epiration time in seconds

Configuring a Service Account for a Pod

Unlike the default service accounts, custom service accounts are not configured for a pod automatically. If we want to configure my-service-account for a pod, the following are the conditions:

  • A service account cannot be mounted to a running pod.

To configure a service account for a pod, we can use below pod definition file:

apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
serviceAccountName: my-service-account
automountServiceAccountToken: true

Using the service account in a pod

The service account that we create above, does not contain any permission. Once we have created a role and role binding, we can use the service account to perform the specified task.

[For role & role binding examples — please click here]

  • Exec into the pod using the below command
kubectl exec -it my-pod-with-service-account
  • Store the secret token in a variable
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
  • Send curl request to the Kubernetes API.
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/pods/ --insecure

The above curl command will list down all the pods and their configuration in the default namespaces.

me@mycom % kubectl exec -it my-pod-with-service-account -- bash                                                   
root@my-pod-with-service-account:/# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
root@my-pod-with-service-account:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/pods/ --insecure
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "46252191"
},
"items":[truncated]

References:

--

--