8 min to read
Understanding Kubernetes Service Accounts
🎯 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.
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
- Binds the ClusterRole to the ServiceAccount
Cluster Role
- Defines the permissions for the ServiceAccount
Role Binding
- Binds the Role to the ServiceAccount
Role
- Defines the permissions for the ServiceAccount
🔐 Key Components
- ServiceAccount Admission Controller
- Assigns default SA to pods
- Part of K8s API server
- ServiceAccount Token Controller
- Manages token generation
- Part of kube-controller-manager
- ServiceAccount Controller
- Manages SA lifecycle
- Handles secret creation/deletion
🔑 Kubernetes Authentication Components
- Token
- ca.crt
- Namespace
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?
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.
Comments