diff --git a/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/01-lesson-security-model.md b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/01-lesson-security-model.md new file mode 100644 index 0000000..0b418c8 --- /dev/null +++ b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/01-lesson-security-model.md @@ -0,0 +1,87 @@ +# Defense in Depth & the Kubernetes Security Model + +## Security is a posture, not a single setting + +There is no single "make it secure" switch in Kubernetes. Real security comes from **defense in +depth** — multiple independent layers, each reducing what an attacker (or a bug) can do if an +earlier layer fails. If a container has a vulnerability that lets an attacker execute arbitrary +code, the question that actually matters is: what can that code DO once it's running? A +container running as root, with every Linux capability, on a writable filesystem, with no +restrictions at all, can do far more damage than one deliberately hardened to need almost +nothing. + +## Least privilege: grant only what's actually needed + +The organizing principle behind everything in this course is **least privilege**: a workload +should have exactly the permissions and capabilities it needs to do its job, and nothing more. +This sounds obvious stated plainly, but the DEFAULT behavior of a plain container is closer to +"maximum privilege unless told otherwise" — root user, all standard Linux capabilities, a +writable filesystem. Hardening a Pod means deliberately narrowing that down. + +## SecurityContext: where you actually configure this + +`securityContext` (settable at the Pod level and per-container) is where you declare these +restrictions: + +```yaml +spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: app + securityContext: + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] +``` + +- **`runAsNonRoot` / `runAsUser`** — the container process runs as a specific non-root user + instead of root. If a container image was built to require root and you set this, it will + fail to start — which is a good, loud failure, not a silent security gap. Hardening + sometimes surfaces an image's OWN bad assumptions. +- **`capabilities: drop: ["ALL"]`** — Linux capabilities are fine-grained permissions + (`NET_BIND_SERVICE`, `SYS_ADMIN`, dozens more) a process can hold even without full root. + Dropping all of them, then adding back only the specific ones actually needed, is the + least-privilege pattern applied to capabilities specifically. +- **`readOnlyRootFilesystem: true`** — the container's own filesystem cannot be written to at + all (use an explicit `emptyDir` volume, ROOT 08, for anything that genuinely needs to write + temporary files). This closes off a whole category of attack that relies on writing a + malicious file to disk. + +## Why this matters even when "nothing is currently wrong" + +None of these settings fix a bug or a vulnerability — they limit the BLAST RADIUS if one exists +that you don't yet know about. A container hardened with non-root, dropped capabilities, and a +read-only filesystem gives a successful exploit dramatically less to work with than an +unhardened one, even against the exact same vulnerability. This is the entire point of defense +in depth: you don't rely on any single layer being perfect. + +## Instructor Notes + +**Approximate timing:** 15 minutes read; 10-12 minutes live with the demonstration. + +**Demo plan:** Have the demonstration activity ready to run right after this lesson — seeing a +concrete before/after Pod definition lands the abstract "least privilege" principle far better +than the lesson text alone. + +**Likely misconceptions to watch for:** +- Students sometimes think hardening settings are only relevant "if you expect to be attacked." + Reframe: defense in depth assumes SOMETHING will eventually go wrong somewhere (a + vulnerability, a bug, a misconfiguration) and prepares for that, rather than betting + everything on nothing ever going wrong. +- `runAsNonRoot: true` failing a Pod's startup is sometimes read as "the security setting is + broken." It's the opposite — it's correctly refusing to let a root-requiring image start, + which is valuable, actionable information about the image itself. + +**Discussion question:** "If dropping all capabilities breaks a container that turns out to +genuinely need one specific capability, what's the right fix — removing the drop, or something +more targeted?" (Answer: add back only that ONE specific capability rather than removing the +blanket drop — least privilege means "exactly what's needed," not "give up and grant +everything again.") + +**Optional extension:** ask students to identify one setting from this lesson that would have +made zero difference against a vulnerability that lets an attacker read environment variables +(a good check that they understand these are layered, not universal, protections — the answer +is that none of SecurityContext's settings here directly protect secrets stored in env vars; +that's a different concern, covered later in this course). diff --git a/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/02-demonstration-securitycontext.md b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/02-demonstration-securitycontext.md new file mode 100644 index 0000000..fe12824 --- /dev/null +++ b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/02-demonstration-securitycontext.md @@ -0,0 +1,87 @@ +# 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. diff --git a/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/03-knowledge-check.md b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/03-knowledge-check.md new file mode 100644 index 0000000..6ad1286 --- /dev/null +++ b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/03-knowledge-check.md @@ -0,0 +1,47 @@ +# Knowledge Check: Security Model & SecurityContext + +--- + +**1. A container is hardened with `capabilities: drop: ["ALL"]` and then fails because it +genuinely needs to bind to a low-numbered network port, which requires `NET_BIND_SERVICE`. +What's the least-privilege fix?** + +*Answer:* Add back only `NET_BIND_SERVICE` specifically (`capabilities: add: +["NET_BIND_SERVICE"]` alongside the drop-all), not remove the blanket drop entirely. +Least privilege means granting exactly what's needed, not reverting to granting everything. + +--- + +**2. True or false: setting `runAsNonRoot: true` fixes a vulnerability inside the application +itself.** + +*Answer:* False. It doesn't fix anything about the application's code — it limits what a +compromise of that code could DO, by ensuring the process never has root privileges to begin +with. This is the defense-in-depth distinction: reducing blast radius, not eliminating bugs. + +--- + +**3. Why might `runAsNonRoot: true` cause a Pod to fail to start entirely, and is that a +problem with the setting?** + +*Answer:* If the container image was built assuming it runs as root (e.g. it tries to bind a +privileged port, or write to a root-owned directory), the Pod will fail to start under +`runAsNonRoot`. This is NOT a problem with the setting — it's valuable, actionable information +that the image itself makes an assumption that should probably be fixed. + +--- + +**4. A container has `readOnlyRootFilesystem: true` and needs to write temporary cache files. +What's the correct way to allow that without abandoning the read-only filesystem entirely?** + +*Answer:* Mount an explicit `emptyDir` volume (ROOT 08) at the specific path the application +needs to write to. This keeps the rest of the filesystem read-only while allowing the one +legitimately-needed writable location. + +## Instructor Notes + +**Facilitation:** Question 1 and question 4 both reinforce the same "narrow the exception, don't +remove the protection" pattern — worth calling out that they're the same principle applied +twice. + +**Approximate timing:** 5-7 minutes. diff --git a/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/04-guided-exercise-hardening-plan.md b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/04-guided-exercise-hardening-plan.md new file mode 100644 index 0000000..837e4cf --- /dev/null +++ b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/04-guided-exercise-hardening-plan.md @@ -0,0 +1,51 @@ +# Guided Exercise: Planning a Pod Hardening Checklist + +Paper-and-thinking exercise before the Labs. + +## The scenario + +You're handed a Pod spec for a small internal API with NO `securityContext` set at all — the +fully-default, unhardened case from the demonstration. Your team lead asks you to harden it +before it ships. + +## Your task + +Without writing the actual YAML yet, answer these: + +1. List the settings you'd add, in the order you'd verify them (hint: there are at least three + distinct concerns from the lesson — user/root, filesystem, capabilities). +2. For each setting, name ONE way you'd notice if you'd broken the application by adding it + (what would the failure actually look like?). +3. If the application genuinely needs to write a cache file to disk, does that mean you should + skip `readOnlyRootFilesystem` entirely? What's the more precise fix? +4. Suppose after hardening, the container still runs fine but a security scan flags that it + still has the `NET_RAW` capability. What's the most likely reason, and what's the fix? + +## A worked answer to check yourself against + +1. In a reasonable order: (a) `runAsNonRoot`/`runAsUser` — a fundamental identity change, + surfaces image assumptions fastest; (b) `capabilities: drop: ["ALL"]` — narrow, usually safe + for a typical web API; (c) `readOnlyRootFilesystem: true` — most likely to need an + accompanying volume, so verify last once the others are confirmed working. +2. (a) Pod fails to start entirely (`CrashLoopBackOff` or an immediate error) if the image + assumed root. (b) The application errors on a specific operation that needed a capability + you dropped (e.g. failing to bind a privileged port). (c) A write operation fails at runtime + (a log line, an exception) the first time the app tries to write to disk. +3. No — the fix is mounting an explicit `emptyDir` volume at the specific path the cache needs, + keeping the rest of the filesystem read-only rather than abandoning the protection entirely. +4. Most likely: `capabilities: drop: ["ALL"]` was never actually applied (a YAML indentation + mistake, or it was added to the Pod's securityContext instead of the container's — capability + drops are container-level, not Pod-level). The fix is verifying the drop is actually present + on the CONTAINER's securityContext specifically. + +## Instructor Notes + +**Facilitation:** Question 4 is deliberately tricky — Pod-level vs. container-level +`securityContext` fields is a real, common source of "I set this and it didn't work" confusion +worth surfacing here before students hit it in a Lab. + +**Approximate timing:** 15 minutes live with discussion; 12-15 minutes self-paced. + +**Optional extension:** ask students to identify which of the settings in this exercise would +be MOST valuable to apply first if they could only add one to a large, existing fleet of +unhardened Pods under time pressure, and why. diff --git a/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/09-reflection.md b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/09-reflection.md new file mode 100644 index 0000000..34c5ce2 --- /dev/null +++ b/content/root/root-09-kubernetes-security/m01-security-model-and-securitycontext/09-reflection.md @@ -0,0 +1,42 @@ +# Reflection: Least Privilege Is a Default, Not an Afterthought + +You just hardened real Pods across several Labs — non-root, dropped capabilities, a read-only +filesystem, and a full SecurityContext review. Take a few minutes to connect this to real +practice. + +## Reflection questions + +1. Before this module, would you have described "runs as root with a fully writable + filesystem" as insecure, or just as "the default, unremarkable configuration"? Has that + changed? +2. None of the settings you configured fixed a bug in any application. What did they actually + change, precisely? +3. If your team had to choose between hardening every new Pod by default (as a policy) versus + hardening Pods only after an incident reveals a gap, which produces less total risk over + time, and why? + +## Real Ops takeaway + +**Unhardened is not neutral — it's the maximum-privilege default.** A container with no +SecurityContext configured isn't "unopinionated"; it's root, every capability, fully writable — +the MOST privileged configuration possible, not a safe middle ground. Teams that treat hardening +as optional are choosing maximum blast radius by default, usually without realizing they're +choosing anything at all. + +**Defense in depth means no single mistake should be catastrophic.** The value of stacking +non-root + dropped capabilities + read-only filesystem isn't that any ONE of them is perfect — +it's that a real compromise would have to defeat several independent restrictions simultaneously +instead of exploiting one gap that grants everything. This is exactly why security-minded teams +apply all of these by default rather than picking "the one that matters most." + +This module is complete. Continue to the next module. + +## Instructor Notes + +**Discussion questions for a live classroom:** question 3 is worth pushing on directly — it's +the actual business case for security-by-default, not just a technical preference. + +**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing. + +**Optional extension:** ask students to draft a one-paragraph "default Pod hardening policy" +their hypothetical team would adopt, based on what they've learned in this module. diff --git a/content/root/root-09-kubernetes-security/m02-serviceaccounts-and-rbac/01-lesson-serviceaccounts-rbac.md b/content/root/root-09-kubernetes-security/m02-serviceaccounts-and-rbac/01-lesson-serviceaccounts-rbac.md new file mode 100644 index 0000000..ff3519a --- /dev/null +++ b/content/root/root-09-kubernetes-security/m02-serviceaccounts-and-rbac/01-lesson-serviceaccounts-rbac.md @@ -0,0 +1,90 @@ +# 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. diff --git a/content/root/root-09-kubernetes-security/m02-serviceaccounts-and-rbac/02-knowledge-check.md b/content/root/root-09-kubernetes-security/m02-serviceaccounts-and-rbac/02-knowledge-check.md new file mode 100644 index 0000000..195d890 --- /dev/null +++ b/content/root/root-09-kubernetes-security/m02-serviceaccounts-and-rbac/02-knowledge-check.md @@ -0,0 +1,44 @@ +# Knowledge Check: ServiceAccounts & RBAC + +--- + +**1. A Pod is created with no ServiceAccount specified. What identity does it actually run as, +and what can that identity do by default?** + +*Answer:* The `default` ServiceAccount in its Namespace, which by default has essentially no +permissions — it can authenticate to the API but is granted nothing until an RBAC binding says +otherwise. + +--- + +**2. You create a Role granting `get`/`list` on Pods, but a workload still can't read Pods. +What's the most likely missing piece?** + +*Answer:* A RoleBinding (or ClusterRoleBinding) connecting that Role to the workload's actual +ServiceAccount. A Role by itself grants nothing to anyone — it has to be bound to an identity. + +--- + +**3. True or false: a ClusterRole can only ever be applied cluster-wide.** + +*Answer:* False. A ClusterRole can be bound via a RoleBinding to apply within just ONE +Namespace (reusing a cluster-wide-DEFINED permission set in a namespace-SCOPED way), or via a +ClusterRoleBinding to apply cluster-wide. The Role-type and Binding-type are independent +choices. + +--- + +**4. Why would a security review flag a Role granting `verbs: ["*"]` on `resources: ["*"]` +even if the workload "seems to work fine"?** + +*Answer:* That grant is root-equivalent API access — it violates least privilege regardless of +whether the workload happens to only use a small subset of it today. "Works fine" doesn't mean +"correctly scoped" — an over-broad grant is a real risk the moment that workload (or its +credentials) is compromised, whether or not it's ever exploited in practice. + +## Instructor Notes + +**Facilitation:** Question 4 is worth discussing even if everyone answers correctly — the "it +works, so what's the problem" instinct is exactly what least-privilege review exists to counter. + +**Approximate timing:** 5 minutes. diff --git a/content/root/root-09-kubernetes-security/m03-network-and-supplychain-security/01-lesson-network-supplychain.md b/content/root/root-09-kubernetes-security/m03-network-and-supplychain-security/01-lesson-network-supplychain.md new file mode 100644 index 0000000..73a6af1 --- /dev/null +++ b/content/root/root-09-kubernetes-security/m03-network-and-supplychain-security/01-lesson-network-supplychain.md @@ -0,0 +1,76 @@ +# NetworkPolicy as a Security Control & Supply Chain Trust + +## NetworkPolicy, revisited as a security posture + +ROOT 07 introduced NetworkPolicy architecturally: Kubernetes doesn't isolate Pod-to-Pod traffic +by default, and NetworkPolicy is how you declare intended reachability. Here, the same +mechanism is the point of a deliberate SECURITY practice: **default deny**. + +A default-deny policy blocks all traffic to selected Pods, and you then explicitly allow only +what's actually needed: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny +spec: + podSelector: {} # selects EVERY Pod in the namespace + policyTypes: ["Ingress"] +``` + +An empty `podSelector: {}` with no ingress rules means "select every Pod, allow nothing in" — +the network equivalent of the SecurityContext principle from earlier in this course: start +from nothing, add back only what's proven necessary. This is exactly the opposite of +Kubernetes' un-configured default (everything open), which is precisely why it has to be +declared explicitly rather than relying on any built-in setting. + +**Egress restriction** works the same way in reverse — restricting what a Pod can call OUT to, +not just what can call IN. A compromised Pod that can freely make outbound connections to +anything is far more dangerous (data exfiltration, calling out to an attacker's server) than one +restricted to only the specific destinations it legitimately needs. + +## Supply chain: trusting what you run before it ever starts + +Everything covered so far in this course assumes the container image itself is trustworthy. +Supply-chain security is about that assumption specifically: + +- **Image-pull secrets** — when pulling from a private registry, the cluster needs credentials + to authenticate. Storing and using these correctly (rather than, say, a publicly-readable + registry with no access control) is a basic supply-chain control: only images from a trusted, + access-controlled source get pulled at all. +- **Minimizing image footprint** (referenced here, not a hands-on Lab in this module) — a + smaller image means fewer packages, fewer known CVEs, and a smaller attack surface if the + image is ever compromised. + +## TLS at the Ingress layer: protecting data in transit + +Recall ROOT 07's Ingress lesson — routing external HTTP(S) traffic by hostname/path. Configuring +TLS termination at that same Ingress layer means traffic between users and your cluster is +encrypted, protecting against interception on the network path. This is a security concern +layered on top of the same architectural mechanism, the same relationship NetworkPolicy has to +its ROOT 07 introduction. + +## Instructor Notes + +**Approximate timing:** 15 minutes read; 10 minutes live. + +**Likely misconceptions to watch for:** +- Students sometimes think "default deny" means blocking ALL traffic including what the + application needs to function. Clarify: default deny is the STARTING point; you then add + specific allow rules for exactly what's needed — the goal state is fully functional AND fully + intentional, not non-functional. +- Egress restriction is sometimes seen as less important than ingress. Emphasize the + exfiltration angle explicitly — a compromised Pod's outbound reach is just as much a real + risk as inbound exposure. + +**Discussion question:** "Why is 'default deny, then explicitly allow' a better starting +posture than 'default allow, then explicitly block known-bad traffic'?" (Answer: default-deny +means a NEW, unanticipated attempt to reach or leave a Pod is blocked by default; default-allow +means anything not YET identified as bad gets through — you're always one step behind an +attacker under the second model.) + +**Optional extension:** ask what a real team would want to verify before trusting a +publicly-available container image from an unfamiliar source, beyond just "does it work" — a +good bridge into image scanning/provenance concepts (not built as a hands-on Lab in this course, +but worth naming as a real practice). diff --git a/content/root/root-09-kubernetes-security/m03-network-and-supplychain-security/02-knowledge-check.md b/content/root/root-09-kubernetes-security/m03-network-and-supplychain-security/02-knowledge-check.md new file mode 100644 index 0000000..dcf71e5 --- /dev/null +++ b/content/root/root-09-kubernetes-security/m03-network-and-supplychain-security/02-knowledge-check.md @@ -0,0 +1,45 @@ +# Knowledge Check: Network & Supply Chain Security + +--- + +**1. A NetworkPolicy has `podSelector: {}` with `policyTypes: ["Ingress"]` and no ingress rules +defined. What's the practical effect on every Pod in that namespace?** + +*Answer:* Every Pod is selected, and since no ingress rule allows anything, all incoming traffic +to every Pod is denied — this is the "default deny" starting posture, applied namespace-wide. + +--- + +**2. Why is restricting EGRESS (outbound) traffic from a Pod a real security control, not just +a mirror-image nicety of ingress restriction?** + +*Answer:* A compromised Pod with unrestricted egress can exfiltrate data to an attacker-controlled +destination or call out to download additional malicious payloads. Restricting egress to only +what's legitimately needed limits what a compromise can accomplish, independent of whether +inbound access was ever an issue. + +--- + +**3. True or false: "default deny, then explicitly allow" and "default allow, then explicitly +block known-bad traffic" provide equivalent security given the same set of rules.** + +*Answer:* False. Default-deny blocks anything NOT yet explicitly allowed, including new/ +unanticipated attempts. Default-allow only blocks what's already been identified as bad — an +attacker doing something not yet on the block list gets through under the second model but not +the first. + +--- + +**4. Why does minimizing a container image's footprint (fewer packages) improve supply-chain +security specifically, rather than just making the image smaller/faster to pull?** + +*Answer:* Fewer installed packages means fewer potential known vulnerabilities (CVEs) present in +the image at all, and a smaller attack surface if the image is ever compromised — it's a +security property, not merely a performance optimization. + +## Instructor Notes + +**Facilitation:** Question 3 is the core conceptual point of this module — worth confirming it's +genuinely understood, not just correctly guessed. + +**Approximate timing:** 5-7 minutes. diff --git a/content/root/root-09-kubernetes-security/m04-cluster-hardening-and-runtime/01-lesson-cluster-hardening.md b/content/root/root-09-kubernetes-security/m04-cluster-hardening-and-runtime/01-lesson-cluster-hardening.md new file mode 100644 index 0000000..e881fcd --- /dev/null +++ b/content/root/root-09-kubernetes-security/m04-cluster-hardening-and-runtime/01-lesson-cluster-hardening.md @@ -0,0 +1,64 @@ +# Pod Security Admission & a Survey of Runtime Hardening + +This module is deliberately a survey, not a deep dive — the topics here are real and important, +but lean toward cluster-operator/platform-team concerns rather than the daily-driver skills most +ROOT_ learners will use constantly. Treat the labs in this module as good-to-know, not required +mastery, before moving on. + +## Pod Security Admission: enforcing a baseline across a Namespace + +Everything you configured by hand in earlier modules (non-root, dropped capabilities, read-only +filesystem) can also be ENFORCED automatically at the Namespace level via **Pod Security +Admission (PSA)** — a label on the Namespace itself that rejects any Pod violating a chosen +security standard (`privileged`, `baseline`, or `restricted`, from least to most strict): + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: production + labels: + pod-security.kubernetes.io/enforce: restricted +``` + +This replaces the older PodSecurityPolicy (PSP) mechanism, which was removed from Kubernetes — +if you encounter PSP in older documentation or an older cluster, know that PSA is its current +replacement. + +## Immutable Secrets: preventing a class of accidental change + +A Secret can be marked `immutable: true`, which prevents its data from being changed after +creation (a new Secret with a new name has to be created instead, and workloads updated to +reference it). This trades a small amount of operational flexibility for a real safety +property: an accidental or malicious edit to a live Secret can't silently happen. + +## A survey of further topics (labs available, treat as optional depth) + +- **Audit policy** — configuring what the Kubernetes API server logs about requests made against + it (who did what, when) — genuinely important for incident investigation, but a cluster-admin + concern, not something most workload teams configure themselves. +- **RuntimeClass** — lets specific Pods run under a different container runtime configuration + (e.g. an extra-sandboxed runtime like gVisor or Kata Containers) for workloads that need + stronger isolation than the default runtime provides — relevant mainly for genuinely + untrusted or high-risk workloads, not typical applications. + +## Instructor Notes + +**Approximate timing:** 15 minutes read (shorter than other lessons — deliberately survey-depth). + +**Framing for instructors:** Be explicit with students that this module is intentionally +lighter than the earlier ones in this course — the goal is awareness ("this exists, here's +roughly what it's for") rather than hands-on fluency, consistent with not every CKS-style topic +belonging in the core learning path. + +**Discussion question:** "Why would a platform team prefer enforcing security standards via +Pod Security Admission at the Namespace level, instead of trusting every team to configure +SecurityContext correctly on every Pod by hand?" (Answer: PSA provides a hard backstop — +correctly-configured SecurityContext relies on every individual Pod author doing it right every +time; PSA rejects violations automatically regardless of whether any individual author +remembered to.) + +**Optional extension:** ask which of `privileged`/`baseline`/`restricted` a team should start a +brand-new, security-conscious project with, and why not jump straight to the strictest option +on a cluster already running older, unaudited workloads (a good practical judgment question +about migration risk, not just definitions).