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:
- Using the
default
Service Account - 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
-
Copy the Python script into the pod:
kubectl cp list_pods.py pod-with-default-sa:/app/list_pods.py
-
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
- 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