From 73093b9328092e37c01e090a47677c42cb38ace1 Mon Sep 17 00:00:00 2001 From: Mosiur Rahman Date: Tue, 25 Aug 2026 15:12:00 +0600 Subject: [PATCH] completed lesson 31 --- RESOURCES.md | 2 + lab/lesson-31-answer.yaml | 27 +++++++++ lab/lesson-31-fixture.yaml | 11 ++++ lab/lesson-32-audit.jsonl | 7 +++ ...5-validating-admission-policy-retrieval.md | 11 ++++ .../0031-cel-privileged-container-policy.html | 57 ++++++++++++++++++ lessons/0032-audit-success-vs-attempt.html | 58 +++++++++++++++++++ 7 files changed, 173 insertions(+) create mode 100644 lab/lesson-31-answer.yaml create mode 100644 lab/lesson-31-fixture.yaml create mode 100644 lab/lesson-32-audit.jsonl create mode 100644 learning-records/0005-validating-admission-policy-retrieval.md create mode 100644 lessons/0031-cel-privileged-container-policy.html create mode 100644 lessons/0032-audit-success-vs-attempt.html diff --git a/RESOURCES.md b/RESOURCES.md index 09da359..2f57e54 100644 --- a/RESOURCES.md +++ b/RESOURCES.md @@ -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) diff --git a/lab/lesson-31-answer.yaml b/lab/lesson-31-answer.yaml new file mode 100644 index 0000000..e7edc0e --- /dev/null +++ b/lab/lesson-31-answer.yaml @@ -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 \ No newline at end of file diff --git a/lab/lesson-31-fixture.yaml b/lab/lesson-31-fixture.yaml new file mode 100644 index 0000000..a129fdd --- /dev/null +++ b/lab/lesson-31-fixture.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: admission-lab + labels: + cks-training: admission +--- +apiVersion: v1 +kind: Namespace +metadata: + name: admission-control diff --git a/lab/lesson-32-audit.jsonl b/lab/lesson-32-audit.jsonl new file mode 100644 index 0000000..d239781 --- /dev/null +++ b/lab/lesson-32-audit.jsonl @@ -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}} diff --git a/learning-records/0005-validating-admission-policy-retrieval.md b/learning-records/0005-validating-admission-policy-retrieval.md new file mode 100644 index 0000000..57129f8 --- /dev/null +++ b/learning-records/0005-validating-admission-policy-retrieval.md @@ -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. diff --git a/lessons/0031-cel-privileged-container-policy.html b/lessons/0031-cel-privileged-container-policy.html new file mode 100644 index 0000000..763f83e --- /dev/null +++ b/lessons/0031-cel-privileged-container-policy.html @@ -0,0 +1,57 @@ + + + + + + Lesson 31: CEL privileged-container policy + + + +
+

Lesson 0031 · 25 minutes · Cluster Hardening

+

Reject privileged containers with native admission control

+

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.

+ +

Timed task: 15 minutes

+

Apply lab/lesson-31-fixture.yaml. Create lab/lesson-31-answer.yaml from scratch with exactly two resources:

+ +

The policy must evaluate Pod CREATE and UPDATE requests. It must reject a Pod when any regular container or init container explicitly sets securityContext.privileged: true.

+

The binding must enforce the policy with Deny only in namespaces labelled cks-training=admission. Use a helpful validation message. Do not reject containers that omit securityContext or omit privileged.

+ +
Retrieval target: optional fields are the trap. Your CEL must safely handle a missing securityContext and a missing privileged field. Remember that the policy defines logic and request matching; the binding supplies enforcement action and namespace scope.
+ +

Verification

+
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}}]}}'
+

Expected: allowed is admitted, denied is rejected with your message, and outside-scope is admitted.

+ +

Retrieval check

+
+

Question

+

Where should the namespace selector and Deny action be configured?

+ + + + + +
+ +

Documentation target

+

Use the official Validating Admission Policy documentation only after your first attempt. Inspect local schemas with kubectl explain validatingadmissionpolicy.spec and kubectl explain validatingadmissionpolicybinding.spec.

+ +

What to send me

+

Save the answer, run the three checks, then say check. I will review only the first issue at a time. Ask me a follow-up question whenever a field or CEL expression is unclear.

+ +
+ + + diff --git a/lessons/0032-audit-success-vs-attempt.html b/lessons/0032-audit-success-vs-attempt.html new file mode 100644 index 0000000..701dbd2 --- /dev/null +++ b/lessons/0032-audit-success-vs-attempt.html @@ -0,0 +1,58 @@ + + + + + + Lesson 32: Audit success versus attempt + + + +
+

Lesson 0032 · 20 minutes · Monitoring, Logging, and Runtime Security

+

Separate a failed attack from a successful compromise

+

Suspicious intent is not the same as successful impact. Reconstruct the incident from the authenticated principal, affected resources, timestamps, and HTTP response codes.

+ +

Timed task: 12 minutes

+

Analyze lab/lesson-32-audit.jsonl with a structured JSON tool. Create lab/lesson-32-answer.md containing concise answers to these questions:

+
    +
  1. Which suspicious RBAC request failed, who made it, and what evidence proves it failed?
  2. +
  3. Which principal completed the successful attack sequence?
  4. +
  5. What was the first successful escalation event? Include timestamp, verb, resource type, namespace, and resource name.
  6. +
  7. Which credential-bearing object was accessed successfully?
  8. +
  9. What runtime action followed?
  10. +
  11. Give three immediate containment actions in operational order.
  12. +
+

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.

+ +
Incident reflex: read responseStatus.code before claiming impact. Then pivot on user.username, source IP, and time. Contain the authority first, isolate execution second, and rotate exposed credentials after access is stopped.
+ +

PowerShell starting point

+
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
+ +

Retrieval check

+
+

Question

+

An audit event shows a suspicious RBAC create request with response code 403. What can you conclude?

+ + + + + +
+ +

Documentation target

+

Use the official Kubernetes auditing documentation after your first attempt.

+ +

What to send me

+

Save the incident note, then say check. 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.

+ +
+ + +