How to use service account tokens for Authenticating Subjects in Kubernetes

How to use service account tokens for Authenticating Subjects in Kubernetes

kubernetes_bearertoken_auth

Test Environment

Ubuntu 20.04
Kubernetes Cluster v1.23.1

What is Kubernetes

Kubernetes is an Open source container orchestration engine which helps in automating the deployments, scaling and management of containerised applications. Kubernetes can be used to deploy your applications into containerised environment using various different workloads and expose the application to the end user using the various networking features available. Today most of the monolithic application are being converted into microservice architecture application in which each component is developed as a separate API and deployed into the containerised environment. This type of micro serviced architecture application can be easily managed, upgraded and maintained. Kubernetes orchestration engine helps in automating these containerised application deployment, scaling, upgrading by utilising the resources in an optimised manner.

Kubernetes API server which is a service deployed on the Master node is the component which exposes the HTTP(S) API endpoint. All the API request to manage the Kubernetes cluster go through the API server. Here in this article will see how the Kubernetes API server can authenticate and authrorize an API request before it provides access to the cluster resources.

Kubernetes API can be accessed using the following methods

  • Kubectl
  • Client libraries
  • REST API requests

Types of users accessing the API server

  • Human user
  • Service accounts

Supported Authentication Modules are

  • Client certificates
  • Passwords
  • Static tokens
  • Bootstrap tokens
  • JSON web tokens (used for service accounts)

Here in this article we will see how we can use the Bearer token to authenticate an API request before it is sent to the authorization module which further examines the request for the permissions that are granted on the resources available in the kubernetes cluster.

If you are interested in watching the video. Here is the youtube video on the same step by step procedure outline below.

Procedure

Step1: Create a Service Account

In this step we are going to create a service account. service accounts are used by the processes in containers inside the pods to communication with API server.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl create sa bearertokenauth
serviceaccount/bearertokenauth created

Step2: Retrieve the Bearer token from the Service Account

When a service account is created in kubernetes, it automatically associates a secret token (ie. Bearer token). Let us find identify the secret token name with the below command.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl get sa bearertokenauth -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2021-12-24T07:59:05Z"
  name: bearertokenauth
  namespace: default
  resourceVersion: "18430"
  uid: d15ac857-1034-4341-b339-c99998e07d22
secrets:
- name: bearertokenauth-token-fvmmd

Now, lets extract the secret token from the secret named ‘bearertokenauth-token-fvmmd’ and decode it using the base64.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl get secret bearertokenauth-token-fvmmd -o jsonpath='{.data.token}' | base64 -d
eyJhbGciOiJSUzI1NiIsImtpZCI6IkQ2b2lwY29ibURNcVBKc2J5bWlicWNVd1RBejRsVFZQV2ZzeXk2d3lJcDQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImJlYXJlcnRva2VuYXV0aC10b2tlbi1mdm1tZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJiZWFyZXJ0b2tlbmF1dGgiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkMTVhYzg1Ny0xMDM0LTQzNDEtYjMzOS1jOTk5OThlMDdkMjIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpiZWFyZXJ0b2tlbmF1dGgifQ.qdP1NHUAhTVUJ_kEpxh6qPQOSW1oTWHno_RZqw6EvgVgNS1hHLj_o4NDnb0Ge01B22Gh88YQhV001GS1OK9diHYZleWJUMqfTd9J3DTk6pYnUJ659DIMggLvUaO51W4P37ioYTdY7mY9tsVdhM3zoknUSw81P5aOBLzB6au3j5VO3htO5mUJei4jC70CiluGR8SvhgvjP7IRrjuFIEqOPdNUDeEQMYn6HwIjq5xPrd317lmX589wL6PMeJZVGNAnNWvvNFXtBR0CproEEbQ4Bq180Nw6ZfCi3x2rDcANwu13HczpoGR2kwPd-zteDtRiiKp3hcuARlR6OJMiO_42xQ

Step3: Add the ‘bearertokenauth’ credentials to kubeconfig

Here we are going to add the decoded bearer token into the kubectl configuration file as shown below.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl config set-credentials bearertokenauth --token=eyJhbGciOiJSUzI1NiIsImtpZCI6IkQ2b2lwY29ibURNcVBKc2J5bWlicWNVd1RBejRsVFZQV2ZzeXk2d3lJcDQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImJlYXJlcnRva2VuYXV0aC10b2tlbi1mdm1tZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJiZWFyZXJ0b2tlbmF1dGgiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkMTVhYzg1Ny0xMDM0LTQzNDEtYjMzOS1jOTk5OThlMDdkMjIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpiZWFyZXJ0b2tlbmF1dGgifQ.qdP1NHUAhTVUJ_kEpxh6qPQOSW1oTWHno_RZqw6EvgVgNS1hHLj_o4NDnb0Ge01B22Gh88YQhV001GS1OK9diHYZleWJUMqfTd9J3DTk6pYnUJ659DIMggLvUaO51W4P37ioYTdY7mY9tsVdhM3zoknUSw81P5aOBLzB6au3j5VO3htO5mUJei4jC70CiluGR8SvhgvjP7IRrjuFIEqOPdNUDeEQMYn6HwIjq5xPrd317lmX589wL6PMeJZVGNAnNWvvNFXtBR0CproEEbQ4Bq180Nw6ZfCi3x2rDcANwu13HczpoGR2kwPd-zteDtRiiKp3hcuARlR6OJMiO_42xQ
User "bearertokenauth" set.

Step4: Create a new context for the ‘bearertokenauth’

Now, lets create a new context with the user ‘bearertokenauth’ provided access to the cluster ‘kubernetes’ as shown below.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl config set-context bearertokenauth --cluster=kubernetes --user=bearertokenauth
Context "bearertokenauth" created.

Step5: Switch to the new context to validate the access

Let’s now switch to the new ‘bearertokenauth’ context and try to access the kubernetes cluster resources.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl config use-context bearertokenauth
Switched to context "bearertokenauth".
kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl get pods
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:bearertokenauth" cannot list resource "pods" in API group "" in the namespace "default"

As you can see the service account does not have access to list the pods in default namespace. Let’s create an role and rolebinding to attach required permissions to the service account in the next steps.

Step6: Create a role

Switch the context to kubernetes-admin@kubernetes ( ie. the default one that is setup when kubernetes cluster is provisioned) with the required privileges.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".

Now let us create a role which provides the below level of access to the pods as shown below.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl create role bearertokenauth --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods
role.rbac.authorization.k8s.io/bearertokenauth created
kubeadmin@kubemaster:~/stack/normal_user_auth$ 
kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl get role bearertokenauth -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  creationTimestamp: "2021-12-24T08:27:02Z"
  name: bearertokenauth
  namespace: default
  resourceVersion: "20426"
  uid: 45bd0cb8-130d-4976-8bb8-9e6c255c12e8
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - create
  - get
  - list
  - update
  - delete

Step7: Create a RoleBinding

Now, lets create a rolebinding to bind the new role ‘bearertokenauth’ with serviceaccount ‘default:bearertokenauth’. Note, you need to append the namespace to which the service account belongs.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl create rolebinding bearertoken-binding-bearertokenauth --role=bearertokenauth --serviceaccount=default:bearertokenauth --namespace=default
rolebinding.rbac.authorization.k8s.io/bearertoken-binding-bearertokenauth created
kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl get rolebinding bearertoken-binding-bearertokenauth -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  creationTimestamp: "2021-12-24T08:30:27Z"
  name: bearertoken-binding-bearertokenauth
  namespace: default
  resourceVersion: "20671"
  uid: affbdd45-d77c-4443-8760-2fafffad8515
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: bearertokenauth
subjects:
- kind: ServiceAccount
  name: bearertokenauth
  namespace: default

Step8: Switch to the ‘bearertokenauth’ context and validate access

Again, now let’s switch to the new context ‘bearertokenauth’ which we created earlier and try to list the pods in the default namespace. As you can see now we are not getting the forbidden error anymore. That means we are not able to access the pods resources in the default namespace. Unfortunately i didn’t created any pods in the default namspace so the output is empty. You can create some pods in the default namespace on your end and should be able to see them in the output on your end.

kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl config use-context bearertokenauth
Switched to context "bearertokenauth".
kubeadmin@kubemaster:~/stack/normal_user_auth$ kubectl get pods
No resources found in default namespace.

Step9: Bash script to emulate the above procedure

Here is a very basic bash script to emulate the above procedure that we discussed. This script accepts one argument that is the serviceaccount name which we need to pass while running the script. The script execution pauses at each step waiting for you to hit enter to proceed to the next step.

kubeadmin@kubemaster:~/stack/kubernetes_bearertoken_auth$ cat bearertokeauth.sh 
#!/bin/bash

serviceaccountName=$1


# Create service account

echo "==========================================================================================================="
echo "kubectl create sa $serviceaccountName"
echo "==========================================================================================================="

kubectl create sa $serviceaccountName
read -p "Press enter to continue"

# Retrieve the bearer token

echo "==========================================================================================================="
echo "kubectl get sa $serviceaccountName -o jsonpath='{.secrets[0].name}'"
echo "kubectl get secret $secretName -o jsonpath='{.data.token}' | base64 -d"
echo "==========================================================================================================="

secretName=`kubectl get sa $serviceaccountName -o jsonpath='{.secrets[0].name}'`
tokenID=`kubectl get secret $secretName -o jsonpath='{.data.token}' | base64 -d`
read -p "Press enter to continue"

# Update kubectl configuration

echo "==========================================================================================================="
echo "kubectl config set-credentials $serviceaccountName --token=$tokenID"
echo "kubectl config set-context $serviceaccountName --cluster=kubernetes --user=$serviceaccountName"
echo "==========================================================================================================="

kubectl config set-credentials $serviceaccountName --token=$tokenID
kubectl config set-context $serviceaccountName --cluster=kubernetes --user=$serviceaccountName
read -p "Press enter to continue"

# Create a role and rolebinding

echo "==========================================================================================================="
echo "kubectl create role $serviceaccountName --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods"
echo "kubectl create rolebinding bearertoken-binding-$serviceaccountName --role=$serviceaccountName --serviceaccount=default:$serviceaccountName --namespace=default"
echo "==========================================================================================================="

kubectl create role $serviceaccountName --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods
kubectl create rolebinding bearertoken-binding-$serviceaccountName --role=$serviceaccountName --serviceaccount=default:$serviceaccountName --namespace=default
read -p "Press enter to continue"

# Validate the access using bearer token
echo "==========================================================================================================="
echo "kubectl config use-context $serviceaccountName"
echo "kubectl get pods"
echo "==========================================================================================================="

kubectl config use-context $serviceaccountName
kubectl get pods
kubeadmin@kubemaster:~/stack/kubernetes_bearertoken_auth$ ./bearertokeauth.sh satestauth

Hope you learned something new in this article. Thank you..