Kubernetes’ Service Account is a type of account managed by Kubernetes, which is particularly convenient to manage, but it is not easy to understand the application context when you are new to this type of account. This article is a complete overview that I have read after reading many documents, and I believe it can provide a certain level of understanding of service accounts.

Kubernetes’ Service Account

Account Types

There are two types of Kubernetes accounts. 1.

  1. User accounts (Normal Users)

    Anyone who wants to connect to and access the Kubernetes cluster needs to create a “User Account” and provide credential information to the client (e.g., kubectl) for authentication through the Kubernetes API server.

    I think the name should be better understood as User Accounts, but the official Kubernetes website calls it Normal Users.

  2. Service Accounts

    Any container running inside a Pod that wants to access the Kubernetes API server (kube-apiserver) needs to have a “Service Account” bound to the Pod first in order to pass the authentication of the Kubernetes API server.

Experience the namespace default Service Account

When you create namespace, a service account called default is created for you by default.

  1. Create dev namespace

    1
    2
    
    kubectl create namespace dev
    kubectl label namespace dev name=dev
    
  2. Get the serviceaccounts under the dev namespace

    1
    
    kubectl get serviceaccounts --namespace=dev
    
    1
    2
    
    NAME      SECRETS   AGE
    default   1         9s
    
  3. Get the contents of the default service account under the dev namespace

    1
    
    kubectl get serviceaccounts default -n=dev -o yaml
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    apiVersion: v1
    kind: ServiceAccount
    metadata:
    creationTimestamp: "2022-08-23T14:55:51Z"
    name: default
    namespace: dev
    resourceVersion: "1434536"
    selfLink: /api/v1/namespaces/dev/serviceaccounts/default
    uid: 3b750bc5-fd6c-43b0-9c64-4a4700f522ae
    secrets:
    - name: default-token-xpqc7
    

    It will automatically bind a secrets to keep the Token information of the service account.

  4. Get the secrets content bound by the default service account under the dev namespace

    1
    
    kubectl get secrets default-token-xpqc7 -n=dev -o yaml
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    apiVersion: v1
    data:
    ca.crt: DATA+OMITTED
    namespace: ZGV2
    token: TOKEN+OMITTED
    kind: Secret
    metadata:
    annotations:
        kubernetes.io/service-account.name: default
        kubernetes.io/service-account.uid: 3b750bc5-fd6c-43b0-9c64-4a4700f522ae
    creationTimestamp: "2022-08-23T14:55:51Z"
    name: default-token-xpqc7
    namespace: dev
    resourceVersion: "1434535"
    selfLink: /api/v1/namespaces/dev/secrets/default-token-xpqc7
    uid: 868a7d4f-74b8-4be4-8c0e-b9d5a3e678b2
    type: kubernetes.io/service-account-token
    

Experience how Pods use Service Account

  1. We start by creating a Pod in the dev namespace without specifying a default service account.

    1
    
    kubectl run microbot --image=dontrebootme/microbot:v1 -n dev
    
  2. In fact, all Pods are added to default service accounts by default

    If you don’t specify spec.serviceAccountName when you create a Pod in namespace, Kubernetes will also add the default service account in the same namespace by default. Therefore, every Pod must have a service account bound to it.

    1
    
    kubectl get microbot -n dev -o yaml
    

    At this point you will see the following YAML file, with a serviceAccountName: default that has been automatically set in.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    apiVersion: v1
    kind: Pod
    metadata:
    labels:
        run: microbot
    name: microbot
    namespace: dev
    spec:
    containers:
    - image: dontrebootme/microbot:v1
        imagePullPolicy: IfNotPresent
        name: microbot
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
        name: kube-api-access-dfs8b
        readOnly: true
    dnsPolicy: ClusterFirst
    enableServiceLinks: true
    nodeName: microk8s-vm
    preemptionPolicy: PreemptLowerPriority
    priority: 0
    restartPolicy: Always
    schedulerName: default-scheduler
    securityContext: {}
    serviceAccount: default
    serviceAccountName: default
    ...
    

    And by default, a /var/run/secrets/kubernetes.io/serviceaccount directory is mounted in the Pod!

  3. View the /var/run/secrets/kubernetes.io/serviceaccount directory inside the Pod

    1
    
    kubectl exec microbot -it -n dev -- sh
    
    1
    
    ls -laF /var/run/secrets/kubernetes.io/serviceaccount
    

    serviceaccount

    It looks like it automatically mounts all the secrets contained in serviceaccounts to this directory!

  4. Send HTTP requests to kube-apiserver in the Pod’s container

    1
    2
    3
    4
    
    apk update
    apk add curl
    
    curl --insecure https://kubernetes.default.svc.cluster.local:443/api/
    

    If TOKEN is not brought in, it will be accessed as system:anonymous for anonymous users.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    {
    "kind": "Status",
    "apiVersion": "v1",
    "metadata": {
    
    },
    "status": "Failure",
    "message": "forbidden: User \"system:anonymous\" cannot get path \"/api/\"",
    "reason": "Forbidden",
    "details": {
    
    },
    "code": 403
    }
    
  5. Bring in the TOKEN of the default service account to access the kube-apiserver

    1
    2
    3
    
    CACERT='/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
    TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
    curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/
    

    At this point you will see that the API request has been validated.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    {
    "kind": "APIVersions",
    "versions": [
        "v1"
    ],
    "serverAddressByClientCIDRs": [
        {
        "clientCIDR": "0.0.0.0/0",
        "serverAddress": "172.25.239.227:16443"
        }
    ]
    }
    

This is the standard way to use Service Account in Pod! 👍

Adding Role Permissions to a Service Account

In fact, although this “authenticated” TOKEN can call part of the kube-apiserver API, the default service account does not actually have access to any resources in the K8s cluster. We have to create a Role and assign privileges to it through the RBAC mechanism, and then bind the account through RoleBinding to give it sufficient privileges to access resources.

  1. Try to get the PodList list first

    1
    
    curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/dev/pods/
    

    You will get the following error message.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    {
    "kind": "Status",
    "apiVersion": "v1",
    "metadata": {},
    "status": "Failure",
    "message": "pods is forbidden: User \"system:serviceaccount:dev:default\" cannot list resource \"pods\" in API group \"\" in the namespace \"dev\"",
    "reason": "Forbidden",
    "details": {
        "kind": "pods"
    },
    "code": 403
    }
    

    At this point we will know that our User is system:serviceaccount:dev:default and our API group is "", namespace is dev and resource type (kind) is pods.

    In addition, you can also use the kubectl auth can-i command to quickly find out if a specific user has privileges to specific resources, which is quite useful!

    1
    
    kubectl auth can-i get pods -n=dev --as=system:serviceaccount:dev:default
    

    He will simply reply to you yes or no. In its current state, it should reply no! 👍

  2. Create Role objects

    1
    
    kubectl create role read-pods -n=dev --verb='get,list' --resource=pods
    

    or generate the corresponding YAML file content via the -dry-run=client -o yaml parameter.

    1
    
    kubectl create role read-pods -n=dev --verb='get,list' --resource=pods --dry-run=client -o yaml
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
    creationTimestamp: null
    name: read-pods
    namespace: dev
    rules:
    - apiGroups:
    - ""
    resources:
    - pods
    verbs:
    - get
    - list
    
  3. Create RoleBinding objects

    1
    
    kubectl create rolebinding read-pods -n dev --user=system:serviceaccount:dev:default --role=read-pods
    

    or generate the corresponding YAML file content via the -dry-run=client -o yaml parameter.

    1
    
    kubectl create rolebinding read-pods -n dev --user=system:serviceaccount:dev:default --role=read-pods --dry-run=client -o yaml
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    creationTimestamp: null
    name: read-pods
    namespace: dev
    roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: Role
    name: read-pods
    subjects:
    - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: system:serviceaccount:dev:default
    

    The above syntax can also be written in the subjects: field, it is actually the same, ServiceAccount (kind: ServiceAccount ) (name: default ) is GeneralAccount (kind: User ) (name: system: serviceaccount:dev:default ), but the names are expressed in different ways.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: read-pods
    namespace: dev
    roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: Role
    name: read-pods
    subjects:
    - kind: ServiceAccount
    name: default
    

    First check if the system:serviceaccount:dev:default service account has been granted get permissions for pods.

    1
    2
    
    kubectl auth can-i get pods -n=dev --as=system:serviceaccount:dev:default
    kubectl auth can-i list pods -n=dev --as=system:serviceaccount:dev:default
    
  4. Get the PodList list again

    1
    
    curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/dev/pods/
    

    At this point, you should be able to get the complete PodList list with detailed information.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    {
    "kind": "PodList",
    "apiVersion": "v1",
    "metadata": {
        "selfLink": "/api/v1/namespaces/dev/pods/",
        "resourceVersion": "1446386"
    },
    "items": [
        {
        "metadata": {
            "name": "microbot",
            "namespace": "dev",
            ...
        }
        ...
        }
        ...
    ]
    }
    

Experience creating a new service account

With the above understanding of default service account, I believe it is not difficult to understand the usage of custom service account. Here are the steps to experience it.

  1. Create a custom service account monitor in the dev namespace

    1
    
    kubectl create serviceaccount monitor -n dev
    
  2. Set Role and RoleBinding

    1
    2
    
    kubectl create role monitor-pods -n=dev --verb='get,list,watch' --resource='pods,pods/status'
    kubectl create rolebinding monitor-pods -n dev --serviceaccount='dev:monitor' --role=monitor-pods
    
  3. Create the Pod using YAML and specify the serviceAccountName: monitor service account

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    apiVersion: v1
    kind: Pod
    metadata:
    name: "microbot"
    namespace: dev
    labels:
        app: "microbot"
    spec:
    containers:
    - name: microbot
        image: "dontrebootme/microbot:v1"
    serviceAccountName: monitor
    

    A quick way to apply it with PowerShell.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    @'
    apiVersion: v1
    kind: Pod
    metadata:
    name: "microbot"
    namespace: dev
    labels:
        app: "microbot"
    spec:
    containers:
    - name: microbot
        image: "dontrebootme/microbot:v1"
    serviceAccountName: monitor
    '@ | kubectl apply -f -
    
  4. Calling the API server from a container in Pods

    1
    
    kubectl exec microbot -it -n dev -- sh
    
    1
    2
    3
    4
    5
    6
    
    apk update
    apk add curl
    
    CACERT='/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
    TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
    curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/dev/pods/
    

Summary

Now you should know how to access the resources in the Kubernetes cluster from your application in the Pod.

I used to think that to read ConfigMaps or Secrets from a Pod you had to mount them as volumeMounts or env, but in fact there is no way to apply RBAC licenses via volumeMounts or env. When you want to restrict application access to cluster resources, you have to change the YAML and apply updates, which is not reliable.

Furthermore, it is not very convenient to have to redeploy or restart Deployment every time you change configuration settings.

Now you can access Kubernetes resources directly from your application through a service account, and you can limit the scope of access through the RBAC mechanism, which is both flexible and secure, which I think is great! 👍

Reference

  • https://blog.miniasp.com/post/2022/08/24/Understanding-Service-Account-in-Kubernetes-through-MicroK8s