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]>
88 lines
2.9 KiB
Markdown
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.
|