Understanding Kubernetes Service Accounts

Featured image

image reference link



🎯 Overview

Service Accounts in Kubernetes are credentials used to grant permissions to pods for interacting with the API server.


What is Service Account?

Service Account is a Kubernetes resource that provides an identity for processes that run in a Pod.

Service account key elements play an essential role in the authentication and authorization process within the cluster.

k8s-service-account-component image reference link

Before Kubernetes 1.24, the service account token is automatically created when the service account is created for the first time.

$ kubectl get serviceaccount -n somaz
NAME                                   SECRETS   AGE
default                                1         27d

$ kubectl get secret -n somaz
NAME                                               TYPE                                  DATA   AGE
default-token-7c6dm                                kubernetes.io/service-account-token   3      27d

🔑 Service Account Basics

# Basic Service Account Structure (K8s 1.24+)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: servicenow-discovery
  namespace: default
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: servicenow-discovery-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: "servicenow-discovery"
---
apiVersion: v1
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  # "namespace" omitted since ClusterRoles are not namespaced
  name: read-only
rules:
- apiGroups:
    - apps
    - extensions
    - "*"
    - ""
  resources: ["*"]
  verbs: ["get", "watch", "list"]
---
apiVersion: v1
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: discovery-global
subjects:
- kind: ServiceAccount
  name: servicenow-discovery
  namespace: default
- kind: User
  name: servicenow-discovery
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: read-only
  apiGroup: rbac.authorization.k8s.io


Cluster Role Binding

Cluster Role

Role Binding

Role


🔐 Key Components

  1. ServiceAccount Admission Controller
    • Assigns default SA to pods
    • Part of K8s API server
  2. ServiceAccount Token Controller
    • Manages token generation
    • Part of kube-controller-manager
  3. ServiceAccount Controller
    • Manages SA lifecycle
    • Handles secret creation/deletion

🔑 Kubernetes Authentication Components

When a service account is created, Kubernetes automatically creates a security secret containing the following three components.

Token

A token is a JSON web token (JWT) that can be used to authenticate requests to the Kubernetes API server on behalf of service accounts. The token is signed with the private key of the Kubernetes API server

It can be verified using the corresponding public key (in ca.crt). The token contains information about the service account, such as its name and namespaces to which it belongs.

ca.crt

The ca.crt file contains a certificate of certification authority (CA) for the Kubernetes cluster. It is used to establish trust between the client and the API server when making a request.

The client can use this CA certificate to verify that the API server’s certificate is valid and signed by the same CA. This allows the client to verify that it is communicating with an authenticated API server and not a malicious actor.

Namespace

Namespace is a key component of Kubernetes’ multi-tenant architecture. Namespace is used to logically separate resources within a cluster, allowing multiple teams or projects to share the same cluster without interfering with each other.

When a service account is created, it is associated with a specific namespace. If the service account is not granted full cluster permission through ClusterRoleBindings, the token of the service account authorized for that namespace through RoleBindings can only be used to access resources within that namespace.



🔄 Component Table

🔧 Component 📝 Description
🔑 Token 📄 JWT for API authentication
📜 ca.crt 🔐 Cluster CA certificate
📂 Namespace 🗂️ Associated namespace



💡 Pratice Service Account

I’m going to proceed with the existing service account and service account token.

$ k get serviceaccounts -n somaz
NAME                                   SECRETS   AGE
default                                1         27d

$ k get secret -n somaz
NAME                                               TYPE                                  DATA   AGE
default-token-7c6dm                                kubernetes.io/service-account-token   3      27d

The service account is mounted on the pad as shown below.

k describe po -n somaz somaz-db-d7d785ff4-mwgfm
    Mounts:
      /etc/mysql/conf.d from config-volume (rw)
      /var/lib/mysql from mysql-persistent-storage-dev2 (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-9dnzq (ro)

If you check the pad, there is a token in the place where it is mounted. So you can communicate with the Kubernetes API server with that token.

k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- ls -l /var/run/secrets/kubernetes.io/serviceaccount/
total 0
lrwxrwxrwx 1 root root 13 Apr 12 10:00 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Apr 12 10:00 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Apr 12 10:00 token -> ..data/token

If you check the token file, there is a value.

k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOi...

And after creating the Role, Rollbinding is done.

Simply put, Role is a role that only works in certain namespaces ClusterRole is a role that operates throughout the cluster

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: service-account-somaz-role
  namespace: somaz
rules:
  - apiGroups: [""] 
    resources: ["pods"]
    verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: service-account-somaz-role-binding
  namespace: somaz 
subjects:
  - kind: ServiceAccount
    name: default
    namespace: somaz
roleRef:
  kind: Role 
  name: service-account-somaz-role 
  apiGroup: rbac.authorization.k8s.io

Check the communication as below.

k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- bash
bash-4.4# cd /var/run/secrets/kubernetes.io/serviceaccount/
bash-4.4# TOKEN=$(cat token)
bash-4.4# curl -X GET https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/somaz/pods --header "Authorization: Bearer $TOKEN" --insecure
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "79523984"
  },
  "items": [
    {
      "metadata": {
        "name": "somaz-db-d7d785ff4-mwgfm",
        "generateName": "somaz-db-d7d785ff4-",
        "namespace": "somaz",
        "uid": "2f44c054-937a-478c-b719-20298846f236",
...

❓ Common Questions

Q1: What is $KUBERNETES_SERVICE_HOST?
A1: API server IP address automatically injected into pods

Q2: Why are pod and cluster token values different?
A2: Kubernetes automatically rotates tokens for security purposes

🔑 What is $KUBERNETES_SERVICE_HOST?

It indicates the IP address of the Kubernetes API server.

It is automatically injected into the Pod environment and allows the running Pod to communicate with the API server.

echo $KUBERNETES_SERVICE_HOST
10.233.0.1

k get service -n default
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.233.0.1   <none>        443/TCP   372d

🔑 Why are pod and cluster token values different?

First, check the token value of the pad.

k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZC...

Second, check the token value of the cluster.

$ k get secrets -n somaz
NAME                                               TYPE                                  DATA   AGE
default-token-7c6dm                                kubernetes.io/service-account-token   3      27d

$ k get secrets -n somaz default-token-7c6dm -o yaml | k neat
apiVersion: v1
data:
  ca.crt:...
  namespace: c29tYXoK # echo "somaz" | base64 -> c29tYXoK
  token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkltVlNSalpvYnpaR09UZER...

Obviously the two token values are different. So how do we communicate?

🔐 Why Are the Two Tokens Different?

The reasons are as follows. In fact, it seems that the two tokens should be the same, but Kubernetes automatically rotates the service account token for security reasons.

This is a measure to strengthen security in case the service account token is stolen or leaked. The rotation period of the service account token varies according to the Kubernetes cluster setting.

Therefore, depending on the time of creation, the token of the pad and the token inquired with the kubectl get secret command may be different. This is why the two tokens are different.



📚 Reference