completed lesson 31
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
Official Baseline and Restricted requirements. Use for: workload hardening and Pod Security Admission.
|
||||
- [Kubernetes auditing](https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/)
|
||||
Audit-policy concepts and event investigation. Use for: monitoring and incident-response tasks.
|
||||
- [Kubernetes Validating Admission Policy](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/)
|
||||
Official policy, binding, scoping, and CEL guidance. Use for: native admission-control drills.
|
||||
|
||||
## Wisdom (Communities)
|
||||
|
||||
|
||||
27
lab/lesson-31-answer.yaml
Normal file
27
lab/lesson-31-answer.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: ValidatingAdmissionPolicy
|
||||
metadata:
|
||||
name: security-admission
|
||||
spec:
|
||||
failurePolicy: Fail
|
||||
matchConstraints:
|
||||
resourceRules:
|
||||
- apiVersions: ["v1"]
|
||||
apiGroups: [""]
|
||||
operations: ["CREATE","UPDATE"]
|
||||
resources: ["pods"]
|
||||
validations:
|
||||
- expression: "object.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.privileged) || c.securityContext.privileged != true) && (!has(object.spec.initContainers) || object.spec.initContainers.all(c, !has(c.securityContext) || !has(c.securityContext.privileged) || c.securityContext.privileged != true))"
|
||||
message: "Privileged regular and init containers are not allowed"
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: ValidatingAdmissionPolicyBinding
|
||||
metadata:
|
||||
name: security-admission-binding
|
||||
spec:
|
||||
policyName: security-admission
|
||||
validationActions: [Deny]
|
||||
matchResources:
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
cks-training: admission
|
||||
11
lab/lesson-31-fixture.yaml
Normal file
11
lab/lesson-31-fixture.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: admission-lab
|
||||
labels:
|
||||
cks-training: admission
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: admission-control
|
||||
7
lab/lesson-32-audit.jsonl
Normal file
7
lab/lesson-32-audit.jsonl
Normal file
@@ -0,0 +1,7 @@
|
||||
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"b1","stage":"ResponseComplete","requestReceivedTimestamp":"2026-08-25T04:20:10Z","verb":"list","user":{"username":"system:serviceaccount:monitoring:collector"},"sourceIPs":["10.244.1.20"],"objectRef":{"resource":"pods","namespace":"checkout"},"responseStatus":{"code":200}}
|
||||
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"b2","stage":"ResponseComplete","requestReceivedTimestamp":"2026-08-25T04:21:05Z","verb":"create","user":{"username":"system:serviceaccount:default:unknown"},"sourceIPs":["10.244.4.19"],"objectRef":{"apiGroup":"rbac.authorization.k8s.io","resource":"clusterrolebindings","name":"unknown-admin"},"responseStatus":{"code":403,"status":"Failure"}}
|
||||
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"b3","stage":"ResponseComplete","requestReceivedTimestamp":"2026-08-25T04:22:11Z","verb":"create","user":{"username":"system:serviceaccount:checkout:release-bot"},"sourceIPs":["10.244.2.44"],"objectRef":{"apiGroup":"rbac.authorization.k8s.io","resource":"rolebindings","namespace":"checkout","name":"release-bot-secret-reader"},"responseStatus":{"code":201}}
|
||||
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"b4","stage":"ResponseComplete","requestReceivedTimestamp":"2026-08-25T04:22:18Z","verb":"list","user":{"username":"system:serviceaccount:checkout:release-bot"},"sourceIPs":["10.244.2.44"],"objectRef":{"resource":"secrets","namespace":"checkout"},"responseStatus":{"code":200}}
|
||||
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"b5","stage":"ResponseComplete","requestReceivedTimestamp":"2026-08-25T04:22:27Z","verb":"get","user":{"username":"system:serviceaccount:checkout:release-bot"},"sourceIPs":["10.244.2.44"],"objectRef":{"resource":"secrets","namespace":"checkout","name":"payment-gateway"},"responseStatus":{"code":200}}
|
||||
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"b6","stage":"ResponseComplete","requestReceivedTimestamp":"2026-08-25T04:22:51Z","verb":"create","user":{"username":"system:serviceaccount:checkout:release-bot"},"sourceIPs":["10.244.2.44"],"objectRef":{"resource":"pods","subresource":"exec","namespace":"checkout","name":"checkout-api-6d8f"},"responseStatus":{"code":101}}
|
||||
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"b7","stage":"ResponseComplete","requestReceivedTimestamp":"2026-08-25T04:24:00Z","verb":"get","user":{"username":"system:node:cks-worker"},"sourceIPs":["10.0.0.12"],"objectRef":{"resource":"nodes","name":"cks-worker"},"responseStatus":{"code":200}}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Validating Admission Policy retrieval
|
||||
|
||||
The learner completed Lesson 31 after iterative correction. They can now explain that a ValidatingAdmissionPolicy CEL expression returns `true` to admit a request, use `all()` to evaluate every regular and init container, guard optional fields with `has()`, combine compliance checks with short-circuit logic, and scope `Deny` enforcement through a ValidatingAdmissionPolicyBinding namespace selector.
|
||||
|
||||
## Evidence
|
||||
|
||||
The final manifest passes static review and includes a helpful denial message. Runtime admission outcomes remain unverified because `kubectl` was unavailable in the teaching shell.
|
||||
|
||||
## Retrieval target
|
||||
|
||||
Revisit CEL list macros and optional-field guards later without stepwise prompting. Continue reporting the complete issue list while correcting only one issue per review round.
|
||||
57
lessons/0031-cel-privileged-container-policy.html
Normal file
57
lessons/0031-cel-privileged-container-policy.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lesson 31: CEL privileged-container policy</title>
|
||||
<link rel="stylesheet" href="../assets/course.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<p class="eyebrow">Lesson 0031 · 25 minutes · Cluster Hardening</p>
|
||||
<h1>Reject privileged containers with native admission control</h1>
|
||||
<p class="lede">Write a policy and binding from memory, then prove both enforcement and namespace scope. This is a focused retrieval drill: no webhook and no third-party policy engine.</p>
|
||||
|
||||
<h2>Timed task: 15 minutes</h2>
|
||||
<p>Apply <code>lab/lesson-31-fixture.yaml</code>. Create <code>lab/lesson-31-answer.yaml</code> from scratch with exactly two resources:</p>
|
||||
<ul>
|
||||
<li>one <code>ValidatingAdmissionPolicy</code>; and</li>
|
||||
<li>one <code>ValidatingAdmissionPolicyBinding</code>.</li>
|
||||
</ul>
|
||||
<p>The policy must evaluate Pod <code>CREATE</code> and <code>UPDATE</code> requests. It must reject a Pod when any regular container or init container explicitly sets <code>securityContext.privileged: true</code>.</p>
|
||||
<p>The binding must enforce the policy with <code>Deny</code> only in namespaces labelled <code>cks-training=admission</code>. Use a helpful validation message. Do not reject containers that omit <code>securityContext</code> or omit <code>privileged</code>.</p>
|
||||
|
||||
<div class="callout"><strong>Retrieval target:</strong> optional fields are the trap. Your CEL must safely handle a missing <code>securityContext</code> and a missing <code>privileged</code> field. Remember that the policy defines logic and request matching; the binding supplies enforcement action and namespace scope.</div>
|
||||
|
||||
<h2>Verification</h2>
|
||||
<pre><code>kubectl apply -f .\lab\lesson-31-fixture.yaml
|
||||
kubectl apply -f .\lab\lesson-31-answer.yaml
|
||||
|
||||
kubectl -n admission-lab run allowed --image=nginx:1.27-alpine --restart=Never
|
||||
kubectl -n admission-lab run denied --image=nginx:1.27-alpine --restart=Never --overrides='{"spec":{"containers":[{"name":"denied","image":"nginx:1.27-alpine","securityContext":{"privileged":true}}]}}'
|
||||
kubectl -n admission-control run outside-scope --image=nginx:1.27-alpine --restart=Never --overrides='{"spec":{"containers":[{"name":"outside-scope","image":"nginx:1.27-alpine","securityContext":{"privileged":true}}]}}'</code></pre>
|
||||
<p>Expected: <code>allowed</code> is admitted, <code>denied</code> is rejected with your message, and <code>outside-scope</code> is admitted.</p>
|
||||
|
||||
<h2>Retrieval check</h2>
|
||||
<div class="quiz">
|
||||
<h3>Question</h3>
|
||||
<p>Where should the namespace selector and <code>Deny</code> action be configured?</p>
|
||||
<label><input type="radio" name="q" value="binding">In the policy binding, which scopes and enforces the policy.</label>
|
||||
<label><input type="radio" name="q" value="policy">In the policy validation, which replaces the binding entirely.</label>
|
||||
<label><input type="radio" name="q" value="namespace">In Namespace annotations, which execute the CEL expression directly.</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 <a href="https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/">Validating Admission Policy documentation</a> only after your first attempt. Inspect local schemas with <code>kubectl explain validatingadmissionpolicy.spec</code> and <code>kubectl explain validatingadmissionpolicybinding.spec</code>.</p>
|
||||
|
||||
<h2>What to send me</h2>
|
||||
<p>Save the answer, run the three checks, then say <em>check</em>. I will review only the first issue at a time. Ask me a follow-up question whenever a field or CEL expression is unclear.</p>
|
||||
<footer>Previous: <a href="0030-generate-image-sbom.html">Generate an image SBOM</a> · CKS path: Cluster Hardening</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==='binding'){f.textContent='Correct. The binding selects namespaces and chooses the enforcement action.';f.className='feedback good';}else{f.textContent='Not quite. The policy defines validation logic; the binding scopes and enforces it.';f.className='feedback bad';}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
58
lessons/0032-audit-success-vs-attempt.html
Normal file
58
lessons/0032-audit-success-vs-attempt.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lesson 32: Audit success versus attempt</title>
|
||||
<link rel="stylesheet" href="../assets/course.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<p class="eyebrow">Lesson 0032 · 20 minutes · Monitoring, Logging, and Runtime Security</p>
|
||||
<h1>Separate a failed attack from a successful compromise</h1>
|
||||
<p class="lede">Suspicious intent is not the same as successful impact. Reconstruct the incident from the authenticated principal, affected resources, timestamps, and HTTP response codes.</p>
|
||||
|
||||
<h2>Timed task: 12 minutes</h2>
|
||||
<p>Analyze <code>lab/lesson-32-audit.jsonl</code> with a structured JSON tool. Create <code>lab/lesson-32-answer.md</code> containing concise answers to these questions:</p>
|
||||
<ol>
|
||||
<li>Which suspicious RBAC request failed, who made it, and what evidence proves it failed?</li>
|
||||
<li>Which principal completed the successful attack sequence?</li>
|
||||
<li>What was the first successful escalation event? Include timestamp, verb, resource type, namespace, and resource name.</li>
|
||||
<li>Which credential-bearing object was accessed successfully?</li>
|
||||
<li>What runtime action followed?</li>
|
||||
<li>Give three immediate containment actions in operational order.</li>
|
||||
</ol>
|
||||
<p>Also include the exact command you used. Do not identify a principal by audit ID, and do not describe a rejected request as a completed privilege escalation.</p>
|
||||
|
||||
<div class="callout"><strong>Incident reflex:</strong> read <code>responseStatus.code</code> before claiming impact. Then pivot on <code>user.username</code>, source IP, and time. Contain the authority first, isolate execution second, and rotate exposed credentials after access is stopped.</div>
|
||||
|
||||
<h2>PowerShell starting point</h2>
|
||||
<pre><code>Get-Content .\lab\lesson-32-audit.jsonl |
|
||||
ForEach-Object { $_ | ConvertFrom-Json } |
|
||||
Select-Object requestReceivedTimestamp, verb,
|
||||
@{n='principal';e={$_.user.username}},
|
||||
@{n='code';e={$_.responseStatus.code}}, objectRef</code></pre>
|
||||
|
||||
<h2>Retrieval check</h2>
|
||||
<div class="quiz">
|
||||
<h3>Question</h3>
|
||||
<p>An audit event shows a suspicious RBAC create request with response code 403. What can you conclude?</p>
|
||||
<label><input type="radio" name="q" value="attempt">The request was attempted but this event does not prove the binding was created.</label>
|
||||
<label><input type="radio" name="q" value="success">The binding was created and the principal gained the requested permissions.</label>
|
||||
<label><input type="radio" name="q" value="unknown">The event contains no useful evidence about whether the request succeeded.</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/debug/debug-cluster/audit/">auditing documentation</a> after your first attempt.</p>
|
||||
|
||||
<h2>What to send me</h2>
|
||||
<p>Save the incident note, then say <em>check</em>. I will list all detected issues and help you correct only one per round. Ask me a follow-up question if any audit field is unclear.</p>
|
||||
<footer>Previous: <a href="0031-cel-privileged-container-policy.html">CEL privileged-container policy</a> · CKS path: Monitoring, Logging, and Runtime Security</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==='attempt'){f.textContent='Correct. HTTP 403 records a rejected attempt, not a successful RBAC change.';f.className='feedback good';}else{f.textContent='Not quite. Response code 403 means the API server rejected this request.';f.className='feedback bad';}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user