Four new modules, moving ROOT 09 off PLANNED and building the 22-scenario CKS-style bank into a coherent course instead of disconnected exam tasks — see docs/ROOT_CURRICULUM_COVERAGE_MATRIX.md for the CORE/GUIDED/INDEPENDENT/ADVANCED/ASSESSMENT-BANK classification behind every placement. root-09-m01-security-model-and-securitycontext — READY, full gold-standard pattern: defense in depth, least privilege, hardening a Pod with SecurityContext. Lesson, demonstration (a Pod before/after hardening), knowledge check, guided exercise (a hardening checklist), real Labs (CORE: non-root/drop-capabilities/read-only-fs/ pod-security-context; GUIDED: seccomp; ADVANCED: AppArmor; INDEPENDENT: the generic pod-security-context duplicate), and a reflection. root-09-m02-serviceaccounts-and-rbac — PARTIAL: workload identity and least-privilege permissions. Lesson + knowledge check + real Labs. root-09-m03-network-and-supplychain-security — PARTIAL: NetworkPolicy as a security control (default-deny), image/registry trust, TLS ingress. Lesson + knowledge check + real Labs. root-09-m04-cluster-hardening-and-runtime — PARTIAL, deliberately survey-depth: Pod Security Admission and a lighter treatment of audit-policy/runtime-sandboxing topics that don't belong in the core daily-driver path. Co-Authored-By: Claude Sonnet 5 <[email protected]>
4.1 KiB
Identity for Workloads: ServiceAccounts & RBAC
Workloads need an identity too, separate from yours
When YOU run kubectl, you're authenticated as yourself. But a Pod running INSIDE the cluster
that needs to call the Kubernetes API (to list other Pods, read a ConfigMap, whatever) needs its
own identity — it can't and shouldn't use your personal credentials. That identity is a
ServiceAccount.
Every Pod runs as SOME ServiceAccount — if you don't specify one, it uses the default
ServiceAccount in its Namespace, which by default has essentially no permissions. A Pod's
ServiceAccount token is automatically mounted into the container unless you explicitly disable
that (automountServiceAccountToken: false) — worth doing for any Pod that never actually needs
to call the Kubernetes API at all, since an unused, mounted credential is still something an
attacker could steal and use.
RBAC: what that identity is actually allowed to do
Having an identity (a ServiceAccount) doesn't grant any permissions by itself — RBAC (Role-Based Access Control) is the separate system that defines what an identity can DO.
- A Role (namespace-scoped) or ClusterRole (cluster-wide) defines a set of permissions: which API resources, which verbs (get/list/watch/create/update/delete).
- A RoleBinding (or ClusterRoleBinding) connects a Role/ClusterRole to a specific identity (a ServiceAccount, a user, or a group) — the Role alone grants nothing to anyone until it's bound.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: ServiceAccount
name: my-app
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
This grants the my-app ServiceAccount permission to get/list Pods in the default
Namespace — and nothing else. It cannot delete Pods, cannot touch Secrets, cannot do anything
not explicitly listed.
Role vs. ClusterRole: scope matters
A Role only grants permissions within the ONE Namespace it's defined in. A ClusterRole can grant permissions across the entire cluster (or, bound via a RoleBinding instead of a ClusterRoleBinding, can also be reused namespace-by-namespace) — reach for a ClusterRole only when the workload genuinely needs cluster-wide visibility, not by default.
Least privilege, applied to permissions specifically
The same principle from the previous module applies directly here: grant the narrowest set of
verbs on the narrowest set of resources that the workload actually needs. A Role granting
get/list on Pods is very different from one granting * on * — the latter is
"root-equivalent" API access, and should be treated with exactly that level of caution.
Instructor Notes
Approximate timing: 15 minutes read; 10 minutes live.
Likely misconceptions to watch for:
- Students often conflate "ServiceAccount" with "user account for logging in." A ServiceAccount is specifically an identity for WORKLOADS, not a human login mechanism.
- The Role/RoleBinding two-step (define permissions, then separately grant them to someone) is sometimes seen as redundant. Emphasize the reuse benefit: the same Role can be bound to multiple identities without redefining the permission set each time.
Discussion question: "Why might you explicitly disable automounting a ServiceAccount token for a Pod that never calls the Kubernetes API?" (Answer: an unused but present credential is still something an attacker who compromises the container could steal and use elsewhere — removing it entirely removes that risk for a Pod that never needed it.)
Optional extension: ask what the practical difference is between granting a ClusterRole via a RoleBinding (scoped to one Namespace) versus a ClusterRoleBinding (cluster-wide) — a good check that students understand the Role/ClusterRole and Binding-type distinctions are independent axes, not the same choice.