Back to Kubernetes Course

Kubernetes Course

Authentication vs. Authorization

screenshot of the app

Every request to the Kubernetes API server goes through two distinct checks:

  • Authentication: "Who are you?" Kubernetes doesn't manage user accounts itself — it supports several authentication strategies (client certificates, static tokens, OIDC integration with an identity provider, or cloud-provider-specific IAM integration), but the outcome is always the same: a username and group memberships are attached to the request.
  • Authorization: "Are you allowed to do this?" This is where RBAC (Role-Based Access Control) comes in — the standard mechanism for controlling what an authenticated identity can do inside the cluster.

ServiceAccounts — Identity for Pods

Users authenticate as themselves, but Pods need their own identity to talk to the API server (for example, a controller Pod that needs to list other Pods). A ServiceAccount provides that identity.

apiVersion: v1 kind: ServiceAccount metadata: name: app-reader namespace: production
spec: serviceAccountName: app-reader containers: - name: app image: myapp:1.0

Every namespace has a default ServiceAccount automatically, which is used by any Pod that doesn't explicitly specify one — and by default it typically has no meaningful permissions, which is the safe starting point.


Roles and RoleBindings (Namespace-Scoped)

A Role defines a set of permissions — which actions (verbs) are allowed on which resource types — scoped to a single namespace:

apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader namespace: production rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"]

A RoleBinding grants that Role to a specific user, group, or ServiceAccount:

apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: read-pods-binding namespace: production subjects: - kind: ServiceAccount name: app-reader namespace: production roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io
  • apiGroups: [""] refers to the core API group (Pods, Services, ConfigMaps, Secrets, etc.). Other resources use named groups, e.g. apps for Deployments, batch for Jobs.
  • verbs are the allowed actions: get, list, watch, create, update, patch, delete, and a few others.
  • A Role only grants permissions within its own namespace, even if the same Role name exists in another namespace — they're entirely separate objects.

ClusterRoles and ClusterRoleBindings

For permissions that span all namespaces, or apply to cluster-scoped resources (like Nodes or PersistentVolumes), use ClusterRole and ClusterRoleBinding instead:

apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: node-viewer rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get", "list", "watch"]
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: node-viewer-binding subjects: - kind: User name: jane@example.com apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: node-viewer apiGroup: rbac.authorization.k8s.io

A ClusterRole can also be bound within a single namespace via a regular RoleBinding — a useful pattern for reusing one well-defined set of permissions (like a generic "edit" role) across many namespaces without redefining it each time.

kubectl auth can-i list pods --as=system:serviceaccount:production:app-reader kubectl get rolebindings,clusterrolebindings -A

Principle of Least Privilege

  • Never grant cluster-admin (the built-in, all-powerful ClusterRole) to a ServiceAccount unless absolutely necessary — it's equivalent to root on the entire cluster.
  • Avoid wildcard permissions (resources: ["*"], verbs: ["*"]) except for genuinely cluster-wide administrative tooling.
  • Give each application's ServiceAccount only the exact verbs and resources it needs, scoped to its own namespace whenever possible.

Pod Security — Restricting What Containers Can Do

Beyond RBAC (controlling API access), a separate layer of security controls what a running container is allowed to do on its node — the Pod Security Standards (which replaced the older, now-removed PodSecurityPolicy).

spec: securityContext: runAsNonRoot: true runAsUser: 1000 readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: ["ALL"]
  • runAsNonRoot: Refuses to start the container if its image would run as root.
  • readOnlyRootFilesystem: Mounts the container's own filesystem as read-only, forcing any writes to go to explicitly mounted volumes — this significantly limits what an attacker can do if the application is compromised.
  • allowPrivilegeEscalation: false combined with capabilities.drop: ["ALL"] removes Linux capabilities a typical application container never needs (like the ability to bind low ports as root or manipulate network interfaces).

At the namespace level, the built-in Pod Security Admission controller can enforce these baselines cluster-wide via a namespace label:

kubectl label namespace production pod-security.kubernetes.io/enforce=restricted

This rejects any Pod in that namespace that doesn't meet the restricted profile — one of the simplest, highest-leverage security controls available in a modern cluster.