Understanding and Implementing Service Accounts in Kubernetes

by Gautham Pai

Introduction

Kubernetes Service Accounts play a crucial role in controlling access to cluster resources. This guide will walk you through the concept of Service Accounts and explain how to use them to control access to resources within a cluster.

What are Service Accounts?

Service Accounts in Kubernetes are used to provide an identity for processes running in a Pod. They allow you to control access to the Kubernetes API and other resources within the cluster. Every Pod in a Kubernetes cluster runs with an associated Service Account, and if not specified, it uses the default Service Account in the namespace.

Our Goal

In this article, we'll create a Python script that lists pods in a cluster. We'll then see how this script behaves with different Service Account configurations:

  1. Using the default Service Account
  2. Using a custom Service Account with restricted permissions

Prerequisites

  • A running Kubernetes cluster - you can use kind
  • kubectl installed and configured
  • Docker installed (for building our custom image)

Ensure that you have a ~/data/service-accounts-example directory.

mkdir -p ~/data/service-accounts-example
cd ~/data/service-accounts-example

Step 1: Creating a Custom Docker Image

First, let's create a Dockerfile for our custom image that includes Python, IPython, and the Kubernetes client library.

# Use an official Python runtime as a parent image
FROM python:3.9-slim

# Set the working directory in the container
WORKDIR /app

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir ipython kubernetes

# Keep the container running
CMD ["/bin/bash"]

Build this image:

docker build -t kubernetes-ipython:latest .

If you're using kind, make the image available to the cluster:

kind load docker-image kubernetes-ipython:latest

Step 2: Creating a Pod

Now, let's create a YAML called pod-with-default-sa.yaml that has a Pod definition using this image:

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-default-sa
spec:
  containers:
  - name: kubernetes-ipython
    image: kubernetes-ipython:latest
    imagePullPolicy: Never
    command: ["/bin/bash"]
    stdin: true
    tty: true

Apply this YAML:

kubectl apply -f pod-with-default-sa.yaml

In Kubernetes, each namespace has its own default service account. The default service account is named default and is automatically created when a namespace is created. Later, when a Pod is created without any specific Service Account, it uses this default Service Account.

Step 3: Creating a Python Script to List Pods

Create a Python script (list_pods.py) that lists all pods in the cluster:

from kubernetes import client, config

def list_pods():
    config.load_incluster_config()
    v1 = client.CoreV1Api()
    print("Listing pods with their IPs:")
    ret = v1.list_pod_for_all_namespaces(watch=False)
    for i in ret.items:
        print(f"{i.status.pod_ip}\t{i.metadata.namespace}\t{i.metadata.name}")

if __name__ == "__main__":
    list_pods()

Step 4: Running the Script with the default Service Account

  1. Copy the Python script into the pod:

    kubectl cp list_pods.py pod-with-default-sa:/app/list_pods.py
    
  2. Run the script within the pod:

    kubectl exec -it pod-with-default-sa -- python /app/list_pods.py
    

You should see an error:

kubernetes.client.exceptions.ApiException: (403)
Reason: Forbidden
HTTP response headers: HTTPHeaderDict({'Audit-Id': 'd398154b-33e5-49c9-830f-4dd03d9d7d2d', 'Cache-Control': 'no-cache, private', 'Content-Type': 'application/json', 'X-Content-Type-Options': 'nosniff', 'X-Kubernetes-Pf-Flowschema-Uid': '6a3957b9-486b-41d7-a839-314428bb27b2', 'X-Kubernetes-Pf-Prioritylevel-Uid': 'd46cd2ff-8d51-4f24-9516-e49aadc81951', 'Date': 'Wed, 04 Sep 2024 07:19:10 GMT', 'Content-Length': '274'})
HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"pods is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"pods\" in API group \"\" at the cluster scope","reason":"Forbidden","details":{"kind":"pods"},"code":403}

Step 5: Creating a ClusterRoleBinding

Let us create a ClusterRoleBinding to allow the default Service Account to list all pods in all namespaces. Create a YAML called, allow-default-to-list-pods.yaml with the following contents:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: default-sa-pod-lister
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pod-lister
subjects:
- kind: ServiceAccount
  name: default
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-lister
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["list", "get"]

Apply the YAML:

kubectl apply -f allow-default-to-list-pods.yaml

You can use the following command to check if the default service account can list pods:

kubectl auth can-i --as=system:serviceaccount:default:default list pods

Verify that you can now actually list pods:

kubectl exec -it pod-with-default-sa -- python3 list_pods.py

Delete the permissions:

kubectl delete -f allow-default-to-list-pods.yaml

Verify that the pod can no longer list pods:

kubectl auth can-i --as=system:serviceaccount:default:default list pods
kubectl exec -it pod-with-default-sa -- python3 list_pods.py

Step 6: Create a restricted Service Account

While we can edit the permissions of the default Service Account, it may be desirable to do this with a custom, restricted Service Account that is associated with specific Pods.

Using a custom Service Account instead of the default Service Account provides several benefits:

  • Fine-grained permissions: With a custom service account, you can define specific permissions tailored to the needs of your application or pod, rather than relying on the broad permissions granted to the default service account.
  • Security: By limiting permissions to only what's necessary, you reduce the attack surface in case the service account is compromised.
  • Auditing and logging: Custom service accounts make it easier to track and monitor activities performed by specific applications or pods.
  • Resource isolation: Separate service accounts for different applications or teams help ensure that resources are accessed and used appropriately.
  • Role-based access control: Custom service accounts enable role-based access control, aligning with Kubernetes' RBAC model.
  • Avoiding broad permissions: The default service account often has broad permissions, which can be a security risk. Custom service accounts help avoid this.
  • Multi-tenancy: In multi-tenant clusters, custom service accounts help isolate and manage resources for different tenants.
  • Compliance: Custom service accounts can help meet compliance requirements by providing a clear audit trail and enforcing least-privilege access.

By using custom service accounts, you can implement a more secure, flexible, and maintainable Kubernetes environment.

Let's create a custom Service Account with restricted permissions. Create a YAML called restricted-service-account.yaml with the following contents:

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

Apply this YAML:

kubectl apply -f restricted-service-account.yaml

Verify that the my-restricted-service-account can list pods but only in the default namespace. The following 2 commands will print a yes:

kubectl auth can-i --as=system:serviceaccount:default:my-restricted-service-account list pods
kubectl auth can-i --as=system:serviceaccount:default:my-restricted-service-account list pods -n default

But my-restricted-service-account cannot list pods in the kube-system namespace:

kubectl auth can-i --as=system:serviceaccount:default:my-restricted-service-account list pods -n kube-system

Step 6: Updating the Pod to Use the Restricted Service Account

Modify the Pod YAML (pod.yaml) to use the new Service Account:

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-restricted-sa
spec:
  containers:
  - name: kubernetes-ipython
    image: kubernetes-ipython:latest
    imagePullPolicy: Never
    command: ["/bin/bash"]
    stdin: true
    tty: true
  serviceAccountName: my-restricted-service-account

Delete the old pod and recreate it:

kubectl delete -f pod.yaml
kubectl apply -f pod.yaml

Step 7: Running the Script with the Restricted Service Account

  1. Copy and run the script again:
    kubectl cp list_pods.py pod-with-restricted-sa:/app/list_pods.py
    kubectl exec -it pod-with-restricted-sa -- python /app/list_pods.py
    

This time, you should see an error message indicating that the Service Account doesn't have permission to list pods in all namespaces.

Step 8: Modifying the Script for Restricted Access

Update the Python script to only list pods in the default namespace:

from kubernetes import client, config

def list_pods():
    config.load_incluster_config()
    v1 = client.CoreV1Api()
    print("Listing pods with their IPs in the default namespace:")
    ret = v1.list_namespaced_pod(namespace="default")
    for i in ret.items:
        print(f"{i.status.pod_ip}\t{i.metadata.namespace}\t{i.metadata.name}")

if __name__ == "__main__":
    list_pods()

Run this updated script, and you should now be able to list pods in the default namespace only.

kubectl cp list_pods.py pod-with-restricted-sa:/app/list_pods.py
kubectl exec -it pod-with-restricted-sa -- python /app/list_pods.py

This is how Service Accounts in Kubernetes can be used to control access to cluster resources. We saw how the default Service Account often has broader permissions, while custom Service Accounts can be used to implement the principle of least privilege, granting only the necessary permissions to your workloads.

By using custom Service Accounts and RBAC rules, you can create fine-grained access controls for your Pods, ensuring that they have access only to the resources they need. This is a crucial aspect of maintaining a secure Kubernetes environment.

Step 9: Cleanup

Delete all resources created during this experiment:

kubectl delete -f pod.yaml 
kubectl delete -f restricted-service-account.yaml

Test Your Knowledge

No quiz available

Tags