feat(curriculum): Phase 6C module upgrades, security assessment bank, ROOT 11 orientation
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]>
This commit is contained in:
+84
@@ -0,0 +1,84 @@
|
||||
# Externalizing Configuration: ConfigMaps & Secrets
|
||||
|
||||
## The problem: hardcoded configuration doesn't scale
|
||||
|
||||
If your application's database URL, log level, or feature flags are baked directly into its
|
||||
container image, changing any of them means rebuilding and redeploying the image — for a
|
||||
one-line config change. Worse, the SAME image can't be reused across dev/staging/production if
|
||||
each environment needs different settings compiled in.
|
||||
|
||||
Kubernetes solves this by letting you inject configuration into a container at RUNTIME,
|
||||
separately from the image itself — the same image, different config per environment.
|
||||
|
||||
## ConfigMap: configuration that isn't secret
|
||||
|
||||
A **ConfigMap** holds arbitrary key/value configuration data — log levels, feature flags, URLs,
|
||||
whole config files. It's a plain Kubernetes object, readable by anyone with API access to that
|
||||
Namespace, so it's for configuration that ISN'T sensitive.
|
||||
|
||||
Two ways to consume a ConfigMap in a Pod:
|
||||
|
||||
- **As environment variables** — each key becomes an env var the application reads normally.
|
||||
- **As a mounted volume** — each key becomes a FILE inside the container, useful for
|
||||
applications that expect to read configuration from disk rather than environment variables.
|
||||
|
||||
```yaml
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: app-env
|
||||
```
|
||||
|
||||
## Secret: the same idea, for sensitive data
|
||||
|
||||
A **Secret** has the identical shape and consumption pattern as a ConfigMap (env vars or a
|
||||
mounted volume) but is meant for sensitive values — passwords, API keys, tokens. Kubernetes
|
||||
stores Secret data base64-ENCODED, not encrypted — this is an important distinction:
|
||||
base64 is trivially reversible, so a Secret's real protection comes from Kubernetes' RBAC
|
||||
(who can even read Secret objects at all, ROOT 09) and, in a properly configured cluster,
|
||||
encryption at rest — not from the encoding itself.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: app-secret
|
||||
type: Opaque
|
||||
data:
|
||||
api-key: c3VwZXJzZWNyZXQ= # base64 of "supersecret" — NOT encrypted
|
||||
```
|
||||
|
||||
## Why the same mechanism, two different objects
|
||||
|
||||
You might ask why Kubernetes doesn't just have one "Config" object with a "sensitive" flag.
|
||||
Keeping them separate makes the DISTINCTION visible and enforceable — RBAC rules can grant
|
||||
"read ConfigMaps" without granting "read Secrets," and a quick `kubectl get configmaps` never
|
||||
accidentally exposes something that should have been a Secret. The separation is a deliberate
|
||||
safety boundary, not redundant API surface.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Demo plan:** Before the demonstration activity, it's worth showing `kubectl get secret
|
||||
app-secret -o yaml` live and pointing out the base64 value, then decoding it on the spot
|
||||
(`echo <value> | base64 -d`) — the "this isn't actually encrypted" realization lands much better
|
||||
seen than described.
|
||||
|
||||
**Likely misconceptions to watch for:**
|
||||
- Students very commonly assume Secrets are encrypted by Kubernetes automatically. Correct this
|
||||
explicitly and early — it's a genuine, consequential security misunderstanding.
|
||||
- The env-var vs. volume-mount choice is sometimes seen as arbitrary. Note one real
|
||||
consideration: env vars are visible via `kubectl describe pod` and process inspection tools
|
||||
inside the container more easily than a mounted file's contents — not a hard rule, but a
|
||||
reason some teams prefer volume mounts specifically for Secrets.
|
||||
|
||||
**Discussion question:** "If someone with `kubectl get secrets` access on your namespace can
|
||||
trivially read every Secret's real value, what is a Secret actually protecting you from?"
|
||||
(Answer: it protects against accidental exposure through normal channels like
|
||||
`kubectl get configmaps`, source control, or logs — not against a determined actor with direct
|
||||
API read access, which is exactly why RBAC restricting who has that access is the real control.)
|
||||
|
||||
**Optional extension:** ask what changes about this picture on a cluster with encryption-at-rest
|
||||
enabled for Secrets (answer: the ETCD-stored data itself becomes encrypted, protecting against
|
||||
someone who gains access to the underlying etcd storage directly — still separate from and in
|
||||
addition to RBAC controlling API-level access).
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
# Demonstration: A Secret Is Encoded, Not Encrypted
|
||||
|
||||
## Creating a Secret
|
||||
|
||||
```
|
||||
$ kubectl create secret generic app-secret --from-literal=api-key=supersecret
|
||||
secret/app-secret created
|
||||
```
|
||||
|
||||
## Looking at it
|
||||
|
||||
```
|
||||
$ kubectl get secret app-secret -o yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: app-secret
|
||||
data:
|
||||
api-key: c3VwZXJzZWNyZXQ=
|
||||
type: Opaque
|
||||
```
|
||||
|
||||
## Decoding it — no special permission needed beyond reading the Secret itself
|
||||
|
||||
```
|
||||
$ echo "c3VwZXJzZWNyZXQ=" | base64 -d
|
||||
supersecret
|
||||
```
|
||||
|
||||
Anyone who can run `kubectl get secret app-secret -o yaml` (i.e. anyone RBAC allows to READ
|
||||
Secrets in this Namespace) can trivially recover the real value with a standard, built-in
|
||||
command — no cracking, no special tooling. This is exactly why the lesson's point matters: the
|
||||
protection here is WHO can read the Secret object at all (RBAC), not the base64 encoding itself.
|
||||
|
||||
## Comparing to a ConfigMap
|
||||
|
||||
```
|
||||
$ kubectl get configmap app-config -o yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: app-config
|
||||
data:
|
||||
environment: production
|
||||
log_level: info
|
||||
```
|
||||
|
||||
A ConfigMap's values are stored in PLAIN TEXT — no encoding step at all. This is consistent with
|
||||
ConfigMaps being explicitly for non-sensitive data; there's no pretense of protection to begin
|
||||
with.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo script:** Run this live, actually decoding the value in front of the class — the gap
|
||||
between "Secret" (a name implying protection) and what the command output shows is the entire
|
||||
point, and it lands far better performed than read.
|
||||
|
||||
**Approximate timing:** 6-8 minutes live; 5 minutes self-paced.
|
||||
|
||||
**Common misconception to preempt:** this demonstration directly targets the single most common
|
||||
misconception about Kubernetes Secrets — do not skip it even if time is short, since a wrong
|
||||
belief here has real security consequences later.
|
||||
|
||||
**Optional extension:** ask what a `SealedSecret` or an external secrets manager (mentioned by
|
||||
name only, not built as a hands-on topic here) is trying to solve that a plain Kubernetes Secret
|
||||
does not — a good forward pointer without requiring new infrastructure in this course.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# Knowledge Check: Configuration & Secrets
|
||||
|
||||
---
|
||||
|
||||
**1. A teammate says "our database password is safe because it's stored as a Kubernetes
|
||||
Secret." What's missing from that statement?**
|
||||
|
||||
*Answer:* A Secret's data is base64-ENCODED, not encrypted — anyone who can read the Secret
|
||||
object at all can trivially decode it. The real protection is RBAC (who is allowed to read
|
||||
Secrets in that namespace) and, on a properly configured cluster, encryption at rest — not the
|
||||
Secret object type itself.
|
||||
|
||||
---
|
||||
|
||||
**2. Why does Kubernetes have two separate objects (ConfigMap and Secret) instead of one
|
||||
"Config" object with a sensitivity flag?**
|
||||
|
||||
*Answer:* Keeping them separate makes the distinction enforceable via RBAC — a role can grant
|
||||
"read ConfigMaps" without granting "read Secrets" — and makes it visible at a glance
|
||||
(`kubectl get configmaps` never risks accidentally listing something sensitive).
|
||||
|
||||
---
|
||||
|
||||
**3. An application needs its configuration to change without rebuilding its container image
|
||||
across dev/staging/production. Which Kubernetes mechanism solves this, and why not just bake
|
||||
different configs into three separate images?**
|
||||
|
||||
*Answer:* ConfigMaps (and Secrets, for sensitive values) injected at runtime via env vars or a
|
||||
mounted volume. Baking config into the image means maintaining three separate images for what
|
||||
should be identical application code — externalizing configuration lets the SAME image run
|
||||
correctly in every environment.
|
||||
|
||||
---
|
||||
|
||||
**4. True or false: mounting a ConfigMap as a volume and consuming it as environment variables
|
||||
are functionally identical from the application's perspective.**
|
||||
|
||||
*Answer:* False. An application reading environment variables needs code that reads env vars; an
|
||||
application expecting a config FILE on disk needs the volume-mount approach. The right choice
|
||||
depends on how the application itself is written to read configuration, not just personal
|
||||
preference.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 1 is the single most important takeaway from this module — confirm
|
||||
it's genuinely understood, not just correctly guessed, before moving to the Labs.
|
||||
|
||||
**Approximate timing:** 5-7 minutes.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Guided Exercise: ConfigMap, Secret, Env Var, or Volume?
|
||||
|
||||
Paper-and-thinking exercise before the Labs.
|
||||
|
||||
## The scenario
|
||||
|
||||
Your application needs four pieces of configuration:
|
||||
|
||||
1. A log level (`debug`/`info`/`warn`) that ops sometimes needs to flip without redeploying.
|
||||
2. A database password.
|
||||
3. A large JSON config file the application reads from a specific path at startup.
|
||||
4. A feature flag read once at container startup via an environment variable, per the
|
||||
application's existing code.
|
||||
|
||||
## Your task
|
||||
|
||||
For each, decide: ConfigMap or Secret, and env var or volume mount — then justify each choice.
|
||||
|
||||
## A worked answer to check yourself against
|
||||
|
||||
1. **ConfigMap, env var.** Not sensitive; the application likely already reads it as an env var
|
||||
(a common convention), and env vars are simple to change and trigger a rolling restart with.
|
||||
2. **Secret, either — but env var is common here too**, since most applications expect
|
||||
credentials via environment variables by convention. The KEY decision is Secret (sensitive),
|
||||
not the delivery mechanism.
|
||||
3. **ConfigMap, volume mount.** The application expects a FILE at a path, not an environment
|
||||
variable — volume mount is the only mechanism that presents a ConfigMap as an actual file.
|
||||
4. **ConfigMap (assuming not sensitive) or Secret (if it is), env var** — the application's
|
||||
EXISTING code dictates this; you don't get to choose a mechanism the application wasn't
|
||||
written to read.
|
||||
|
||||
## Self-check
|
||||
|
||||
If you chose "volume mount" for #4 or "env var" for #3, revisit the lesson — the deciding
|
||||
factor here isn't which mechanism seems more "correct" in the abstract, it's what the
|
||||
application actually expects. Getting this backwards means the application simply won't find
|
||||
its configuration at all.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Emphasize that #4's answer depends entirely on EXISTING application
|
||||
behavior — a good moment to note that platform engineers frequently have to adapt Kubernetes
|
||||
config delivery to match an application's assumptions, not the reverse.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live with discussion; 8 minutes self-paced.
|
||||
|
||||
**Optional extension:** ask what would happen if a ConfigMap consumed as a volume mount is
|
||||
UPDATED after the Pod is already running — a good bridge to the real (if not hands-on-tested
|
||||
here) behavior that mounted ConfigMap files update automatically after a short delay, while
|
||||
env-var-injected values do NOT update without a Pod restart.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Reflection: Configuration Is Infrastructure, Secrets Are a Trust Boundary
|
||||
|
||||
You just externalized configuration and handled a real Secret. Take a few minutes to connect
|
||||
this to production practice.
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, would you have assumed a Kubernetes Secret was encrypted? What's the
|
||||
real-world consequence of a team believing that when it isn't true?
|
||||
2. If your team's RBAC accidentally granted a low-trust role `get secrets` access across an
|
||||
entire namespace, what's actually exposed — and would your team necessarily notice?
|
||||
3. Why does separating configuration from the container image matter beyond convenience — think
|
||||
about what it enables for testing or incident response.
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**A misunderstood Secret is a false sense of security.** Teams that believe Secrets are
|
||||
encrypted by default sometimes skip additional controls (proper RBAC scoping, external secrets
|
||||
management, encryption at rest) that they'd otherwise recognize as necessary. The gap between
|
||||
perceived and actual protection is exactly where real incidents happen.
|
||||
|
||||
**Externalized configuration is what makes rollback and incident response fast.** If a bad
|
||||
config value ships (a wrong feature flag, an incorrect log level flooding storage), fixing it
|
||||
means updating a ConfigMap/Secret and letting Pods pick up the change — not rebuilding and
|
||||
redeploying an entire image. This is a direct, practical payoff of the separation this module
|
||||
taught, not just an architectural nicety.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 works well as a small-group exercise —
|
||||
have students actually list what a namespace's Secrets typically contain in a real application
|
||||
(DB creds, API keys, TLS certs) to make the exposure concrete.
|
||||
|
||||
**Approximate timing:** 8 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Optional extension:** ask students to draft a one-line policy statement their hypothetical
|
||||
team would adopt about who is allowed `get`/`list` access to Secrets in production namespaces.
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
# Demonstration: Tracing a Request Through Ingress
|
||||
|
||||
## The setup
|
||||
|
||||
An Ingress `web-ingress` routes `app.example.com/` to Service `web-svc`, which fronts a
|
||||
Deployment `web-app`. Nothing works yet from outside the cluster — let's see why, step by step.
|
||||
|
||||
## Step 1: is the Ingress object even doing anything?
|
||||
|
||||
```
|
||||
$ kubectl get ingress web-ingress
|
||||
NAME CLASS HOSTS ADDRESS PORTS AGE
|
||||
web-ingress traefik app.example.com 80 2m
|
||||
```
|
||||
|
||||
`ADDRESS` is empty. On a real cluster, this column fills in once the Ingress controller has
|
||||
actually processed the object and assigned it a reachable address — an empty address after a
|
||||
couple of minutes is the first sign to check the controller itself, not your YAML.
|
||||
|
||||
## Step 2: is the controller running at all?
|
||||
|
||||
```
|
||||
$ kubectl get pods -n kube-system -l app.kubernetes.io/name=traefik
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
traefik-7d8f9c6b5d-abcde 1/1 Running 0 10d
|
||||
```
|
||||
|
||||
Running fine — so the ADDRESS delay in this case is transient, not a missing controller. (If
|
||||
this returned no Pods at all, THAT would be the actual root cause — no controller means no
|
||||
Ingress object ever gets acted on, exactly like the demonstration in ROOT 07's Ingress lesson
|
||||
described.)
|
||||
|
||||
## Step 3: once ADDRESS populates, does the route actually work?
|
||||
|
||||
```
|
||||
$ kubectl get ingress web-ingress
|
||||
NAME CLASS HOSTS ADDRESS PORTS AGE
|
||||
web-ingress traefik app.example.com 10.0.1.50 80 3m
|
||||
|
||||
$ curl -H "Host: app.example.com" http://10.0.1.50/
|
||||
<html>...web-app's real response...</html>
|
||||
```
|
||||
|
||||
Success — but notice the `-H "Host: app.example.com"` header. Ingress routes by HOSTNAME, not
|
||||
just by IP — hitting the Ingress controller's address WITHOUT the matching Host header would
|
||||
route differently (or not at all), because that's literally what the Ingress rule is matching
|
||||
on.
|
||||
|
||||
## The general shape
|
||||
|
||||
Symptom (nothing reachable from outside) has at least three DIFFERENT possible layers: the
|
||||
Ingress object itself, the controller processing it, and the Service/Pod chain underneath it
|
||||
(already covered in ROOT 07's Services module). Check them in that order — Ingress ADDRESS,
|
||||
then controller health, then the underlying Service — rather than guessing at DNS or firewall
|
||||
issues first.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo script:** If a real Ingress controller is available in the demo environment, run this
|
||||
live including the Host-header nuance — students are frequently surprised that `curl <ip>`
|
||||
alone doesn't work the way they expect.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live; 5-7 minutes self-paced.
|
||||
|
||||
**Common misconception to preempt:** students often expect an Ingress's IP/address to work like
|
||||
a Service's ClusterIP (reachable directly, no extra header needed). Reinforce that Ingress
|
||||
routing is fundamentally HOSTNAME/PATH-based, which is exactly what makes one Ingress able to
|
||||
route many different domains through one entry point.
|
||||
|
||||
**Optional extension:** ask what would happen if TWO Ingress objects both claimed the same host
|
||||
+ path — a good forward pointer to why Ingress configuration conflicts are a real operational
|
||||
concern on a shared cluster, without needing to build a hands-on lab around it here.
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Guided Exercise: Planning Network Isolation
|
||||
|
||||
Paper-and-thinking exercise before the Lab.
|
||||
|
||||
## The scenario
|
||||
|
||||
A `database` Pod (label `app=database`) in the `netpol` namespace should only ever be reachable
|
||||
by Pods labeled `role=backend` — not the frontend, not anything else in the cluster.
|
||||
|
||||
## Your task
|
||||
|
||||
1. Before writing any YAML: right now, with NO NetworkPolicy in place, can the frontend reach
|
||||
the database? Why?
|
||||
2. What would a NetworkPolicy that ONLY specifies `podSelector: { matchLabels: { app: database
|
||||
} }` and no `ingress` rules at all do to traffic reaching the database — including from
|
||||
`role=backend` Pods?
|
||||
3. Sketch (in words, not full YAML) what the `ingress` rule needs to say to allow ONLY
|
||||
`role=backend` traffic in.
|
||||
4. If a new Pod is later created with `role=backend` in a DIFFERENT namespace, would your policy
|
||||
as sketched in #3 allow it? What would need to change if cross-namespace access should be
|
||||
allowed?
|
||||
|
||||
## A worked answer to check yourself against
|
||||
|
||||
1. Yes — by default, every Pod can reach every other Pod regardless of labels or namespace
|
||||
(ROOT 07's Ingress & NetworkPolicy lesson). Nothing restricts this until a policy exists.
|
||||
2. It would block ALL incoming traffic to the database, including from `role=backend` — an
|
||||
empty `ingress` list under a policy that selects the Pod means "no traffic is allowed in,"
|
||||
not "no policy applies."
|
||||
3. An `ingress` rule with `from: [{ podSelector: { matchLabels: { role: backend } } }]` — this
|
||||
allows traffic specifically from Pods carrying that label, and nothing else.
|
||||
4. Not automatically — a bare `podSelector` in the `from` clause matches Pods in the SAME
|
||||
namespace as the policy by default. Allowing a different namespace requires adding a
|
||||
`namespaceSelector` alongside the `podSelector` in the same `from` entry.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is the one most likely to trip students up — it directly reinforces
|
||||
the "empty ingress rules means deny-all, not no-op" point from the lesson, which is worth
|
||||
confirming actually landed before the Lab.
|
||||
|
||||
**Approximate timing:** 10-12 minutes live with discussion; 10 minutes self-paced.
|
||||
|
||||
**Optional extension:** ask what a security-minded team would ALSO want to restrict about this
|
||||
database Pod's OWN outbound (egress) traffic, beyond who can reach it — a light forward pointer
|
||||
to ROOT 09's security-control framing of NetworkPolicy, without requiring students to build an
|
||||
egress rule here.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Reflection: Reachability Should Be a Decision, Not a Default
|
||||
|
||||
You just configured real Ingress routing and real network isolation. Take a few minutes to
|
||||
connect this to production thinking.
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, would you have assumed a fresh Kubernetes cluster isolates Pods from
|
||||
each other by default? Has that changed?
|
||||
2. If your team runs a multi-tenant cluster (several unrelated applications sharing it), what's
|
||||
the risk of NEVER applying any NetworkPolicy at all?
|
||||
3. Ingress lets many different domains share one entry point. What's the operational benefit of
|
||||
that beyond just "fewer objects to manage"?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**"No NetworkPolicy" is not neutral — it's full reachability.** Exactly like an unhardened
|
||||
Pod's SecurityContext (ROOT 09 covers this in depth), the absence of a NetworkPolicy isn't a
|
||||
safe default; it's the MOST permissive configuration available. On any cluster where more than
|
||||
one team or application shares infrastructure, that's a real, often-overlooked exposure.
|
||||
|
||||
**Centralizing external entry points through Ingress reduces attack surface, not just object
|
||||
count.** Every separate LoadBalancer Service is a separate externally-reachable endpoint to
|
||||
secure, monitor, and patch. Routing through one (or a small number of) Ingress controllers means
|
||||
security controls — TLS termination, rate limiting, WAF rules — are configured and audited in
|
||||
one place instead of scattered across every application team's own Service definitions.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is worth pushing on with a concrete
|
||||
scenario — ask what a compromised low-value application (a demo app, a staging environment)
|
||||
could reach on a cluster with zero NetworkPolicies, versus one with sensible defaults.
|
||||
|
||||
**Approximate timing:** 8 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Optional extension:** ask students to sketch (in words) what a reasonable DEFAULT-DENY policy
|
||||
for an entire new namespace would look like, before any application-specific allow rules are
|
||||
added — previews the security-control framing ROOT 09 builds on.
|
||||
Reference in New Issue
Block a user