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]>
91 lines
4.1 KiB
Markdown
91 lines
4.1 KiB
Markdown
# 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.
|
|
|
|
```yaml
|
|
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.
|