Skip to content

Kubernetes Service Accounts Guide — Pod Identity and Access

DodaTech Updated 2026-06-24 9 min read

In this tutorial, you'll learn about Kubernetes Service Accounts Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

A Kubernetes Service Account provides an identity for pods to authenticate with the Kubernetes API server and access cloud provider resources, separate from user accounts.

What You'll Learn

You'll master Service Accounts — creating and managing accounts, token-based authentication, mounting credentials in pods, binding RBAC roles, automount control, and workload identity federation for cloud access.

Why This Problem Matters

Every pod that interacts with the Kubernetes API (operators, CI/CD runners, monitoring agents) needs authenticated identity. Using the default Service Account with excessive permissions is a security risk. Dedicated Service Accounts with minimal RBAC are essential for security.

Real-World Use

Doda Browser's CI/CD pipeline uses a dedicated Service Account for deployment operations. The account has permissions only to create/update Deployments and Services in specific namespaces. The default Service Account in the namespace has no API access.

Service Account Architecture

flowchart TB
  subgraph Kubernetes
    SA[ServiceAccount]
    Token[JSON Web Token]
    Secret[API Secret]
    Role[Role/ClusterRole]
    Binding[RoleBinding]
  end
  
  subgraph Pod
    App[Application]
    Volume[Token Volume
mounted at
/var/run/secrets/kubernetes.io/serviceaccount] end subgraph External K8sAPI[Kubernetes API] CloudAPI[Cloud Provider API
via Workload Identity] end SA --> Token Token --> Secret Secret --> Volume Volume --> App App -->|Authenticate via token| K8sAPI Role --> Binding Binding --> SA SA --> CloudAPI

Creating a Service Account

# service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: deploy-bot
  namespace: production
  annotations:
    description: "CI/CD deployment service account"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deploy-role
  namespace: production
rules:
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deploy-bot-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: deploy-bot
    namespace: production
roleRef:
  kind: Role
  name: deploy-role
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f service-account.yaml
kubectl get serviceaccount -n production deploy-bot

Expected output:

NAME         SECRETS   AGE
deploy-bot   0         10s

Using a Service Account in a Pod

apiVersion: v1
kind: Pod
metadata:
  name: ci-runner
  namespace: production
spec:
  serviceAccountName: deploy-bot
  automountServiceAccountToken: true
  containers:
    - name: runner
      image: alpine:3.20
      command:
        - sh
        - "-c"
        - |
          KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
          KUBE_CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
          KUBE_NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
          
          curl -s --cacert $KUBE_CA \
            -H "Authorization: Bearer $KUBE_TOKEN" \
            https://kubernetes.default.svc/api/v1/namespaces/$KUBE_NS/pods
      resources:
        limits:
          cpu: 100m
          memory: 128Mi

Python Client with Service Account

import os
from kubernetes import client, config

def get_in_cluster_client():
    config.load_incluster_config()
    return client.CoreV1Api()

def list_pods_with_sa(v1: client.CoreV1Api, namespace: str = None):
    ns = namespace or os.environ.get(
        "KUBERNETES_NAMESPACE", "default"
    )
    pods = v1.list_namespaced_pod(ns)
    return pods.items

def create_deployment_with_sa():
    config.load_incluster_config()
    apps = client.AppsV1Api()

    deploy = client.V1Deployment(
        metadata=client.V1ObjectMeta(
            name="sa-deployed-app",
            labels={"app": "sa-demo"}
        ),
        spec=client.V1DeploymentSpec(
            replicas=2,
            selector={"matchLabels": {"app": "sa-demo"}},
            template=client.V1PodTemplateSpec(
                metadata=client.V1ObjectMeta(
                    labels={"app": "sa-demo"}
                ),
                spec=client.V1PodSpec(
                    service_account_name="deploy-bot",
                    containers=[
                        client.V1Container(
                            name="app",
                            image="nginx]
                        )
                    ]
                )
            )
        )
    )
    apps.create_namespaced_deployment("production", deploy)

# Test that the SA can list pods (but should fail on namespaces)
v1 = get_in_cluster_client()
try:
    pods = list_pods_with_sa(v1, "production")
    print(f"Success! Found {len(pods)} pods in 'production'")
except client.exceptions.ApiException as e:
    print(f"Permission denied: {e.reason}")

try:
    pods = list_pods_with_sa(v1, "kube-system")
    print(f"Found {len(pods)} pods in 'kube-system'")
except client.exceptions.ApiException as e:
    print(f"Expected error: {e.reason}")

Expected output:

Success! Found 3 pods in 'production'
Expected error: Forbidden

The deploy-bot SA has access to pods in production (via RoleBinding), but not in kube-system.

Token Management

# Create a long-lived token secret (Kubernetes <1.24)
# In v1.24+, tokens are auto-generated by the TokenRequest API

# Manually create a token secret
kubectl create token deploy-bot -n production

# Create a token with expiration
kubectl create token deploy-bot -n production --duration=24h

# List token secrets
kubectl get secrets -n production | grep deploy-bot

Expected output:

deploy-bot-token-abc12   kubernetes.io/service-account-token   1      10s

Disabling Auto-Mount

# Disable globally for all pods in a namespace
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: sensitive
automountServiceAccountToken: false
---
# Disable per pod
apiVersion: v1
kind: Pod
metadata:
  name: no-sa
spec:
  automountServiceAccountToken: false
  containers:
    - name: app
      image: alpine
      command: ["sleep", "3600"]

Workload Identity (Cloud Provider)

# GKE Workload Identity
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gcs-reader
  namespace: data-pipeline
  annotations:
    iam.gke.io/gcp-service-account: gcs-reader-sa@project.iam.gserviceaccount.com
---
# AWS IAM Roles for Service Accounts (IRSA)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-writer
  namespace: data-pipeline
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/s3-writer-role
---
# Azure Workload Identity
apiVersion: v1
kind: ServiceAccount
metadata:
  name: blob-reader
  namespace: data-pipeline
  annotations:
    azure.workload.identity/client-id: 12345678-1234-1234-1234-123456789012

RBAC with Service Accounts

# Cluster-level permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-monitor
rules:
  - apiGroups: [""]
    resources: ["nodes", "persistentvolumes", "namespaces"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-monitor-binding
subjects:
  - kind: ServiceAccount
    name: monitoring-agent
    namespace: monitoring
roleRef:
  kind: ClusterRole
  name: cluster-monitor
  apiGroup: rbac.authorization.k8s.io

Common Mistakes

1. Using the Default Service Account for Everything

The default Service Account typically has minimal permissions, but many deployments leave it unmounted or with no explicit RBAC. Create dedicated Service Accounts per application with minimal permissions.

2. Overly Permissive ClusterRole

Binding a ClusterRole with wildcard permissions (*) to a Service Account defeats RBAC. Use specific resources and verbs. Grant only get, list, watch for read-only, not create, update, delete, patch.

3. Not Disabling Automount When Unnecessary

If your application doesn't use the Kubernetes API, disable automountServiceAccountToken: false. The mounted token is an attack vector if the container is compromised.

4. Using User Credentials in Pods

Setting KUBECONFIG or ~/.kube/config with user credentials inside a container violates security best practices. Always use Service Account tokens.

5. Hardcoding Namespace in Service Account Name

Service Accounts are namespaced. kubectl run --serviceaccount=deploy-bot uses the SA from the current namespace. Specify namespace explicitly: --namespace production.

6. Forgetting Token Rotation

Service Account tokens should be rotated regularly. In Kubernetes 1.24+, tokens are auto-generated and auto-expired. For older versions, manually rotate tokens or use a token controller.

7. No Audit on Service Account Usage

Without audit logging, you can't detect compromised Service Accounts. Enable audit logging in kube-apiserver and monitor for unusual API calls from Service Accounts.

Practice Questions

1. What is the difference between a user account and a Service Account?

User accounts are for humans (kubectl users, API consumers). Service Accounts are for applications (pods, automated processes). User accounts are global; Service Accounts are namespaced. User accounts require external authentication; Service Accounts are authenticated via tokens.

2. How does a pod authenticate to the Kubernetes API using a Service Account?

The token is automatically mounted at /var/run/secrets/Kubernetes.io/serviceaccount/token. The pod presents this token in the Authorization: Bearer header of API requests, along with the CA certificate from the same directory.

3. What happens to a Service Account token when the Service Account is deleted?

All tokens associated with the Service Account are deleted. Pods using that Service Account will lose API access but continue running. New API calls fail with 401 Unauthorized.

4. How do you grant a Service Account access to a specific cloud resource?

Use workload identity federation: annotate the Service Account with the cloud provider's IAM role (e.g., eks.amazonaws.com/role-arn for AWS, iam.gke.io/gcp-service-account for GCP). The cloud provider's OIDC integration exchanges the Kubernetes token for a cloud identity token.

5. Challenge: Design a least-privilege Service Account strategy for a platform team.

The team manages 50 Microservices across 3 environments (dev, staging, production). Each microservice needs specific API permissions. CI/CD pipelines need deployment permissions. Monitoring needs read-only access cluster-wide. Design a hierarchy of Service Accounts and RBAC bindings with the principle of Least Privilege.

Mini Project: Service Account Permission Tester

import os
import json
from kubernetes import client, config, watch

class SAPermissionTester:
    def __init__(self, namespace: str = "default"):
        self.namespace = namespace
        try:
            config.load_incluster_config()
            self.v1 = client.CoreV1Api()
            self.rbac = client.RbacAuthorizationV1Api()
        except config.ConfigException:
            self.v1 = None

    def test_permissions(self, sa_name: str) -> dict:
        results = {}

        # Check what RoleBindings reference this SA
        try:
            bindings = self.rbac.list_namespaced_role_binding(
                self.namespace
            )
            for binding in bindings.items:
                for subject in binding.subjects:
                    if (subject.kind == "ServiceAccount"
                        and subject.name == sa_name):
                        role_name = binding.role_ref.name
                        role = self.rbac.read_namespaced_role(
                            role_name, self.namespace
                        )
                        results["role"] = role_name
                        results["rules"] = [
                            {
                                "resources": r.resources,
                                "verbs": r.verbs,
                                "apiGroups": r.api_groups
                            }
                            for r in role.rules
                        ]
        except Exception as e:
            results["error"] = str(e)

        return results

    def display_permissions(self, sa_name: str):
        perms = self.test_permissions(sa_name)
        print(f"ServiceAccount: {sa_name}")
        print(f"Namespace: {self.namespace}")
        print(f"Bound Role: {perms.get('role', 'none')}")
        print("Permissions:")
        for rule in perms.get("rules", []):
            for resource in rule.get("resources", []):
                print(f"  {resource}: {', '.join(rule.get('verbs', []))}")

tester = SAPermissionTester("production")
# Simulate display without actual cluster
print("ServiceAccount: deploy-bot")
print("Namespace: production")
print("Bound Role: deploy-role")
print("Permissions:")
print("  deployments: get, list, watch, create, update, patch")
print("  statefulsets: get, list, watch, create, update, patch")
print("  services: get, list, watch, create, update")
print("  configmaps: get, list, watch, create, update")
print("  pods: get, list, watch")

Expected output:

ServiceAccount: deploy-bot
Namespace: production
Bound Role: deploy-role
Permissions:
  deployments: get, list, watch, create, update, patch
  statefulsets: get, list, watch, create, update, patch
  services: get, list, watch, create, update
  configmaps: get, list, watch, create, update
  pods: get, list, watch

FAQ

What is the difference between a Service Account token and a Kubernetes Secret?

A Service Account token is a JSON Web Token (JWT) used for authentication. In Kubernetes 1.24+, tokens are ephemeral and auto-generated via the TokenRequest API. The Secret (of type Kubernetes.io/service-account-token) was the traditional way to store tokens but is no longer auto-created for new Service Accounts.

Can a pod use multiple Service Accounts?

No. Each pod can only use one Service Account, specified in spec.serviceAccountName. However, the pod can have multiple containers, and the same token is mounted into all containers.

How do I grant a Service Account access to another namespace?

Use a RoleBinding in the target namespace that references the Service Account from its own namespace: subjects: [{ kind: ServiceAccount, name: my-sa, namespace: source-ns }]. The RoleBinding must be created in the target namespace.

What's Next

Kubernetes Metrics Server Guide
Kubernetes Namespaces & RBAC
Kubernetes Security Contexts

Congratulations on completing this Service Accounts guide! Here's where to go from here:

  • Practice daily — Create a dedicated Service Account for each application
  • Build a project — Set up workload identity federation for cloud resource access
  • Explore related topics — OIDC federation, token request API, Pod Identity, OPA Gatekeeper for SA validation
  • Join the community — Share your Service Account strategies and get feedback

Remember: every expert was once a beginner. Keep authenticating!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro