Files
ced4568andClaude Sonnet 5 960a638ec9 feat(root): add Kubernetes security curriculum
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]>
2026-08-26 09:07:07 -05:00

88 lines
2.9 KiB
Markdown

# Demonstration: A Pod Before and After Hardening
## Before: the default, unhardened Pod
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-unhardened
spec:
containers:
- name: app
image: myapp:latest
```
Checking what this actually runs as:
```
$ kubectl exec app-unhardened -- id
uid=0(root) gid=0(root) groups=0(root)
$ kubectl exec app-unhardened -- touch /etc/anything-i-want
$ kubectl exec app-unhardened -- ls -la /etc/anything-i-want
-rw-r--r-- 1 root root 0 Jan 1 00:00 /etc/anything-i-want
```
Running as root, with a fully writable filesystem, including system directories. This is not a
misconfiguration — it's simply what happens when nothing restricts it.
## After: the same application, hardened
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-hardened
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
```
```
$ kubectl exec app-hardened -- id
uid=1000 gid=0 groups=0
$ kubectl exec app-hardened -- touch /etc/anything-i-want
touch: /etc/anything-i-want: Read-only file system
```
Same application image, same functionality (assuming the app doesn't itself require root or
filesystem writes it wasn't told about) — but a compromise of this container now can't write
to the filesystem, can't use the elevated capabilities that were dropped, and never had root to
begin with.
## What this actually buys you
Nothing here fixed a bug in `myapp:latest` — the image is identical in both cases. What changed
is what an attacker (or a bug behaving unexpectedly) could DO if something inside that container
went wrong. The unhardened version had almost no limits; the hardened version has several
independent ones, any of which alone would have blocked the `touch` command above.
## Instructor Notes
**Demo script:** Run both Pods live if time allows — watching `touch` succeed on one and fail
on the other in real time is far more convincing than reading it.
**Approximate timing:** 8-10 minutes live; 5-7 minutes self-paced.
**Common misconception to preempt:** students sometimes read the hardened Pod's failed `touch`
as "hardening broke something." Reinforce: the FUNCTIONALITY didn't change (the application
itself never needed to write to `/etc`) — only an unnecessary, unused capability was removed.
If hardening breaks something the application genuinely needs, that's real information about
the application, not a reason to skip hardening.
**Optional extension:** ask what would happen if `myapp:latest` were built assuming it could
write a cache file to `/tmp` inside the container — a good bridge to the fact that
`readOnlyRootFilesystem` often needs an explicit `emptyDir` volume mounted at the specific path
an application legitimately needs to write to (ROOT 08), rather than being an all-or-nothing
choice.