Upgrades six PARTIAL modules to the gold-module standard (lesson/demonstration/ knowledge-check/guided-exercise/lab(s)/reflection), preserving every existing lab activity_id and scenario_id: root-06-m03 (jobs/cronjobs/daemonsets), root-07-m04 (ingress/networkpolicy), root-07-m02 (configuration/secrets, converted in place from a bare 5-lab bank), root-09-m02 (serviceaccounts/rbac), root-09-m03 (network/supply-chain security), root-10-m03 (troubleshooting framework). root-05-m01 (20-task kubectl-fluency bank) gets a minimal bookending lesson + reflection rather than a full redesign, since each task already carries its own explanation. root-12-m01, root-capstone-m01, and root-09-m04 are promoted to READY via documented exceptions (no new activities) where adding one would misrepresent their intentional design. Wires a new rootnode-security-assessment (exam, 60min, hint_policy disabled) using the existing Phase 3B assessment engine: 3 required hands-on CORE scenarios + 4 random-draw pools covering SecurityContext, RBAC, network policy, and the 7 previously-unwired CKS MCQs. Wires the previously-unwired readiness-probe-task scenario as an OPTIONAL practice lab in root-10-m01, using the existing is_required model. Adds root-11-m01-devops-platform-thinking — an honest, lesson-only ROOT 11 orientation module naming DevOps/platform engineering practices already exercised in ROOT 05-10/12, explicit that GitOps/CI-CD/IaC/observability need lab infrastructure this platform doesn't yet provide. No fake labs. Adds infra/scripts/validate-course-content.js: cross-checks the DB against scenario/content files for duplicate ids, orphans, invalid references, cycles, sequence collisions, instructor-notes leakage, and (new) self-paced content deferring explanation to a live instructor. 0 errors against the resulting 22 courses/modules, 163 activities, 98 scenario files. No application code changed — every gap was satisfiable with existing tables (course_modules, module_activities, assessment_definitions) and the existing content-delivery pipeline. Co-Authored-By: Claude Sonnet 5 <[email protected]>
3.1 KiB
Demonstration: Default-Deny, Before and After
Before: everything can reach everything
$ kubectl run test-pod --image=busybox --rm -it -- wget -qO- --timeout=2 http://backend-svc
<real response from backend>
Any Pod in the namespace can reach backend-svc, regardless of what it is or who deployed it —
this is the un-configured default the ROOT 07 lesson described, now made concrete from a
security angle.
Applying a default-deny policy
$ kubectl apply -f default-deny.yaml
networkpolicy.networking.k8s.io/default-deny created
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector: {}
policyTypes: ["Ingress"]
After: the same request, now blocked
$ kubectl run test-pod --image=busybox --rm -it -- wget -qO- --timeout=2 http://backend-svc
wget: download timed out
No error naming a permission (that's RBAC's job, a different layer) — just a timeout, because the packets are being dropped at the network layer before they ever reach the Pod. This is a DIFFERENT failure signature than an RBAC denial, worth being able to tell apart: RBAC denials are explicit and immediate; NetworkPolicy denials look like the destination is simply unreachable.
Explicitly allowing only what's needed
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
spec:
podSelector:
matchLabels: { app: backend }
ingress:
- from:
- podSelector:
matchLabels: { app: frontend }
$ kubectl run frontend-test --labels="app=frontend" --image=busybox --rm -it -- wget -qO- --timeout=2 http://backend-svc
<real response from backend>
$ kubectl run other-test --labels="app=other" --image=busybox --rm -it -- wget -qO- --timeout=2 http://backend-svc
wget: download timed out
Same destination, same request — the ONLY difference is the caller's label. This is least-privilege network access made concrete: reachability now reflects an explicit decision, not an accident of both Pods existing in the same cluster.
Instructor Notes
Demo script: Run all three states live if a real cluster is available — the timeout (not an error) for the denied case is worth calling out explicitly, since it looks identical to a genuinely broken/unreachable service from the caller's perspective.
Approximate timing: 8-10 minutes live; 5-7 minutes self-paced.
Common misconception to preempt: students sometimes expect a NetworkPolicy denial to produce a clear "forbidden" error like RBAC does. It doesn't — reinforce that a NetworkPolicy- caused timeout is genuinely hard to distinguish from an actual network/DNS problem without already suspecting NetworkPolicy, which is exactly why checking existing policies is a real diagnostic step worth knowing.
Optional extension: ask how a student would distinguish "blocked by NetworkPolicy" from "the backend Pod is actually down" purely from the caller's side (answer: check the backend Pod's own status/logs directly — if it's healthy and Ready, but still unreachable from one caller and reachable from another with a different label, NetworkPolicy is the likely cause).