Completed upto lesson 35
This commit is contained in:
60
lessons/0033-security-context-placement.html
Normal file
60
lessons/0033-security-context-placement.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lesson 33: SecurityContext placement</title>
|
||||
<link rel="stylesheet" href="../assets/course.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<p class="eyebrow">Lesson 0033 · 25 minutes · Minimize Microservice Vulnerabilities</p>
|
||||
<h1>Place hardening controls at the correct scope</h1>
|
||||
<p class="lede">A correct control at the wrong YAML level is still an invalid solution. Harden both containers while preserving the sidecar's one required writable path.</p>
|
||||
|
||||
<h2>Timed task: 15 minutes</h2>
|
||||
<p>Read <code>lab/lesson-33-fixture.yaml</code>. Create <code>lab/lesson-33-answer.yaml</code> from scratch. Preserve the namespace, Deployment name, replica count, selector, labels, container names, images, commands, and port.</p>
|
||||
<p>Your completed Deployment must:</p>
|
||||
<ul>
|
||||
<li>run the Pod as non-root UID and GID <code>1000</code>;</li>
|
||||
<li>apply <code>RuntimeDefault</code> seccomp to the whole Pod;</li>
|
||||
<li>disable privilege escalation in both containers;</li>
|
||||
<li>drop all Linux capabilities in both containers;</li>
|
||||
<li>make both container root filesystems read-only; and</li>
|
||||
<li>give only <code>log-exporter</code> a writable <code>emptyDir</code> mounted at <code>/var/cache/exporter</code>.</li>
|
||||
</ul>
|
||||
<p>Do not duplicate Pod-wide fields under each container, mount the writable volume into <code>api</code>, add capabilities, or change either image.</p>
|
||||
|
||||
<div class="callout"><strong>Placement reflex:</strong> identity and Pod-wide syscall policy belong under <code>template.spec.securityContext</code>. Privilege escalation, capabilities, and root-filesystem mutability belong under each entry in <code>template.spec.containers</code>.</div>
|
||||
|
||||
<h2>Verification</h2>
|
||||
<pre><code>kubectl apply --dry-run=client -f .\lab\lesson-33-answer.yaml
|
||||
kubectl apply -f .\lab\lesson-33-answer.yaml
|
||||
kubectl -n context-lab rollout status deployment/report-api --timeout=90s
|
||||
kubectl -n context-lab exec deploy/report-api -c log-exporter -- test -f /var/cache/exporter/events.log
|
||||
kubectl -n context-lab exec deploy/report-api -c api -- sh -c 'touch /root/blocked'</code></pre>
|
||||
<p>Expected: schema validation passes, the rollout completes, the sidecar writes its event file, and the API container cannot write to its root filesystem.</p>
|
||||
|
||||
<h2>Retrieval check</h2>
|
||||
<div class="quiz">
|
||||
<h3>Question</h3>
|
||||
<p>Where does <code>readOnlyRootFilesystem</code> belong?</p>
|
||||
<label><input type="radio" name="q" value="container">Inside each container's securityContext.</label>
|
||||
<label><input type="radio" name="q" value="podspec">Directly under the Pod specification.</label>
|
||||
<label><input type="radio" name="q" value="metadata">Inside the Pod template metadata.</label>
|
||||
<button onclick="check()">Check answer</button>
|
||||
<p id="feedback" class="feedback" aria-live="polite"></p>
|
||||
</div>
|
||||
|
||||
<h2>Documentation target</h2>
|
||||
<p>Use the official Kubernetes <a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/">security context documentation</a> after your first attempt. Use <code>kubectl explain deployment.spec.template.spec.securityContext</code> and <code>kubectl explain deployment.spec.template.spec.containers.securityContext</code> to confirm scope.</p>
|
||||
|
||||
<h2>What to send me</h2>
|
||||
<p>Save the YAML, then say <em>check</em>. I will list all issues and help you correct only one per round. Ask me a follow-up question whenever a field's scope is unclear.</p>
|
||||
<footer>Previous: <a href="0032-audit-success-vs-attempt.html">Audit success versus attempt</a> · CKS path: Minimize Microservice Vulnerabilities</footer>
|
||||
</main>
|
||||
<script>
|
||||
function check(){const c=document.querySelector('input[name=q]:checked'),f=document.getElementById('feedback');if(!c){f.textContent='Choose an answer.';f.className='feedback bad';return;}if(c.value==='container'){f.textContent='Correct. Root-filesystem mutability is configured per container.';f.className='feedback good';}else{f.textContent='Not quite. This control belongs in each container securityContext.';f.className='feedback bad';}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
70
lessons/0034-harden-and-isolate-orders-api.html
Normal file
70
lessons/0034-harden-and-isolate-orders-api.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lesson 34: Harden and isolate orders-api</title>
|
||||
<link rel="stylesheet" href="../assets/course.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<p class="eyebrow">Lesson 0034 · 35 minutes · Hard mode · Multi-domain</p>
|
||||
<h1>Harden and isolate a production API</h1>
|
||||
<p class="lede">This original performance task is deliberately denser than a focused drill. Secure the workload and its traffic boundary without changing its application contract.</p>
|
||||
|
||||
<h2>Task</h2>
|
||||
<p>Apply <code>lab/lesson-34-fixture.yaml</code>. Create <code>lab/lesson-34-answer.yaml</code> containing exactly two resources: the corrected <code>orders-api</code> Deployment and one NetworkPolicy named <code>orders-api-boundary</code>.</p>
|
||||
<p>Preserve the Deployment's name, namespace, replicas, selector, labels, ServiceAccount name, container names, images, commands, and port.</p>
|
||||
|
||||
<h2>Workload requirements</h2>
|
||||
<ul>
|
||||
<li>Disable automatic ServiceAccount-token mounting.</li>
|
||||
<li>Run the whole Pod as non-root UID and GID <code>10001</code>, and set Pod-level <code>fsGroup: 10001</code> so the non-root processes can write to their mounted volumes.</li>
|
||||
<li>Apply <code>RuntimeDefault</code> seccomp to the whole Pod.</li>
|
||||
<li>For both containers, disable privilege escalation, drop all capabilities, and make the root filesystem read-only.</li>
|
||||
<li>Create a shared <code>emptyDir</code> mounted at <code>/var/log/orders</code> in both containers.</li>
|
||||
<li>Create a separate <code>emptyDir</code> mounted at <code>/srv</code> only in <code>api</code>, allowing its preserved startup command to create the served file.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Network requirements</h2>
|
||||
<p>The single NetworkPolicy must select only <code>app=orders-api</code>, isolate both ingress and egress, and allow only:</p>
|
||||
<ul>
|
||||
<li>ingress from Pods labelled <code>app=edge-gateway</code> in namespace <code>edge</code>, TCP port <code>8080</code>;</li>
|
||||
<li>egress to Pods labelled <code>app=orders-db</code> in namespace <code>data</code>, TCP port <code>5432</code>; and</li>
|
||||
<li>egress to Pods labelled <code>k8s-app=kube-dns</code> in namespace <code>kube-system</code>, UDP and TCP port <code>53</code>.</li>
|
||||
</ul>
|
||||
<p>Use the immutable namespace label <code>kubernetes.io/metadata.name</code>. Do not use <code>ipBlock</code>, empty peer selectors, additional policies, or wildcard ports.</p>
|
||||
|
||||
<div class="callout"><strong>Hard-mode traps:</strong> a Namespace selector and Pod selector must be in the same peer item to mean AND. Separate list items mean OR. NetworkPolicies are additive, DNS needs both protocols, and a read-only root filesystem needs every required writable path mounted explicitly.</div>
|
||||
|
||||
<h2>Verification</h2>
|
||||
<pre><code>kubectl apply --dry-run=client -f .\lab\lesson-34-answer.yaml
|
||||
kubectl apply -f .\lab\lesson-34-answer.yaml
|
||||
kubectl -n orders-prod rollout status deployment/orders-api --timeout=90s
|
||||
kubectl -n orders-prod get deployment orders-api -o jsonpath='{.spec.template.spec.automountServiceAccountToken}{"\n"}'
|
||||
kubectl -n orders-prod exec deploy/orders-api -c api -- sh -c 'test -f /srv/index.html && touch /var/log/orders/api.log'
|
||||
kubectl -n orders-prod exec deploy/orders-api -c audit-writer -- test -f /var/log/orders/audit.log</code></pre>
|
||||
|
||||
<h2>Retrieval check</h2>
|
||||
<div class="quiz">
|
||||
<h3>Question</h3>
|
||||
<p>How do you require both a namespace label and a Pod label for one NetworkPolicy peer?</p>
|
||||
<label><input type="radio" name="q" value="same">Place both selectors inside the same peer list item.</label>
|
||||
<label><input type="radio" name="q" value="split">Place each selector inside a separate peer list item.</label>
|
||||
<label><input type="radio" name="q" value="policy">Place each selector inside a separate NetworkPolicy object.</label>
|
||||
<button onclick="check()">Check answer</button>
|
||||
<p id="feedback" class="feedback" aria-live="polite"></p>
|
||||
</div>
|
||||
|
||||
<h2>Documentation targets</h2>
|
||||
<p>After your first attempt, use the official Kubernetes <a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/">Security Context</a> and <a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">NetworkPolicy</a> documentation.</p>
|
||||
|
||||
<h2>What to send me</h2>
|
||||
<p>Save the two-resource answer and record your elapsed time. Then say <em>check</em>. I will list all issues and correct one per round. Ask a follow-up question whenever a requirement is ambiguous.</p>
|
||||
<footer>Previous: <a href="0033-security-context-placement.html">SecurityContext placement</a> · Public curriculum alignment: CKS v1.34</footer>
|
||||
</main>
|
||||
<script>
|
||||
function check(){const c=document.querySelector('input[name=q]:checked'),f=document.getElementById('feedback');if(!c){f.textContent='Choose an answer.';f.className='feedback bad';return;}if(c.value==='same'){f.textContent='Correct. Selectors in one peer are ANDed; separate peers are ORed.';f.className='feedback good';}else{f.textContent='Not quite. Keep both selectors in one peer to require both conditions.';f.className='feedback bad';}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
80
lessons/0035-apiserver-audit-hard-mode.html
Normal file
80
lessons/0035-apiserver-audit-hard-mode.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lesson 35: API server and audit hard mode</title>
|
||||
<link rel="stylesheet" href="../assets/course.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<p class="eyebrow">Lesson 0035 · 45 minutes · Hard mode · Control plane</p>
|
||||
<h1>Harden the API server and preserve forensic evidence</h1>
|
||||
<p class="lede">Repair an insecure static Pod and connect it to an ordered audit policy. Treat this as a control-plane maintenance task: preserve required configuration, make paths persistent, and avoid using <code>kubectl apply</code>.</p>
|
||||
|
||||
<h2>Task</h2>
|
||||
<p>Read <code>lab/lesson-35-kube-apiserver-insecure.yaml</code>. Create both:</p>
|
||||
<ul>
|
||||
<li><code>lab/lesson-35-kube-apiserver-answer.yaml</code>, a complete replacement static-Pod manifest; and</li>
|
||||
<li><code>lab/lesson-35-audit-policy.yaml</code>, the referenced audit policy.</li>
|
||||
</ul>
|
||||
<p>Preserve every metadata field, Pod field, container field, image, PKI mount, and command flag unless a requirement below explicitly changes it.</p>
|
||||
|
||||
<h2>API-server requirements</h2>
|
||||
<ul>
|
||||
<li>Disable anonymous authentication and profiling.</li>
|
||||
<li>Use authorization modes <code>Node,RBAC</code>.</li>
|
||||
<li>Enable ServiceAccount lookup.</li>
|
||||
<li>Preserve existing admission plugins and additionally enable <code>NodeRestriction</code>.</li>
|
||||
<li>Read the audit policy at <code>/etc/kubernetes/audit-policy.yaml</code>.</li>
|
||||
<li>Write audit events to <code>/var/log/kubernetes/audit/audit.log</code>.</li>
|
||||
<li>Retain audit logs for 14 days, keep 10 backups, and rotate at 100 MB.</li>
|
||||
<li>Mount host file <code>/etc/kubernetes/audit-policy.yaml</code> read-only at the identical container path using hostPath type <code>File</code>.</li>
|
||||
<li>Mount host directory <code>/var/log/kubernetes/audit</code> read-write at the identical container path using hostPath type <code>DirectoryOrCreate</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Audit-policy requirements</h2>
|
||||
<p>Use <code>audit.k8s.io/v1</code>, kind <code>Policy</code>, globally omit <code>RequestReceived</code>, and order rules so the first match produces:</p>
|
||||
<ul>
|
||||
<li><code>RequestResponse</code> for all Secret requests in namespaces <code>payments</code> and <code>orders-prod</code>;</li>
|
||||
<li><code>Metadata</code> for <code>create</code>, <code>update</code>, <code>patch</code>, <code>delete</code>, and <code>deletecollection</code> on Roles, RoleBindings, ClusterRoles, and ClusterRoleBindings;</li>
|
||||
<li><code>Request</code> for all <code>pods/exec</code> requests; and</li>
|
||||
<li><code>Metadata</code> for every remaining request.</li>
|
||||
</ul>
|
||||
<p>Do not add a <code>None</code> rule, omit any existing required flag, use a relative path, mount the audit log directory read-only, or apply the static manifest to the API server.</p>
|
||||
|
||||
<div class="callout"><strong>Hard-mode traps:</strong> audit rules are first-match wins; RBAC resources use group <code>rbac.authorization.k8s.io</code>; exec is resource <code>pods</code> with subresource <code>exec</code>, represented as <code>pods/exec</code>; and API-server file paths are useless unless the static Pod mounts their host locations.</div>
|
||||
|
||||
<h2>Verification</h2>
|
||||
<pre><code># These are configuration files, not objects to apply.
|
||||
grep -E -- '--anonymous-auth|--authorization-mode|--profiling|--service-account-lookup|--enable-admission-plugins|--audit-' lab/lesson-35-kube-apiserver-answer.yaml
|
||||
|
||||
# Confirm the two mount paths appear in commands, mounts, and hostPath volumes.
|
||||
grep -nE '/etc/kubernetes/audit-policy.yaml|/var/log/kubernetes/audit' lab/lesson-35-kube-apiserver-answer.yaml
|
||||
|
||||
# Inspect rule order and spelling.
|
||||
cat lab/lesson-35-audit-policy.yaml</code></pre>
|
||||
|
||||
<h2>Retrieval check</h2>
|
||||
<div class="quiz">
|
||||
<h3>Question</h3>
|
||||
<p>Why must the Secret rule appear before the catch-all Metadata rule?</p>
|
||||
<label><input type="radio" name="q" value="first">The first matching rule determines the recorded audit level.</label>
|
||||
<label><input type="radio" name="q" value="merge">All matching rules merge into one combined audit event.</label>
|
||||
<label><input type="radio" name="q" value="last">The final matching rule determines the recorded audit level.</label>
|
||||
<button onclick="check()">Check answer</button>
|
||||
<p id="feedback" class="feedback" aria-live="polite"></p>
|
||||
</div>
|
||||
|
||||
<h2>Documentation targets</h2>
|
||||
<p>After your first attempt, use the official Kubernetes <a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/">kube-apiserver flag reference</a> and <a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/">auditing documentation</a>.</p>
|
||||
|
||||
<h2>What to send me</h2>
|
||||
<p>Save both files and say <em>check</em>. I will list all detected issues and correct one per round. Ask a follow-up question whenever a flag, mount, or audit-rule match is unclear.</p>
|
||||
<footer>Previous: <a href="0034-harden-and-isolate-orders-api.html">Harden and isolate orders-api</a> · Public curriculum alignment: CKS v1.34</footer>
|
||||
</main>
|
||||
<script>
|
||||
function check(){const c=document.querySelector('input[name=q]:checked'),f=document.getElementById('feedback');if(!c){f.textContent='Choose an answer.';f.className='feedback bad';return;}if(c.value==='first'){f.textContent='Correct. A catch-all placed first would shadow every specific rule.';f.className='feedback good';}else{f.textContent='Not quite. Kubernetes stops at the first matching audit rule.';f.className='feedback bad';}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
73
lessons/0036-kube-bench-cis-triage.html
Normal file
73
lessons/0036-kube-bench-cis-triage.html
Normal file
@@ -0,0 +1,73 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lesson 36: kube-bench CIS triage</title>
|
||||
<link rel="stylesheet" href="../assets/course.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<p class="eyebrow">Lesson 0036 · 35 minutes · Tool drill · Cluster Setup</p>
|
||||
<h1>Triage kube-bench findings without obeying the scanner blindly</h1>
|
||||
<p class="lede">A scanner accelerates inspection; it does not replace component knowledge. Extract non-passing checks, verify them against configuration, identify uncovered components, and prioritize fixes by impact.</p>
|
||||
|
||||
<h2>Environment note</h2>
|
||||
<p>The current Windows shell has no Docker, WSL, Kubernetes CLI, or Linux node access. Use the provided kube-bench JSON report for this training attempt. Hero remains unavailable until you rerun kube-bench on a Linux control-plane and worker node.</p>
|
||||
|
||||
<h2>Task: 25 minutes</h2>
|
||||
<p>Analyze these files without modifying them:</p>
|
||||
<ul>
|
||||
<li><code>lab/lesson-36-kube-bench.json</code></li>
|
||||
<li><code>lab/lesson-35-kube-apiserver-insecure.yaml</code></li>
|
||||
<li><code>lab/lesson-36-etcd.yaml</code></li>
|
||||
<li><code>lab/lesson-28-kubelet-review.yaml</code></li>
|
||||
<li><code>lab/lesson-36-coredns.yaml</code></li>
|
||||
</ul>
|
||||
<p>Create <code>lab/lesson-36-answer.md</code> containing:</p>
|
||||
<ol>
|
||||
<li>The exact PowerShell command used to extract every non-PASS result from the JSON.</li>
|
||||
<li>A table for all seven FAIL results and the one WARN result: check ID, component, status, scanner evidence, configuration evidence, verdict, and precise remediation.</li>
|
||||
<li>Your first three remediations in operational priority order, with one sentence of justification each.</li>
|
||||
<li>At least four security findings from the CoreDNS Deployment that kube-bench did not report.</li>
|
||||
<li>One paragraph explaining why WARN does not mean FAIL, why PASS is not universal proof, and why CoreDNS requires manual review here.</li>
|
||||
<li>The exact Linux commands you would use to install pinned kube-bench v0.16.0, verify its checksum, run control-plane/etcd/node/policies targets as root, save JSON, and make CI return a nonzero code when checks fail.</li>
|
||||
</ol>
|
||||
|
||||
<div class="callout"><strong>Tool reflex:</strong> kube-bench has PASS, FAIL, WARN, and INFO states. A WARN commonly needs manual attention. A failed check can also reflect an execution or detection problem, so compare <code>actual_value</code> and <code>reason</code> with effective configuration before changing a node.</div>
|
||||
|
||||
<h2>PowerShell starting shape</h2>
|
||||
<pre><code>$report = Get-Content .\lab\lesson-36-kube-bench.json -Raw | ConvertFrom-Json
|
||||
$report.Controls.tests.results |
|
||||
Where-Object status -ne 'PASS' |
|
||||
Select-Object test_number, status, test_desc, actual_value, reason</code></pre>
|
||||
|
||||
<h2>Linux Hero rerun shape</h2>
|
||||
<pre><code># Run on a Linux node with access to component processes and files.
|
||||
sudo kube-bench run --targets master,etcd,node,policies \
|
||||
--json --outputfile kube-bench.json --exit-code 42</code></pre>
|
||||
<p>You must determine and record the complete pinned installation and checksum-verification commands yourself.</p>
|
||||
|
||||
<h2>Retrieval check</h2>
|
||||
<div class="quiz">
|
||||
<h3>Question</h3>
|
||||
<p>kube-bench reports WARN for a manual check. What should you do?</p>
|
||||
<label><input type="radio" name="q" value="verify">Inspect the effective configuration and record a manual verdict.</label>
|
||||
<label><input type="radio" name="q" value="fail">Treat the warning as a proven configuration failure immediately.</label>
|
||||
<label><input type="radio" name="q" value="ignore">Ignore the warning because only automated checks matter.</label>
|
||||
<button onclick="check()">Check answer</button>
|
||||
<p id="feedback" class="feedback" aria-live="polite"></p>
|
||||
</div>
|
||||
|
||||
<h2>Documentation targets</h2>
|
||||
<p>Use the official <a href="https://github.com/aquasecurity/kube-bench">kube-bench repository</a>, its <a href="https://github.com/aquasecurity/kube-bench/blob/main/docs/installation.md">installation guide</a>, and the applicable CIS benchmark guidance after your first analysis.</p>
|
||||
|
||||
<h2>What to send me</h2>
|
||||
<p>Save the Markdown answer and say <em>check</em>. I will list every issue and correct one per round. This attempt can advance CS-02 training, but the Hero tool gate requires a later real-node run.</p>
|
||||
<footer>Previous: <a href="0035-apiserver-audit-hard-mode.html">API server and audit hard mode</a> · Tracker: <a href="../CKS-MASTERY-CHECKLIST.md">CKS Mastery Checklist</a></footer>
|
||||
</main>
|
||||
<script>
|
||||
function check(){const c=document.querySelector('input[name=q]:checked'),f=document.getElementById('feedback');if(!c){f.textContent='Choose an answer.';f.className='feedback bad';return;}if(c.value==='verify'){f.textContent='Correct. WARN requires a manual, evidence-based verdict.';f.className='feedback good';}else{f.textContent='Not quite. The scanner has not proved either compliance or failure.';f.className='feedback bad';}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user