commit c0da43e51ffdd1963ed571793a1760ff98d41c30 Author: Mosiur Rahman Date: Mon Sep 14 16:44:59 2026 +0600 CKS_FINALE diff --git a/ANSWER.md b/ANSWER.md new file mode 100644 index 0000000..0c98ad8 --- /dev/null +++ b/ANSWER.md @@ -0,0 +1,20 @@ +# CKS-020 — Protect super-admin credentials + +Domain: **2.3 Restrict Kubernetes API access** +Time: **1 minute** · Reward: **100 XP + topic mastery** + +## Task + +Secure `/etc/kubernetes/super-admin.conf`, currently `0644 root:root`: + +1. Back it up to `/root/super-admin.conf.backup`. +2. Set the live file to mode `0600`. +3. Verify the live file with `stat -c '%a %U:%G'`. + +## Section B — scored answer + +```bash +# Your three commands +``` + +Say **Grade CKS-020** when saved. diff --git a/CKS-SYLLABUS-CHECKLIST.md b/CKS-SYLLABUS-CHECKLIST.md new file mode 100644 index 0000000..9f41c5d --- /dev/null +++ b/CKS-SYLLABUS-CHECKLIST.md @@ -0,0 +1,264 @@ +# CKS Mastery Syllabus and Checklist + +Baseline: official six-domain curriculum for the current CKS exam. The Linux Foundation currently advertises a two-hour, performance-based command-line exam. Before exam day, re-check the official page for the Kubernetes version and environment changes. + +Status key: `[ ]` not assessed, `[~]` learning/remediation, `[x]` satisfactory on a fresh first attempt. + +## 0. Exam fluency and prerequisites + +- [ ] Switch and verify `kubectl` contexts before every task. +- [ ] SSH to the designated host and distinguish the base node from task hosts. +- [ ] Use `kubectl explain`, imperative generators, dry-run YAML, JSONPath, and `yq` efficiently. +- [ ] Edit YAML safely with Vim/Nano and validate before applying. +- [ ] Inspect static Pod manifests and wait for control-plane recovery. +- [ ] Use `crictl`, `systemctl`, `journalctl`, `ss`, `ps`, `find`, `openssl`, `sha256sum`/`sha512sum`, and text-processing tools. +- [ ] Back up files before mutation and verify every requested outcome. +- [ ] Search permitted documentation quickly without external search results. +- [ ] Apply time management: read all requirements, prioritize by weight, verify, and move on. + +## 1. Cluster Setup — 15% + +### 1.1 NetworkPolicy and cluster-level network restriction + +- [x] Explain default-allow behavior and policy isolation triggers. +- [x] Build namespace default-deny ingress, egress, and combined policies. +- [x] Select exact sources/destinations with `podSelector` and `namespaceSelector`. +- [x] Combine selectors correctly (AND within one peer; OR across peer entries). +- [x] Allow DNS with the minimum necessary egress. +- [x] Restrict ports and protocols without accidentally widening peer access. +- [x] Use `ipBlock` and `except`, including cloud metadata endpoint protection. +- [x] Account for reply traffic and CNI NetworkPolicy support. +- [x] Test allowed and denied flows from representative Pods. + +### 1.2 CIS benchmark review + +- [x] Run `kube-bench` for the correct node/component/configuration. +- [x] Interpret PASS, FAIL, WARN, and manual checks. +- [x] Map a failed control to kube-apiserver, controller-manager, scheduler, etcd, or kubelet configuration. +- [x] Remediate static Pod flags without breaking YAML or mounts. +- [x] Remediate kubelet configuration/systemd arguments and restart safely. +- [x] Verify component health and rerun the targeted benchmark check. + +### 1.3 Ingress TLS + +- [x] Inspect certificate subject/SAN, issuer, dates, and key/cert correspondence. +- [x] Create a TLS Secret in the correct namespace. +- [x] Configure `ingressClassName`, TLS hosts, rules, paths, path types, Services, and ports. +- [x] Distinguish TLS termination from upstream/mTLS encryption. +- [x] Verify HTTPS routing, SNI/Host behavior, certificate presentation, and HTTP exposure. + +### 1.4 Node metadata and endpoint protection + +- [x] Identify link-local metadata endpoints and the associated credential risk. +- [x] Block workload access to metadata while preserving required egress. +- [x] Recognize host-network and CNI limitations in the threat model. +- [x] Minimize exposure of kubelet, etcd, API server, dashboards, and other management endpoints. +- [x] Verify externally listening sockets and authentication requirements. + +### 1.5 Platform binary verification + +- [x] Determine the exact version and architecture required. +- [x] Download binaries and official checksum material from trusted locations. +- [x] Verify SHA-256/SHA-512 checksums before installation. +- [x] Detect mismatch and refuse deployment of unverified artifacts. +- [x] Check installed binary location/version and preserve permissions/ownership. + +## 2. Cluster Hardening — 15% + +### 2.1 Least-privilege RBAC + +- [x] Enumerate effective permissions with `kubectl auth can-i`, including impersonation. +- [x] Trace RoleBinding/ClusterRoleBinding subjects to Role/ClusterRole rules. +- [x] Distinguish namespaced and cluster-scoped resources. +- [x] Create minimal verbs, API groups, resources, subresources, and `resourceNames` rules. +- [x] Choose RoleBinding versus ClusterRoleBinding without unintended cluster-wide access. +- [x] Detect wildcards, privilege escalation, bind, impersonate, and secret-reading risks. +- [x] Repair overprivileged existing bindings while preserving required access. +- [x] Validate allowed and forbidden operations as the target identity. + +### 2.2 Service-account safety + +- [x] Disable token automounting at ServiceAccount and/or Pod level. +- [x] Create dedicated ServiceAccounts per workload. +- [x] Grant the smallest required RBAC permissions. +- [x] Understand projected, time-bound ServiceAccount tokens and TokenRequest. +- [x] Detect use of default ServiceAccounts and unnecessary API credentials. +- [x] Verify token absence/presence and effective permissions. + +### 2.3 Restrict Kubernetes API access + +- [ ] Harden API server authentication and anonymous access. +- [ ] Configure and reason about authorization modes and admission plugins. +- [ ] Protect API server network exposure and insecure endpoints. +- [ ] Protect kubeconfig files, client certificates, and bootstrap credentials. +- [ ] Inspect certificate identity/group mapping and expiration. +- [ ] Verify API access from authorized and unauthorized identities/locations. + +### 2.4 Secure Kubernetes upgrades + +- [ ] Read the version-skew policy and choose a supported upgrade path. +- [ ] Upgrade kubeadm control plane components in the safe order. +- [ ] Drain/uncordon and upgrade worker nodes safely. +- [ ] Preserve configuration and verify node/control-plane health. +- [ ] Recognize security risk from unsupported or vulnerable versions. + +## 3. System Hardening — 10% + +### 3.1 Minimize host OS footprint + +- [ ] Inventory running services, packages, processes, sockets, and startup units. +- [ ] Disable/remove unnecessary services and packages without harming Kubernetes. +- [ ] Reduce installed tooling and writable/executable attack surface. +- [ ] Verify removed exposure and required component health. + +### 3.2 Least-privilege host identity and access + +- [ ] Inspect users, groups, sudo rules, file ownership, and sensitive permissions. +- [ ] Restrict Kubernetes PKI, kubeconfigs, manifests, etcd data, and logs. +- [ ] Avoid running services as root where unnecessary. +- [ ] Recognize Linux capabilities and remove unnecessary capabilities. + +### 3.3 Minimize external network access + +- [ ] Enumerate listening TCP/UDP ports and owning processes. +- [ ] Distinguish required Kubernetes ports by node role. +- [ ] Stop/disable unauthorized listeners and validate closure. +- [ ] Apply host firewall controls where appropriate without partitioning the cluster. + +### 3.4 Kernel hardening: seccomp and AppArmor + +- [ ] Inspect seccomp/AppArmor availability and loaded profiles on the scheduled node. +- [ ] Apply `RuntimeDefault` and Localhost seccomp profiles with current API fields. +- [ ] Write/load an AppArmor profile and attach it with current API fields. +- [ ] Distinguish enforce, complain, unconfined, RuntimeDefault, and Localhost behavior. +- [ ] Schedule workloads only where required local profiles exist. +- [ ] Trigger a forbidden action and verify enforcement via status/logs. + +## 4. Minimize Microservice Vulnerabilities — 20% + +### 4.1 Pod Security Standards and security contexts + +- [ ] Distinguish Privileged, Baseline, and Restricted Pod Security Standards. +- [ ] Configure Pod Security Admission namespace labels for enforce/audit/warn and versions. +- [ ] Set non-root UID/GID, `runAsNonRoot`, and filesystem group deliberately. +- [ ] Disable privilege escalation and privileged mode. +- [ ] Drop all capabilities and add back only the minimum required. +- [ ] Set `readOnlyRootFilesystem` and provide minimal writable volumes. +- [ ] Restrict host namespaces, hostPath, host ports, proc mounts, devices, and unsafe sysctls. +- [ ] Validate admission behavior and runtime identity/capabilities. + +### 4.2 Secret management and encryption at rest + +- [ ] Avoid secrets in images, command arguments, environment dumps, and plain manifests. +- [ ] Create and consume Secrets with minimal exposure and RBAC. +- [ ] Mount Secret volumes read-only and understand update behavior. +- [ ] Inspect the API server encryption configuration and provider order. +- [ ] Configure authenticated encryption plus an intentional fallback provider. +- [ ] Mount the encryption configuration into the API server safely. +- [ ] Re-encrypt existing Secrets and verify ciphertext directly in etcd. +- [ ] Rotate keys/providers without making existing data unreadable. + +### 4.3 Workload isolation and multi-tenancy + +- [ ] Combine namespaces, RBAC, NetworkPolicy, quotas/limits, and Pod Security Admission. +- [ ] Identify cross-tenant risks from nodes, volumes, credentials, and cluster-scoped resources. +- [ ] Configure RuntimeClass for sandboxed runtimes such as gVisor/Kata when present. +- [ ] Apply RuntimeClass scheduling/overhead concepts where required. +- [ ] Verify the sandbox runtime rather than trusting Pod status alone. + +### 4.4 Pod-to-Pod encryption + +- [ ] Distinguish NetworkPolicy authorization from encryption in transit. +- [ ] Identify when Cilium WireGuard/IPsec or Istio mTLS is appropriate. +- [ ] Inspect encryption/mTLS status using tools supplied by the environment. +- [ ] Configure or repair workload participation and policies as requested. +- [ ] Verify encrypted traffic and avoid claiming encryption from policy alone. + +## 5. Supply Chain Security — 20% + +### 5.1 Minimal and reproducible images + +- [ ] Review Dockerfiles for oversized/untrusted bases, extra packages, shells, secrets, and root execution. +- [ ] Use minimal/distroless/multi-stage patterns appropriate to the workload. +- [ ] Pin images by digest or controlled version and understand mutability tradeoffs. +- [ ] Set a non-root user and minimize filesystem permissions/capabilities. +- [ ] Build and functionally test a hardened replacement image. + +### 5.2 Supply-chain understanding and SBOM + +- [ ] Map source, dependencies, build runner, registry, deployment, and runtime trust boundaries. +- [ ] Generate and inspect an SBOM with `bom` in the required format. +- [ ] Find unexpected/vulnerable components in an SBOM. +- [ ] Protect CI credentials and use isolated/ephemeral build workers. +- [ ] Recognize provenance, attestations, artifact repositories, and dependency pinning. + +### 5.3 Artifact and registry policy + +- [ ] Restrict permitted registries/repositories without bypass gaps. +- [ ] Understand signature/attestation verification at admission. +- [ ] Configure or repair supplied admission policy/webhook material. +- [ ] Use fail-closed behavior when explicitly required and evaluate availability tradeoffs. +- [ ] Test both allowed and denied images and inspect admission errors. + +### 5.4 Static workload and image analysis + +- [ ] Run Kubesec/KubeLinter or supplied static analyzers against manifests. +- [ ] Interpret findings and remediate the manifest, not merely silence the tool. +- [ ] Scan images with Trivy using requested severity and vulnerability filters. +- [ ] Distinguish OS-package and application-dependency vulnerabilities. +- [ ] Select a compliant image while preserving workload function. +- [ ] Record evidence of scan result and deployed digest/tag. + +## 6. Monitoring, Logging, and Runtime Security — 20% + +### 6.1 Behavioral analytics and Falco + +- [ ] Locate Falco configuration, rule files, service, and output logs. +- [ ] Understand rule, condition, output, priority, tags, macros, and lists. +- [ ] Use supported event fields to scope host/container/Kubernetes activity. +- [ ] Create a precise custom rule with low false-positive risk. +- [ ] Reload/restart safely, generate the event, and verify the alert. + +### 6.2 Threat detection across infrastructure and workloads + +- [ ] Investigate suspicious processes, shells, files, sockets, users, and containers. +- [ ] Correlate Kubernetes objects with container-runtime/process information. +- [ ] Detect crypto-mining, reverse shells, credential access, persistence, and unexpected tooling patterns. +- [ ] Preserve evidence and avoid destroying the signal before recording it. + +### 6.3 Attack investigation and attribution + +- [ ] Build a timeline from audit, runtime, host, and workload evidence. +- [ ] Identify initial access, execution, persistence, privilege escalation, credential access, lateral movement, and impact. +- [ ] Find the responsible identity, source address, user agent, object, command, and affected workload. +- [ ] Write exact requested findings to the required path/format. + +### 6.4 Runtime immutability + +- [ ] Enforce read-only root filesystems and provide only necessary writable mounts. +- [ ] Prevent privilege escalation and remove mutation tooling/capabilities. +- [ ] Understand immutable tags versus image digests and runtime filesystem immutability. +- [ ] Verify application health and failed unauthorized writes. + +### 6.5 Kubernetes audit logging + +- [ ] Write ordered audit-policy rules with appropriate levels and stages. +- [ ] Scope users, groups, verbs, namespaces, resources, subresources, and non-resource URLs. +- [ ] Configure API server audit policy/log flags and host mounts. +- [ ] Set log rotation parameters and file permissions. +- [ ] Recover the API server and verify audit event generation. +- [ ] Query audit JSON efficiently to answer forensic questions. + +## 7. Legacy awareness — not a current mastery gate + +- [ ] Recognize PodSecurityPolicy as removed and translate old exercises to Pod Security Admission/security contexts. +- [ ] Recognize deprecated annotations or beta API versions and rewrite them using current fields/APIs. +- [ ] Treat community-repository exam prices, versions, weights, and question counts as historical unless confirmed officially. + +## Progress summary + +- Current domain: Cluster Hardening +- Current topic: Restrict Kubernetes API access +- Active question: CKS-020 +- Topic first-attempt mastery streak: 0 +- Completed mastery topics: 7 (Service-account safety completed with direct assistance; verification was skipped) diff --git a/MISSION.md b/MISSION.md new file mode 100644 index 0000000..d6ede05 --- /dev/null +++ b/MISSION.md @@ -0,0 +1,20 @@ +# Mission: Certified Kubernetes Security Specialist (CKS) + +## Why +Pass the CKS exam and develop security skills strong enough to secure and investigate real Kubernetes clusters under severe time pressure. + +## Success looks like +- Complete difficult, hands-on CKS tasks accurately from a Linux terminal. +- Diagnose and correct security mistakes without being shown the solution. +- Solve a fresh, harder-than-CKS question in one attempt for every curriculum topic. +- Navigate only exam-permitted documentation efficiently. + +## Constraints +- Use one question at a time. +- The learner writes every solution; the teacher never completes an active task. +- After an imperfect attempt, address one mistake at a time with progressively stronger hints. +- A topic is satisfactory only after a fresh question is solved correctly on the first attempt. + +## Out of scope +- Exam dumps, recalled confidential questions, and unauthorized exam assistance. +- Memorizing obsolete Kubernetes APIs as if they were current exam objectives. diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..09464aa --- /dev/null +++ b/NOTES.md @@ -0,0 +1,42 @@ +# Teaching notes + +- Prefer practical, scenario-based tasks that are harder than the real CKS exam. +- Present exactly one active question in `ANSWER.md`. +- Make answering easy with explicit headings, checkboxes, and fenced blocks. +- Do not reveal or implement the solution to an active question. +- Review only what the learner wrote in `ANSWER.md`. +- On an imperfect submission, create a checklist of observed mistakes, but expose and coach only one mistake at a time. +- Hint ladder: (1) goal-oriented nudge, (2) concept pointer, (3) narrow official-doc reference, (4) command/API-shape clue, (5) near-complete scaffold with blanks. Never fill the final answer. +- Score after the submitted answer is functionally complete, then require a fresh transfer question before declaring the topic satisfactory if any coaching was needed. +- One-attempt success on a fresh question means satisfactory; otherwise continue the same topic with a new question after remediation. +- Interleave older mastered skills into later questions for retention. +- Keep questions compact: one focused operation, minimal prose, and a short Section B answer area. +- For configuration-edit tasks, provide the complete realistic exercise file (or a clearly labeled complete exam-supplied file), not only the line to change. The learner should practice locating the field and preserving structure/indentation. + +## Mastery and scoring protocol + +1. Learner edits `ANSWER.md` and says the attempt is ready. +2. Teacher evaluates the answer against an unpublished functional rubric. +3. If incomplete, teacher records all detected issues internally but presents only the first unresolved mistake. +4. Learner fixes that mistake in the file. Teacher then verifies it and reveals the next mistake, if any. +5. Only Section B (the implementation/manifests) is scored, out of 100: + - Functional correctness against the requested end state: 70 + - Security and least privilege: 20 + - Manifest/API precision: 10 + Sections A, C, and D are checked for correctness and used for feedback, but contribute zero points. +6. If the original attempt earned at least 90 and required no correction, mark the topic satisfactory. +7. If correction or hints were needed, give a new, harder transfer question in the same topic. Only a first-attempt score of at least 90 with no critical security error unlocks the next topic. +8. Attempts are counted per question; topic status is tracked in `CKS-SYLLABUS-CHECKLIST.md`. + +## Gamification + +- Clean first-attempt completion: 100 XP. +- Completion after remediation: 40 XP. +- Topic mastery bonus: 150 XP. +- Skipped question: 0 XP. +- Ranks: Cadet (0), Scout (100), Defender (250), Sentinel (500), Guardian (900), CKS Elite (1400). +- Keep `dashboard.html` synchronized whenever a question is graded, completed, skipped, or unlocked. +- Ignore harmless spelling/formatting slips when intent and executability remain unambiguous. Still flag errors that break a command, invalidate YAML, change security behavior, or omit a required outcome. +- Learner reports strong confidence with Ingress and TLS but frequently forgets or mistypes NGINX Ingress annotations; interleave annotation recall later. +- Never skip, archive as incomplete, or advance past an active question unless the learner explicitly uses the word `skip` or clearly says to move to the next topic. Treat `go`, `check`, `verify`, and similar short messages as requests to continue checking the current answer. +- Keep tasks close to realistic CKS execution complexity. Do not require defensive shell control flow (`if`, elaborate `&&` chains, scripting) when a candidate can run a verification command, observe its result, and then perform the next command. Hardness should come from Kubernetes security judgment, not incidental shell puzzles. diff --git a/RESOURCES.md b/RESOURCES.md new file mode 100644 index 0000000..f1d776a --- /dev/null +++ b/RESOURCES.md @@ -0,0 +1,39 @@ +# CKS Resources + +## Knowledge + +- [Linux Foundation: CKS certification and current domains](https://training.linuxfoundation.org/certification/certified-kubernetes-security-specialist/) + Authority for domain weights, prerequisites, current exam version, duration, and performance-based format. +- [CNCF certification curriculum repository](https://github.com/cncf/curriculum) + Authority for the published CKS curriculum. Check for version changes before changing the syllabus. +- [Linux Foundation: Important CKS instructions](https://docs.linuxfoundation.org/tc-docs/certification/important-instructions-cks) + Authority for task/host rules, exam environment, installed tools, and operational constraints. +- [Linux Foundation: Resources allowed during CKS](https://docs.linuxfoundation.org/tc-docs/certification/certification-resources-allowed) + Authority for documentation permitted in the exam VM. +- [Kubernetes documentation](https://kubernetes.io/docs/) + Primary reference for Kubernetes APIs, security concepts, administration, and tasks; permitted during the exam. +- [Falco documentation](https://falco.org/docs/) + Primary reference for runtime detection and Falco rules; permitted during the exam. +- [etcd documentation](https://etcd.io/docs/) + Primary reference for etcd operation and verification; permitted during the exam. +- [Kubernetes SIGs BOM CLI reference](https://kubernetes-sigs.github.io/bom/cli-reference/) + Primary reference for SBOM generation; permitted during the exam. +- [Techiescamp CKS certification guide](https://github.com/techiescamp/cks-certification-guide) + Community explanations and command examples. Use as practice input, not as the authority for current exam metadata. +- [Moabukar CKS exercises](https://github.com/moabukar/CKS-Exercises-Certified-Kubernetes-Security-Specialist) + Community exercises across the six domains. Some content is legacy, including PodSecurityPolicy; modernize before reuse. +- [Killer.sh CKS challenge series](https://github.com/killer-sh/cks-challenge-series) + Public, scenario-oriented practice useful for studying task wording and difficulty without using exam dumps. + +## Wisdom (Communities) + +- [Kubernetes Slack](https://slack.k8s.io/) + Practitioner community; use relevant security and certification-preparation channels to discuss concepts, never confidential exam content. +- [Kubernetes Discuss](https://discuss.kubernetes.io/) + Public community for operational questions and implementation feedback. + +## Source policy + +- Official CNCF/Linux Foundation objectives override community repositories. +- Official project documentation overrides remembered syntax and old examples. +- Never use or request confidential exam questions or dumps. diff --git a/assets/course.css b/assets/course.css new file mode 100644 index 0000000..5226fe0 --- /dev/null +++ b/assets/course.css @@ -0,0 +1,278 @@ +:root { + color-scheme: light; + --ink: #14213d; + --muted: #5f6b7a; + --paper: #fffdf8; + --canvas: #eef3f8; + --line: #d9e1ea; + --accent: #087e8b; + --accent-dark: #075a63; + --warm: #ffb703; + --code: #101827; + --code-ink: #e6edf7; + --shadow: 0 24px 70px rgba(20, 33, 61, 0.12); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + margin: 0; + color: var(--ink); + background: + radial-gradient(circle at 5% 0%, rgba(8, 126, 139, 0.12), transparent 28rem), + linear-gradient(180deg, #f7fafc 0%, var(--canvas) 100%); + line-height: 1.65; +} + +.page { + width: min(1120px, calc(100% - 32px)); + margin: 32px auto 72px; +} + +.hero { + position: relative; + overflow: hidden; + padding: clamp(32px, 6vw, 72px); + color: #fff; + background: linear-gradient(135deg, #14213d 0%, #0e5262 68%, #087e8b 100%); + border-radius: 28px; + box-shadow: var(--shadow); +} + +.hero::after { + content: ""; + position: absolute; + width: 290px; + height: 290px; + right: -105px; + top: -135px; + border: 36px solid rgba(255, 183, 3, 0.2); + border-radius: 50%; +} + +.eyebrow { + margin: 0 0 12px; + color: #9fe7e9; + font-size: 0.78rem; + font-weight: 800; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +h1, h2, h3 { + line-height: 1.15; + text-wrap: balance; +} + +h1 { + max-width: 760px; + margin: 0; + font-family: Georgia, "Times New Roman", serif; + font-size: clamp(2.25rem, 6vw, 5rem); + font-weight: 700; + letter-spacing: -0.045em; +} + +.deck { + max-width: 700px; + margin: 22px 0 0; + color: #d9f1f2; + font-size: clamp(1rem, 2vw, 1.2rem); +} + +.meta { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 28px; +} + +.pill { + padding: 7px 12px; + color: #fff; + background: rgba(255,255,255,0.1); + border: 1px solid rgba(255,255,255,0.22); + border-radius: 999px; + font-size: 0.86rem; + font-weight: 650; +} + +.layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 280px; + gap: 24px; + margin-top: 24px; + align-items: start; +} + +.card { + padding: clamp(24px, 4vw, 44px); + background: var(--paper); + border: 1px solid rgba(217, 225, 234, 0.9); + border-radius: 22px; + box-shadow: 0 12px 40px rgba(20, 33, 61, 0.07); +} + +.card + .card { margin-top: 20px; } + +h2 { + margin: 0 0 20px; + font-family: Georgia, "Times New Roman", serif; + font-size: clamp(1.55rem, 3vw, 2.2rem); + letter-spacing: -0.02em; +} + +h3 { + margin: 28px 0 10px; + font-size: 1.02rem; +} + +p { margin: 0 0 16px; } + +code { + padding: 0.12em 0.35em; + background: #e8f0f3; + border-radius: 5px; + font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace; + font-size: 0.9em; +} + +pre { + overflow-x: auto; + margin: 18px 0; + padding: 18px 20px; + color: var(--code-ink); + background: var(--code); + border-radius: 14px; + line-height: 1.55; +} + +pre code { padding: 0; color: inherit; background: transparent; } + +table { + width: 100%; + margin: 20px 0; + border-collapse: collapse; + font-size: 0.95rem; +} + +th, td { + padding: 12px 14px; + text-align: left; + border-bottom: 1px solid var(--line); +} + +th { color: var(--accent-dark); font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; } + +.requirements { + padding-left: 0; + list-style: none; + counter-reset: requirement; +} + +.requirements li { + position: relative; + min-height: 44px; + margin: 0 0 12px; + padding: 9px 8px 9px 54px; + counter-increment: requirement; +} + +.requirements li::before { + content: counter(requirement); + position: absolute; + left: 0; + top: 5px; + display: grid; + width: 36px; + height: 36px; + place-items: center; + color: #fff; + background: var(--accent); + border-radius: 50%; + font-weight: 800; +} + +.note { + margin-top: 24px; + padding: 16px 18px; + background: #fff7df; + border-left: 4px solid var(--warm); + border-radius: 0 10px 10px 0; +} + +.rail { position: sticky; top: 24px; } + +.rail h2 { font-size: 1.35rem; } +.rail .eyebrow { color: var(--accent-dark); } + +.steps { + margin: 0; + padding: 0; + list-style: none; +} + +.steps li { + position: relative; + padding: 0 0 22px 28px; + color: var(--muted); + border-left: 2px solid var(--line); +} + +.steps li::before { + content: ""; + position: absolute; + width: 10px; + height: 10px; + left: -6px; + top: 6px; + background: var(--paper); + border: 2px solid #aeb9c7; + border-radius: 50%; +} + +.steps li.active { color: var(--ink); font-weight: 750; } +.steps li.active::before { background: var(--warm); border-color: var(--warm); } +.steps li:last-child { padding-bottom: 0; border-left-color: transparent; } + +.button { + display: block; + margin-top: 24px; + padding: 13px 16px; + color: #fff; + background: var(--accent); + border-radius: 10px; + font-weight: 750; + text-align: center; + text-decoration: none; +} + +.button:hover { background: var(--accent-dark); } + +.small { color: var(--muted); font-size: 0.86rem; } + +footer { + padding: 28px 8px 0; + color: var(--muted); + text-align: center; + font-size: 0.86rem; +} + +@media (max-width: 820px) { + .layout { grid-template-columns: 1fr; } + .rail { position: static; order: -1; } +} + +@media print { + body { background: #fff; } + .page { width: 100%; margin: 0; } + .hero, .card { box-shadow: none; } + .hero { color: var(--ink); background: #fff; border: 2px solid var(--ink); } + .hero .deck, .hero .eyebrow { color: var(--ink); } + .pill { color: var(--ink); border-color: var(--ink); } + .layout { grid-template-columns: 1fr; } + .rail, footer { display: none; } +} diff --git a/assets/game.css b/assets/game.css new file mode 100644 index 0000000..34e1b7f --- /dev/null +++ b/assets/game.css @@ -0,0 +1,74 @@ +.game-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; + margin: 22px 0; +} + +.game-stat { + padding: 18px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.18); + border-radius: 16px; +} + +.game-stat strong { display: block; font-size: 1.65rem; line-height: 1.1; } +.game-stat span { color: #c6e8eb; font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; } + +.xp-track { + overflow: hidden; + height: 12px; + margin: 10px 0 6px; + background: #dce7ea; + border-radius: 999px; +} + +.xp-fill { width: 24%; height: 100%; background: linear-gradient(90deg, var(--accent), var(--warm)); } + +.mission-map { display: grid; gap: 12px; } + +.mission { + display: grid; + grid-template-columns: 42px 1fr auto; + gap: 14px; + align-items: center; + padding: 14px 0; + border-bottom: 1px solid var(--line); +} + +.mission:last-child { border-bottom: 0; } + +.mission-mark { + display: grid; + width: 38px; + height: 38px; + place-items: center; + color: #fff; + background: var(--accent); + border-radius: 50%; + font-weight: 800; +} + +.mission.locked .mission-mark { color: var(--muted); background: #e4e9ef; } +.mission.active .mission-mark { color: var(--ink); background: var(--warm); } +.mission h3 { margin: 0 0 2px; } +.mission p { margin: 0; color: var(--muted); font-size: 0.88rem; } +.mission-status { color: var(--accent-dark); font-size: 0.8rem; font-weight: 800; text-transform: uppercase; } + +.achievement-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } +.achievement { padding: 16px; background: #edf7f7; border-radius: 14px; } +.achievement.locked { color: var(--muted); background: #f1f3f6; filter: grayscale(1); } +.achievement strong { display: block; margin-bottom: 4px; } + +.rules { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 20px; padding: 0; list-style: none; } +.rules li { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--line); } +.hero-note { color: #d9f1f2; } +.light-eyebrow { color: var(--accent-dark); } +.light-pill { color: var(--ink); border-color: var(--line); } +.button-secondary { background: var(--ink); } + +@media (max-width: 720px) { + .game-grid, .achievement-grid, .rules { grid-template-columns: 1fr; } + .mission { grid-template-columns: 42px 1fr; } + .mission-status { grid-column: 2; } +} diff --git a/dashboard.html b/dashboard.html new file mode 100644 index 0000000..99a9db3 --- /dev/null +++ b/dashboard.html @@ -0,0 +1,73 @@ + + + + + CKS Security Quest + + + +
+
+

CKS Security Quest

+

Guardian · 1020 XP

+

Advance by completing real security operations. Accuracy earns progress; first-attempt mastery unlocks the next territory.

+
+
15Missions completed
+
2 / 4Cluster Hardening covered
+
2Clean wins
+
+
+

380 XP to rank: CKS Elite

+
+ +
+
+
+

Campaign map

+

Cluster Setup — complete

+
+
✓

1.1 NetworkPolicy

Covered after guided remediation; scheduled for later review.

Covered
+
✓

1.2 CIS benchmark

Covered after guided remediation; scheduled for later review.

Covered
+
✓

1.3 Ingress TLS

Covered; NGINX annotations remain an interleaved review target.

Covered
+
✓

1.4 Metadata protection

Mastered with a clean first-attempt transfer.

Mastered
+
✓

1.5 Binary verification

Mastered with clean checksum transfer.

Mastered
+
✓

2.1 Least-privilege RBAC

Learner-confirmed coverage; scheduled for interleaved review.

Covered
+
✓

2.2 Service accounts

Completed with assistance; verification review remains.

Covered
+
!

2.3 API access

One-minute mastery gate: protect super-admin credentials.

Active
+
+
+ +
+

Achievements

+
+
Policy TamerCompleted a multi-policy containment scenario.
+
Recovery OperatorRecovered API server and kubelet after hardening.
+
Clean SweepSolved CKS-012 with no correction.
+
Domain DefenderCompleted every Cluster Setup topic.
+
+
+
+ + +
+
+ + diff --git a/learning-records/0001-networkpolicy-progress.md b/learning-records/0001-networkpolicy-progress.md new file mode 100644 index 0000000..462080f --- /dev/null +++ b/learning-records/0001-networkpolicy-progress.md @@ -0,0 +1,3 @@ +# NetworkPolicy topic accepted by learner + +The learner produced a functionally correct policy set after guided remediation, then chose to move to CIS benchmark review instead of completing the fresh first-attempt transfer gate. Treat NetworkPolicy as covered by learner request, but interleave it into later exercises because first-attempt mastery was not demonstrated. diff --git a/learning-records/0002-cis-benchmark-progress.md b/learning-records/0002-cis-benchmark-progress.md new file mode 100644 index 0000000..ba63b54 --- /dev/null +++ b/learning-records/0002-cis-benchmark-progress.md @@ -0,0 +1,3 @@ +# CIS benchmark topic accepted by learner + +The learner successfully remediated kube-apiserver and kubelet CIS findings after guided corrections, including backup placement, service recovery, host context, targeted reruns, and socket verification. The learner chose to move on before a clean first-attempt transfer, so interleave CIS remediation later. diff --git a/learning-records/0003-ingress-tls-confidence.md b/learning-records/0003-ingress-tls-confidence.md new file mode 100644 index 0000000..4479845 --- /dev/null +++ b/learning-records/0003-ingress-tls-confidence.md @@ -0,0 +1,3 @@ +# Ingress and TLS confidence established + +The learner produced a correct TLS Secret command and final TLS-enabled Ingress, and reports strong confidence with Ingress/TLS. Certificate inspection and Secret verification were omitted in the submitted workflow; NGINX annotation recall is the learner's stated weak point and should be interleaved later. diff --git a/learning-records/0004-metadata-protection-mastered.md b/learning-records/0004-metadata-protection-mastered.md new file mode 100644 index 0000000..8862f20 --- /dev/null +++ b/learning-records/0004-metadata-protection-mastered.md @@ -0,0 +1,3 @@ +# Metadata endpoint protection mastered + +The learner independently produced a correct egress-isolating NetworkPolicy that retained HTTPS access while excluding the link-local cloud metadata endpoint. CKS-012 scored 100/100 on the first attempt, providing clean transfer evidence. diff --git a/learning-records/0005-binary-verification-mastered.md b/learning-records/0005-binary-verification-mastered.md new file mode 100644 index 0000000..3f27573 --- /dev/null +++ b/learning-records/0005-binary-verification-mastered.md @@ -0,0 +1,3 @@ +# Platform binary verification mastered + +The learner correctly distinguished raw checksum files from standard checksum records, verified SHA-256/SHA-512 data, installed binaries with explicit ownership/mode, and independently completed CKS-015 on the first attempt. diff --git a/learning-records/0006-rbac-prior-knowledge.md b/learning-records/0006-rbac-prior-knowledge.md new file mode 100644 index 0000000..5cb65da --- /dev/null +++ b/learning-records/0006-rbac-prior-knowledge.md @@ -0,0 +1,3 @@ +# RBAC confidence reported + +The learner reports being comfortable with Kubernetes RBAC and requested advancement without attempting CKS-016. Treat RBAC as covered but not independently demonstrated in this workspace; interleave least-privilege and subresource checks later. diff --git a/learning-records/0007-service-account-assisted.md b/learning-records/0007-service-account-assisted.md new file mode 100644 index 0000000..a37a060 --- /dev/null +++ b/learning-records/0007-service-account-assisted.md @@ -0,0 +1,3 @@ +# Service-account token safety completed with assistance + +The learner reviewed a correct dedicated ServiceAccount and CronJob configuration with token automounting disabled at both levels. The final manifests were applied from a combined file; verification was explicitly skipped, so schedule a later interleaved check of projected token absence. diff --git a/lessons/0001-networkpolicy-containment.html b/lessons/0001-networkpolicy-containment.html new file mode 100644 index 0000000..d65a8b8 --- /dev/null +++ b/lessons/0001-networkpolicy-containment.html @@ -0,0 +1,89 @@ + + + + + + + CKS-001 · NetworkPolicy Containment + + + +
+
+

CKS mastery journey · Question 001

+

NetworkPolicy containment under pressure

+

Build a precise default-deny posture, preserve only required flows, and prove that the cloud metadata endpoint remains unreachable.

+
+ Cluster Setup + Attempt 1 + 12 minutes + Harder than CKS +
+
+ +
+
+
+

Scenario

+

Begin by selecting the designated cluster:

+
kubectl config use-context prod-sec
+

Namespace payments already contains these Pods:

+ + + + + + + +
PodLabels
api-0app=api, tier=backend
worker-0app=worker, tier=backend
debug-0app=debug
+

Namespace gateway has label access=payments and contains Pods labeled app=ingress.

+

Namespace observability has label team=monitoring and contains Pods labeled app=prometheus.

+

The cluster DNS Pods are in kube-system with label k8s-app=kube-dns. The cloud instance-metadata endpoint is 169.254.169.254/32.

+
+ +
+

Required end state

+
    +
  1. Every Pod in payments is isolated for both ingress and egress.
  2. +
  3. Only gateway Pods labeled app=ingress may connect to TCP port 8443 on Pods labeled app=api.
  4. +
  5. Only observability Pods labeled app=prometheus may scrape TCP port 9090 on Pods labeled tier=backend.
  6. +
  7. api and worker Pods may query cluster DNS over UDP and TCP port 53; debug-0 must have no egress, including DNS.
  8. +
  9. Pods labeled app=api may make TCP connections to any IPv4 destination on port 443, except 169.254.169.254/32.
  10. +
  11. No other ingress or egress is permitted.
  12. +
  13. Do not modify Pods, namespaces, Services, or existing labels.
  14. +
+
Environment: Assume the installed CNI enforces Kubernetes NetworkPolicy. Do not use Cilium-specific resources.
+
+ +
+

Your deliverables

+

A · Context and pre-checks

+

Record the commands you would use to confirm the context, labels, Pods, namespaces, and existing policy state.

+

B · Complete manifest set

+

Provide every NetworkPolicy needed to achieve the required end state.

+

C · Verification evidence

+

Include at least one positive and one negative test for each distinct rule. Explicitly prove that metadata access is denied and debug-0 cannot use DNS.

+

D · Selector reasoning

+

Explain why the policies do not accidentally allow every Pod in an approved namespace, every port from an approved source, metadata on another port, or DNS from debug-0.

+
+
+ + +
+ +
CKS Mastery Journey · Ask your teacher about anything unclear, but remember: hints become stronger gradually.
+
+ + diff --git a/lessons/0002-networkpolicy-two-sided-isolation.html b/lessons/0002-networkpolicy-two-sided-isolation.html new file mode 100644 index 0000000..7fb996a --- /dev/null +++ b/lessons/0002-networkpolicy-two-sided-isolation.html @@ -0,0 +1,54 @@ + + + + + + + CKS-002 · Two-Sided Isolation + + + +
+
+

CKS mastery journey · Fresh transfer

+

Two-sided NetworkPolicy isolation

+

Secure current and future workloads while composing namespace, Pod, port, DNS, and CIDR restrictions without unintended additive access.

+
Cluster SetupCKS-00215 minutesSection B scored
+
+
+
+
+

Scenario

+
kubectl config use-context secure-prod
+ + + +
PodLabelsListens on
web-0app=webTCP 443
api-0app=apiTCP 8080
batch-0app=batchnone
+

edge is labeled zone=trusted-edge; approved clients are role=proxy. monitoring is labeled purpose=metrics; approved clients are role=scraper.

+

DNS Pods are in kube-system with k8s-app=kube-dns. Database CIDR is 10.40.0.0/16; 10.40.8.0/24 is forbidden. Future unlabeled Pods may appear in blue.

+
+
+

Required end state

+
    +
  1. Default-deny ingress and egress for every current and future Pod in blue.
  2. +
  3. Trusted edge proxies alone may reach web on TCP 443.
  4. +
  5. Only web may reach api on TCP 8080.
  6. +
  7. Approved monitoring scrapers may reach web and api on TCP 9100.
  8. +
  9. Only web and api may query cluster DNS over UDP/TCP 53.
  10. +
  11. Only api may reach the database CIDR on TCP 5432, excluding its reserved subnet.
  12. +
  13. batch has no traffic; no other traffic is permitted.
  14. +
  15. Do not modify existing objects or use CNI-specific policy kinds.
  16. +
+
+
+ +
+
CKS Mastery Journey · Question 002
+
+ + diff --git a/lessons/0003-cis-apiserver-authorization.html b/lessons/0003-cis-apiserver-authorization.html new file mode 100644 index 0000000..5bb726f --- /dev/null +++ b/lessons/0003-cis-apiserver-authorization.html @@ -0,0 +1,34 @@ + + + + + CKS-003 · CIS API Server Repair + + + +
+
+

CKS mastery journey · 1.2 CIS benchmark

+

Repair one API-server failure

+

Make a minimal static-Pod correction, recover the control plane, and prove the targeted CIS check passes.

+
CKS-0036 minutesFocused task
+
+
+
+

Task

+
[FAIL] 1.2.7 Ensure that the --authorization-mode argument is not set to AlwaysAllow
+

On controlplane, edit /etc/kubernetes/manifests/kube-apiserver.yaml.

+

The current line is:

+
- --authorization-mode=AlwaysAllow,RBAC
+

Back up the manifest. Change only what is necessary so the modes are exactly Node,RBAC. Verify API-server recovery and rerun check 1.2.7.

+
+ +
+
CKS Mastery Journey · Question 003
+
+ + diff --git a/lessons/0004-cis-kubelet-anonymous-auth.html b/lessons/0004-cis-kubelet-anonymous-auth.html new file mode 100644 index 0000000..3b0629a --- /dev/null +++ b/lessons/0004-cis-kubelet-anonymous-auth.html @@ -0,0 +1,30 @@ + + + + + CKS-004 · Kubelet CIS Repair + + +
+
+

CKS mastery journey · CIS transfer

+

Repair one kubelet failure

+

One configuration change, one recovery check, one targeted benchmark.

+
CKS-0045 minutes
+
+
+
+

Task

+
[FAIL] 4.2.1 Ensure that the --anonymous-auth argument is set to false
+

On node01, back up /var/lib/kubelet/config.yaml outside its directory. Change authentication.anonymous.enabled: true to the secure value.

+

Restart the correct service, verify the node becomes Ready, and rerun only check 4.2.1 for the node target.

+
+ +
+
+ + diff --git a/lessons/0005-cis-kubelet-readonly-port.html b/lessons/0005-cis-kubelet-readonly-port.html new file mode 100644 index 0000000..8fe6be0 --- /dev/null +++ b/lessons/0005-cis-kubelet-readonly-port.html @@ -0,0 +1,29 @@ + + +CKS-005 · Close Kubelet Read-Only Port +

CKS mastery journey · Final CIS transfer

+

Close one unsafe port

A four-minute kubelet hardening task.

+
CKS-0054 minutes
+

Task

On node02, locate and disable the insecure read-only port in this complete exercise file, /var/lib/kubelet/config.yaml:

+
apiVersion: kubelet.config.k8s.io/v1beta1
+kind: KubeletConfiguration
+address: 0.0.0.0
+port: 10250
+readOnlyPort: 10255
+authentication:
+  anonymous:
+    enabled: false
+  webhook:
+    enabled: true
+  x509:
+    clientCAFile: /etc/kubernetes/pki/ca.crt
+authorization:
+  mode: Webhook
+clusterDNS:
+- 10.96.0.10
+clusterDomain: cluster.local
+rotateCertificates: true
+serverTLSBootstrap: true
+staticPodPath: /etc/kubernetes/manifests
+

Back up first, restart kubelet, prove TCP 10255 is not listening, rerun only CIS check 4.2.4 on the node, and verify node02 is Ready from the original host.

+
diff --git a/lessons/0006-cis-kubelet-authorization.html b/lessons/0006-cis-kubelet-authorization.html new file mode 100644 index 0000000..c241211 --- /dev/null +++ b/lessons/0006-cis-kubelet-authorization.html @@ -0,0 +1,27 @@ + +CKS-006 · Kubelet Authorization
+

CKS mastery journey · CIS final transfer

Correct kubelet authorization

+

Find one insecure field in a complete configuration and perform the full remediation safely.

CKS-0064 minutes
+

Complete exercise file

/var/lib/kubelet/config.yaml on node03:

+
apiVersion: kubelet.config.k8s.io/v1beta1
+kind: KubeletConfiguration
+address: 0.0.0.0
+port: 10250
+readOnlyPort: 0
+authentication:
+  anonymous:
+    enabled: false
+  webhook:
+    enabled: true
+  x509:
+    clientCAFile: /etc/kubernetes/pki/ca.crt
+authorization:
+  mode: AlwaysAllow
+clusterDNS:
+- 10.96.0.10
+clusterDomain: cluster.local
+rotateCertificates: true
+serverTLSBootstrap: true
+staticPodPath: /etc/kubernetes/manifests
+

Back up outside the directory, set authorization to Webhook, restart/check kubelet, rerun node check 4.2.2, then verify node03 is Ready from the original host.

+
diff --git a/lessons/0007-cis-manifest-permissions.html b/lessons/0007-cis-manifest-permissions.html new file mode 100644 index 0000000..c7ac4e9 --- /dev/null +++ b/lessons/0007-cis-manifest-permissions.html @@ -0,0 +1,8 @@ + +CKS-007 · Manifest Permissions
+

CKS Security Quest · Clean-win mission

Repair manifest permissions

+

Three minutes. One file. One targeted CIS proof.

CKS-007100 XP
+

Task

/etc/kubernetes/manifests/kube-controller-manager.yaml
+mode=0644 owner=root group=root
+

On controlplane, back up outside the manifest directory, set mode 0600 without changing ownership, verify both, and rerun only master check 1.1.3.

+
diff --git a/lessons/0008-cis-manifest-ownership.html b/lessons/0008-cis-manifest-ownership.html new file mode 100644 index 0000000..cd2f4ef --- /dev/null +++ b/lessons/0008-cis-manifest-ownership.html @@ -0,0 +1,8 @@ + +CKS-008 · Manifest Ownership
+

CKS Security Quest · Boss gate

Correct manifest ownership

Two minutes. Four commands. Unlock Ingress TLS.

+
CKS-008250 XP available
+

Task

/etc/kubernetes/manifests/kube-scheduler.yaml
+mode=0600 owner=root group=adm
+
  1. Back up outside the manifest directory.
  2. Set owner and group to root without changing mode.
  3. Verify numeric mode and owner:group.
  4. Rerun only master check 1.1.4.
+
diff --git a/lessons/0009-ingress-tls.html b/lessons/0009-ingress-tls.html new file mode 100644 index 0000000..5e14f51 --- /dev/null +++ b/lessons/0009-ingress-tls.html @@ -0,0 +1,27 @@ + +CKS-009 · Ingress TLS
+

CKS Security Quest · Ingress TLS

Secure an existing route

Inspect the certificate, create the Secret, and add TLS without breaking routing.

+
CKS-0096 minutes
+

Supplied files

/opt/certs/store.crt
+/opt/certs/store.key
+

Complete existing Ingress

apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: storefront
+  namespace: shop
+  annotations:
+    nginx.ingress.kubernetes.io/ssl-redirect: "true"
+spec:
+  ingressClassName: nginx
+  rules:
+  - host: store.example.com
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: storefront
+            port:
+              number: 80
+
diff --git a/lessons/0010-dashboard-endpoint.html b/lessons/0010-dashboard-endpoint.html new file mode 100644 index 0000000..1ed5018 --- /dev/null +++ b/lessons/0010-dashboard-endpoint.html @@ -0,0 +1,4 @@ +CKS-010 · Dashboard Endpoint
+

CKS Security Quest · Endpoint protection

Contain the dashboard

Remove public exposure and cluster-wide administrative privilege.

CKS-0104 minutes
+

Task

The supplied dashboard Service is NodePort on 30443. ClusterRoleBinding dashboard-admin grants its ServiceAccount cluster-admin.

+

Keep the dashboard reachable inside the cluster, remove cluster-admin access, and verify both outcomes. Modify/delete only what is necessary.

diff --git a/lessons/0011-binary-verification.html b/lessons/0011-binary-verification.html new file mode 100644 index 0000000..b251697 --- /dev/null +++ b/lessons/0011-binary-verification.html @@ -0,0 +1,4 @@ +CKS-011 · Binary Verification
+

CKS Security Quest · Binary verification

Trust, then install

Verify provenance before a binary reaches a privileged path.

CKS-0114 minutes
+

Task

/opt/release/kubectl
+/opt/release/kubectl.sha256

The checksum file contains a raw official SHA-256 value without a filename.

  1. Verify the binary.
  2. Install only on success as root:root and 0755.
  3. Show client version.
  4. Show installed owner and mode.
diff --git a/lessons/0012-block-cloud-metadata.html b/lessons/0012-block-cloud-metadata.html new file mode 100644 index 0000000..27b85dc --- /dev/null +++ b/lessons/0012-block-cloud-metadata.html @@ -0,0 +1,4 @@ +CKS-012 · Block Metadata
+

CKS Security Quest · Metadata transfer

Block cloud metadata

Keep HTTPS egress, exclude one credential-bearing endpoint.

CKS-0123 minutes
+

Task

In build, isolate egress for role=runner Pods. Allow TCP 443 to any IPv4 destination except 169.254.169.254/32. Permit nothing else.

+
diff --git a/lessons/0013-kubelet-binary.html b/lessons/0013-kubelet-binary.html new file mode 100644 index 0000000..40742d1 --- /dev/null +++ b/lessons/0013-kubelet-binary.html @@ -0,0 +1,4 @@ +CKS-013 · Kubelet Binary
+

CKS Security Quest · Binary transfer

Verify the kubelet

Two minutes. Verify, install, inspect.

CKS-013No shell puzzle
+

Task

/opt/release/kubelet
+/opt/release/kubelet.sha256
  1. Verify the raw checksum with sha256sum --check.
  2. After observing OK, use install for root:root mode 0755.
  3. Show version and final mode/owner.
diff --git a/lessons/0014-kube-apiserver-checksum.html b/lessons/0014-kube-apiserver-checksum.html new file mode 100644 index 0000000..de31770 --- /dev/null +++ b/lessons/0014-kube-apiserver-checksum.html @@ -0,0 +1,4 @@ +CKS-014 · SHA-512 Check
+

CKS Security Quest · One-command gate

Verify kube-apiserver

Feed a raw checksum and binary path to sha512sum.

CKS-0141 minute
+

Files

/opt/kubernetes/kube-apiserver
+/opt/kubernetes/kube-apiserver.sha512

Submit one sha512sum --check command.

diff --git a/lessons/0015-standard-checksum.html b/lessons/0015-standard-checksum.html new file mode 100644 index 0000000..1327e4b --- /dev/null +++ b/lessons/0015-standard-checksum.html @@ -0,0 +1,4 @@ +CKS-015 · Standard Checksum
+

CKS Security Quest · Final binary gate

Verify standard checksum

No string construction. Just verify the supplied record.

CKS-01530 seconds
+

Task

/opt/release/kube-scheduler
+/opt/release/kube-scheduler.sha256

The checksum file already contains the hash and filename in standard format. Verify it.

diff --git a/lessons/0016-rbac-least-privilege.html b/lessons/0016-rbac-least-privilege.html new file mode 100644 index 0000000..817419d --- /dev/null +++ b/lessons/0016-rbac-least-privilege.html @@ -0,0 +1,3 @@ +CKS-016 · Least Privilege RBAC
+

CKS Security Quest · Cluster Hardening

Replace cluster-admin

Give one user exact observation rights in one namespace.

CKS-0165 minutes
+

Task

Delete ClusterRoleBinding maya-admin. In blue, create Role maya-observer and matching RoleBinding so user maya can get/list/watch Pods and get Pod logs—nothing more.

Verify one allowed operation and denied Secret access.

diff --git a/lessons/0017-service-account-token.html b/lessons/0017-service-account-token.html new file mode 100644 index 0000000..ea8350f --- /dev/null +++ b/lessons/0017-service-account-token.html @@ -0,0 +1,3 @@ +CKS-017 · ServiceAccount Safety
+

CKS Security Quest · Service accounts

Remove unnecessary API tokens

A dedicated identity with token automounting disabled at both layers.

CKS-0175 minutes
+

Task

Namespace jobs contains CronJob reporter, which does not use the Kubernetes API.

  1. Create dedicated ServiceAccount reporter.
  2. Disable automounting on the ServiceAccount.
  3. Use it in the complete CronJob Pod template and explicitly disable automounting there.
  4. Apply and verify both settings.
diff --git a/lessons/0018-disable-anonymous-api.html b/lessons/0018-disable-anonymous-api.html new file mode 100644 index 0000000..dceacb5 --- /dev/null +++ b/lessons/0018-disable-anonymous-api.html @@ -0,0 +1,3 @@ +CKS-018 · Disable Anonymous API
+

CKS Security Quest · API access

Reject anonymous clients

Change one static-Pod flag, recover safely, and test both authenticated and anonymous paths.

CKS-0185 minutes
+

Task

The complete exercise manifest is in ANSWER.md. Back it up outside the static-manifest directory, change --anonymous-auth=true, wait for recovery, verify /readyz with kubectl, and confirm an unauthenticated HTTPS request returns 401.

diff --git a/lessons/0019-protect-admin-kubeconfig.html b/lessons/0019-protect-admin-kubeconfig.html new file mode 100644 index 0000000..597af31 --- /dev/null +++ b/lessons/0019-protect-admin-kubeconfig.html @@ -0,0 +1,4 @@ +CKS-019 · Protect Kubeconfig
+

CKS Security Quest · API access

Protect admin credentials

Three commands to secure a high-privilege kubeconfig.

CKS-0192 minutes
+

Task

/etc/kubernetes/admin.conf
+mode=0644 owner=root group=root
  1. Back up to /root/admin.conf.backup.
  2. Set mode 0600 only.
  3. Verify numeric mode and owner:group.
diff --git a/lessons/0020-protect-super-admin.html b/lessons/0020-protect-super-admin.html new file mode 100644 index 0000000..efa6ad5 --- /dev/null +++ b/lessons/0020-protect-super-admin.html @@ -0,0 +1,3 @@ +CKS-020 · Protect Super Admin
+

CKS Security Quest · One-minute gate

Protect super-admin

Back up, secure, verify.

CKS-0203 commands
+

Task

  1. Back up the live kubeconfig to the specified path.
  2. Set the live file to 0600.
  3. Run the supplied stat format against the live file.
diff --git a/queued/CKS-011.md b/queued/CKS-011.md new file mode 100644 index 0000000..91f383a --- /dev/null +++ b/queued/CKS-011.md @@ -0,0 +1,25 @@ +# CKS-011 — Verify and install a Kubernetes binary + +Domain: **1.5 Platform binary verification** +Time: **4 minutes** · Score: **Section B only** + +## Supplied files + +```text +/opt/release/kubectl +/opt/release/kubectl.sha256 +``` + +`kubectl.sha256` contains the official raw SHA-256 value but no filename. + +## Task + +Verify the downloaded binary against the checksum. Install it as `/usr/local/bin/kubectl` with owner `root:root` and mode `0755` only if verification succeeds. Then show its client version and installed ownership/mode. + +## Section B — scored answer + +```bash +# Your commands +``` + +Say **Grade CKS-011** when saved. diff --git a/reference/nginx-ingress-annotations.html b/reference/nginx-ingress-annotations.html new file mode 100644 index 0000000..44ce6f5 --- /dev/null +++ b/reference/nginx-ingress-annotations.html @@ -0,0 +1,15 @@ + +NGINX Ingress Annotation Field Card
+

CKS field card

NGINX annotations

Default prefix: nginx.ingress.kubernetes.io/. Annotation values are strings—quote booleans and numbers.

+

High-value recall set

+ + + + + + + + +
IntentAnnotationExample value
Redirect HTTP when TLS existsssl-redirect"true"
Force redirect with external TLS offloadforce-ssl-redirect"true"
Encrypt NGINX-to-backend trafficbackend-protocol"HTTPS"
Allow client CIDRswhitelist-source-range"10.0.0.0/8"
Rewrite matched URIrewrite-target/$2
Limit request bodyproxy-body-size"8m"
Enable regex pathsuse-regex"true"
TLS passthroughssl-passthrough"true"
+
Security caution: snippet annotations can be critical-risk. TLS passthrough requires controller support and bypasses normal HTTP-layer annotation behavior.
+

Official annotation reference · Official risk table

diff --git a/submissions/CKS-001-attempt-1.md b/submissions/CKS-001-attempt-1.md new file mode 100644 index 0000000..0dc525f --- /dev/null +++ b/submissions/CKS-001-attempt-1.md @@ -0,0 +1,197 @@ +# CKS-001 — NetworkPolicy containment under pressure + +Status: **Attempt 1 — awaiting learner answer** +Domain: **Cluster Setup (15%)** +Topic: **NetworkPolicy and metadata protection** +Target time: **12 minutes** +Difficulty: **Harder than CKS** + +## Scenario + +Use context: + +```bash +kubectl config use-context prod-sec +``` + +Namespace `payments` already contains these Pods: + +| Pod | Labels | +|---|---| +| `api-0` | `app=api`, `tier=backend` | +| `worker-0` | `app=worker`, `tier=backend` | +| `debug-0` | `app=debug` | + +Namespace `gateway` has label `access=payments` and contains Pods labeled `app=ingress`. + +Namespace `observability` has label `team=monitoring` and contains Pods labeled `app=prometheus`. + +The cluster DNS Pods are in namespace `kube-system` with label `k8s-app=kube-dns`. The cloud instance-metadata endpoint is `169.254.169.254/32`. + +## Required end state + +Create one or more NetworkPolicies in namespace `payments` so that: + +1. Every Pod in `payments` is isolated for both ingress and egress. +2. Only `gateway` Pods labeled `app=ingress` may connect to TCP port `8443` on Pods labeled `app=api`. +3. Only `observability` Pods labeled `app=prometheus` may scrape TCP port `9090` on Pods labeled `tier=backend`. +4. `api` and `worker` Pods may query cluster DNS using UDP and TCP port `53`; `debug-0` must have no egress, including DNS. +5. Pods labeled `app=api` may make TCP connections to any IPv4 destination on port `443`, except `169.254.169.254/32`. +6. No other ingress or egress is permitted. +7. Do not modify Pods, namespaces, Services, or existing labels. + +Assume the installed CNI enforces Kubernetes `NetworkPolicy`. Do not use Cilium-specific resources. + +## Deliverables + +Write your proposed commands/manifests and verification plan below. Do not ask the teacher to apply them. + +### A. Context and pre-check commands + +```bash +kubectl config use-context prod-sec +k get pods -n payment +k get pods -n payment -o wide +k get pods -n payment --show-labels +k get ns --show-labels +k get pods -n gateway --show-labels +k get pods -n observability --show-labels +k get pods -n kube-system --show-labels +``` + +### B. NetworkPolicy manifest(s) + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: policy-for-gateway + namespace: payments +spec: + podSelector: + matchLabels: + app: api + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + access: payments + podSelector: + matchLabels: + app: ingress + ports: + - protocol: TCP + port: 8443 + egress: + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 169.254.169.254/32 + ports: + - protocol: TCP + port: 443 + - to: + - podSelector: + matchLabels: + k8s-app: kube-dns + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: 53 + - protocol: UDP + port: 53 +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: policy-for-monitoring + namespace: payments +spec: + podSelector: + matchLabels: + tier: backend + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + team: monitoring + podSelector: + matchLabels: + app: prometheus + ports: + - protocol: TCP + port: 9090 + egress: + - to: + - podSelector: + matchLabels: + k8s-app: kube-dns + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: 53 + - protocol: UDP + port: 53 +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: policy-for-debug + namespace: payments +spec: + podSelector: + matchLabels: + app: debug + policyTypes: + - Ingress + - Egress +``` + +### C. Verification commands and expected outcomes + +Show at least one positive and one negative test for every distinct rule. Tests must demonstrate that metadata access is denied and that `debug-0` has no DNS egress. + +```bash +k apply -f np-payments.yaml +k get networkpolicy -n payments +POD=$(k get pod -n gateway -l app=ingress -o name) +API_IP=$(k get pod api-0 -n payments -o jsonpath='{.status.podIP}') +WORKER_IP=$(k get pod worker-0 -n payments -o jsonpath='{.status.podIP}') +k exec -n gateway $POD -- telnet $API_IP 8443 +POD=$(k get pod -n observability -l app=prometheus -o name) +k exec -n observability $POD -- telnet $WORKER_IP 9090 +``` + +### D. Short reasoning + +Explain briefly why your selectors do not accidentally allow: + +- every Pod in an allowed namespace; +- every port from an allowed source; +- metadata access over a non-443 port; +- DNS access from `debug-0`. + +Write here: + +> + +## Submission + +- [ ] I checked the context. +- [ ] I validated the YAML client-side or server-side. +- [ ] I covered all seven required outcomes. +- [ ] I included both allowed and denied verification cases. +- [ ] I am ready for grading. + +When ready, save this file and tell the teacher: **Grade CKS-001 attempt 1**. diff --git a/submissions/CKS-002-skipped.md b/submissions/CKS-002-skipped.md new file mode 100644 index 0000000..b18e64d --- /dev/null +++ b/submissions/CKS-002-skipped.md @@ -0,0 +1,63 @@ +# CKS-002 — NetworkPolicy two-sided isolation + +Status: **Fresh mastery attempt — awaiting learner answer** +Domain: **Cluster Setup (15%)** +Topic: **NetworkPolicy and cluster-level network restriction** +Target time: **15 minutes** +Difficulty: **Harder than CKS** + +## Scenario + +Use context: + +```bash +kubectl config use-context secure-prod +``` + +Namespace `blue` contains: + +| Pod | Labels | Listens on | +|---|---|---| +| `web-0` | `app=web` | TCP `443` | +| `api-0` | `app=api` | TCP `8080` | +| `batch-0` | `app=batch` | none | + +Additional facts: + +- Namespace `edge` has label `zone=trusted-edge`; its approved clients have label `role=proxy`. +- Namespace `monitoring` has label `purpose=metrics`; its approved clients have label `role=scraper`. +- Cluster DNS Pods are in `kube-system` with label `k8s-app=kube-dns`. +- The internal database range is `10.40.0.0/16`, but subnet `10.40.8.0/24` is reserved and forbidden. +- Future unlabeled Pods may be added to namespace `blue`. + +## Required end state + +Create Kubernetes `NetworkPolicy` resources in namespace `blue` so that: + +1. Every current and future Pod in `blue` is default-denied for both ingress and egress. +2. Only `role=proxy` Pods from the `zone=trusted-edge` namespace may reach `app=web` on TCP `443`. +3. Only `app=web` Pods in `blue` may reach `app=api` on TCP `8080`. +4. Only `role=scraper` Pods from the `purpose=metrics` namespace may reach both `web` and `api` Pods on TCP `9100`. +5. Only `web` and `api` Pods may query cluster DNS over both UDP and TCP `53`. +6. Only `api` Pods may connect to `10.40.0.0/16` on TCP `5432`, excluding `10.40.8.0/24`. +7. `batch` Pods must have no ingress or egress. No other traffic is permitted. +8. Do not modify existing Pods, namespaces, labels, or Services. Use standard Kubernetes `NetworkPolicy` only. + +## Section B — scored implementation + +Section B is the only scored section. Write all required manifests below. + +```yaml +# Replace this comment with your complete NetworkPolicy manifests. +``` + +## Optional self-check — unscored + +- [ ] An empty-selector policy covers future unlabeled Pods. +- [ ] Every cross-namespace peer requires the correct namespace and Pod labels together. +- [ ] The `web → api:8080` path is permitted in both relevant policy directions. +- [ ] DNS does not become available to `batch` or future unlabeled Pods. +- [ ] The reserved database subnet remains unreachable. +- [ ] No rule accidentally opens all ports or all sources. + +When ready, save this file and tell the teacher: **Grade CKS-002 fresh attempt**. diff --git a/submissions/CKS-003-completed.md b/submissions/CKS-003-completed.md new file mode 100644 index 0000000..dce9969 --- /dev/null +++ b/submissions/CKS-003-completed.md @@ -0,0 +1,43 @@ +# CKS-003 — Repair a CIS API-server failure + +Domain: **1.2 CIS benchmark review** +Target time: **6 minutes** +Score: **Section B only** + +## Task + +On host `controlplane`, `kube-bench` reports: + +```text +[FAIL] 1.2.7 Ensure that the --authorization-mode argument is not set to AlwaysAllow +``` + +The kube-apiserver is a static Pod defined at: + +```text +/etc/kubernetes/manifests/kube-apiserver.yaml +``` + +Its current command contains: + +```yaml +- --authorization-mode=AlwaysAllow,RBAC +``` + +Change only what is necessary so authorization modes are exactly `Node,RBAC`. Back up the manifest first. Then verify that the API server recovers and check `1.2.7` passes. + +## Section B — scored answer + +Write the commands you would execute and the final corrected manifest line: + +```bash +cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml.bkp +vi /etc/kubernetes/manifests/kube-apiserver.yaml +- --authorization-mode=Node,RBAC +crictl ps -a | grep -i apiserver +k get pods -n kube-system +ps -aux | grep -i apiserver +kube-bench run --targets master --check 1.2.7 +``` + +When ready, say: **Grade CKS-003**. diff --git a/submissions/CKS-004-completed.md b/submissions/CKS-004-completed.md new file mode 100644 index 0000000..0142ecb --- /dev/null +++ b/submissions/CKS-004-completed.md @@ -0,0 +1,43 @@ +# CKS-004 — Repair one kubelet CIS failure + +Domain: **1.2 CIS benchmark review** +Target time: **5 minutes** +Score: **Section B only** + +## Task + +On worker `node01`, kube-bench reports: + +```text +[FAIL] 4.2.1 Ensure that the --anonymous-auth argument is set to false +``` + +`/var/lib/kubelet/config.yaml` currently contains: + +```yaml +authentication: + anonymous: + enabled: true +``` + +Back up the file outside its directory, correct only this setting, restart the appropriate service, verify `node01` returns to `Ready`, and rerun only check `4.2.1` for the node target. + +## Section B — scored answer + +```bash +ssh node01 +cp /var/lib/kubelet/config.yaml ~/config.yaml.bkp-$(date +%Y-%m-%d) +nano /var/lib/kubelet/config.yaml +authentication: + anonymous: + enabled: false +systemctl restart kubelet +systemctl status kubelet +journalctl -xeu kubelet +kube-bench run --targets node --check 4.2.1 +exit +k get node node01 + +``` + +When ready, say: **Grade CKS-004**. diff --git a/submissions/CKS-005-completed.md b/submissions/CKS-005-completed.md new file mode 100644 index 0000000..098c402 --- /dev/null +++ b/submissions/CKS-005-completed.md @@ -0,0 +1,51 @@ +# CKS-005 — Close the kubelet read-only port + +Domain: **1.2 CIS benchmark review** +Target time: **4 minutes** +Score: **Section B only** + +## Task + +On `node02`, kube-bench reports that the kubelet read-only port is enabled. The complete exercise file `/var/lib/kubelet/config.yaml` is: + +```yaml +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +address: 0.0.0.0 +port: 10250 +readOnlyPort: 10255 +authentication: + anonymous: + enabled: false + webhook: + enabled: true + x509: + clientCAFile: /etc/kubernetes/pki/ca.crt +authorization: + mode: Webhook +clusterDNS: +- 10.96.0.10 +clusterDomain: cluster.local +rotateCertificates: true +serverTLSBootstrap: true +staticPodPath: /etc/kubernetes/manifests +``` + +Back up the file outside its directory, disable the read-only port, restart kubelet, confirm TCP `10255` is no longer listening, rerun only check `4.2.4` on the node, then verify `node02` is `Ready` from the original host. + +## Section B — scored answer + +```bash +ssh node02 +cp /var/lib/kubelet/config.yaml ~/config.yaml-$(date +%Y-%m-%d) +nano /var/lib/kubelet/config.yaml +readOnlyPort: 0 +systemctl restart kubelet +systemctl status kubelet +ss -tunlp | grep -i 10255 +kube-bench run --targets node --check 4.2.4 +exit +k get nodes +``` + +When ready, say: **Grade CKS-005**. diff --git a/submissions/CKS-006-completed.md b/submissions/CKS-006-completed.md new file mode 100644 index 0000000..097ed71 --- /dev/null +++ b/submissions/CKS-006-completed.md @@ -0,0 +1,77 @@ +# CKS-006 — Correct kubelet authorization + +Domain: **1.2 CIS benchmark review** +Target time: **4 minutes** +Score: **Section B only** + +## Task + +On `node03`, kube-bench reports that kubelet authorization is not set to `Webhook`. The complete exercise file `/var/lib/kubelet/config.yaml` is: + +```yaml +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +address: 0.0.0.0 +port: 10250 +readOnlyPort: 0 +authentication: + anonymous: + enabled: false + webhook: + enabled: true + x509: + clientCAFile: /etc/kubernetes/pki/ca.crt +authorization: + mode: AlwaysAllow +clusterDNS: +- 10.96.0.10 +clusterDomain: cluster.local +rotateCertificates: true +serverTLSBootstrap: true +staticPodPath: /etc/kubernetes/manifests +``` + +Back up the file outside its directory, correct only the insecure setting, restart and check kubelet, rerun only check `4.2.2` on the node, then verify `node03` is `Ready` from the original host. + +## Section B — scored answer + +```bash +ssh node03 +cp /var/lib/kubelet/config.yaml ~/config.yaml-$(date +%Y-%m-%d) +nano /var/lib/kubelet/config.yaml +``` + +```yaml +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +address: 0.0.0.0 +port: 10250 +readOnlyPort: 0 +authentication: + anonymous: + enabled: false + webhook: + enabled: true + x509: + clientCAFile: /etc/kubernetes/pki/ca.crt +authorization: + mode: Webhook +clusterDNS: +- 10.96.0.10 +clusterDomain: cluster.local +rotateCertificates: true +serverTLSBootstrap: true +staticPodPath: /etc/kubernetes/manifests + +``` +```bash +systemctl restart kubelet +systemctl status kubelet +kube-bench run --targets node --check 4.2.2 +exit +k get nodes +``` + + + +When ready, say: **Grade CKS-006**. diff --git a/submissions/CKS-007-completed.md b/submissions/CKS-007-completed.md new file mode 100644 index 0000000..4ba4c03 --- /dev/null +++ b/submissions/CKS-007-completed.md @@ -0,0 +1,26 @@ +# CKS-007 — Repair control-plane manifest permissions + +Domain: **1.2 CIS benchmark review** +Target time: **3 minutes** +Reward: **100 XP clean win** + +## Task + +On `controlplane`, kube-bench check `1.1.3` fails because: + +```text +/etc/kubernetes/manifests/kube-controller-manager.yaml mode=0644 owner=root group=root +``` + +Back up the file outside the manifest directory, make its permissions `0600` without changing ownership, verify mode and ownership, then rerun only check `1.1.3` for the master target. + +## Section B — scored answer + +```bash +cp /etc/kubernetes/manifests/kube-controller-manager.yaml ~/kube-controller-manager.yaml-$(date +%Y-%m-%d) +chmod 0600 /etc/kubernetes/manifests/kube-controller-manager.yaml +ls -la /etc/kubernetes/manifests/kube-controller-manager.yaml +kube-bench run --targets master --check 1.1.3 +``` + +When ready, say **Grade CKS-007**. diff --git a/submissions/CKS-008-skipped.md b/submissions/CKS-008-skipped.md new file mode 100644 index 0000000..47a2744 --- /dev/null +++ b/submissions/CKS-008-skipped.md @@ -0,0 +1,27 @@ +# CKS-008 — Correct manifest ownership + +Domain: **1.2 CIS benchmark review** +Time: **2 minutes** · Reward: **100 XP + topic bonus** + +## Task + +On `controlplane`, check `1.1.4` fails: + +```text +/etc/kubernetes/manifests/kube-scheduler.yaml mode=0600 owner=root group=adm +``` + +Required commands: + +- back up outside the manifest directory; +- change ownership to `root:root` without changing mode; +- verify numeric mode and `owner:group`; +- rerun only master check `1.1.4`. + +## Section B — scored answer + +```bash +# Your four commands +``` + +Say **Grade CKS-008** when saved. diff --git a/submissions/CKS-009-reviewed.md b/submissions/CKS-009-reviewed.md new file mode 100644 index 0000000..1429490 --- /dev/null +++ b/submissions/CKS-009-reviewed.md @@ -0,0 +1,85 @@ +# CKS-009 — Add TLS to an existing Ingress + +Domain: **1.3 Ingress TLS** +Time: **6 minutes** · Score: **Section B only** + +## Existing configuration + +Certificate files already exist: + +```text +/opt/certs/store.crt +/opt/certs/store.key +``` + +The complete existing Ingress is: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: storefront + namespace: shop + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + ingressClassName: nginx + rules: + - host: store.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: storefront + port: + number: 80 +``` + +## Task + +1. Inspect the certificate and confirm it covers `store.example.com`. +2. Create TLS Secret `storefront-tls` in namespace `shop` from the supplied files. +3. Update the Ingress for TLS using that Secret without changing its existing route. +4. Verify the Secret type and show the final Ingress. + +## Section B — scored answer + +```bash +k create secret -n shop tls storefront-tls --cert=/opt/certs/store.crt --key=/opt/certs/store.key +``` + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: storefront + namespace: shop + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + +spec: + ingressClassName: nginx + + tls: + - hosts: + - store.example.com + secretName: storefront-tls + rules: + - host: store.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: storefront + port: + number: 80 + +k get ingress -n shop storefront +k describe ingress -n shop storefront +``` + +Say **Grade CKS-009** when saved. diff --git a/submissions/CKS-010-completed.md b/submissions/CKS-010-completed.md new file mode 100644 index 0000000..7bc493e --- /dev/null +++ b/submissions/CKS-010-completed.md @@ -0,0 +1,67 @@ +# CKS-010 — Remove an exposed dashboard endpoint + +Domain: **1.4 Protect node metadata and endpoints** +Time: **4 minutes** · Score: **Section B only** + +## Existing objects + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + type: NodePort + selector: + k8s-app: kubernetes-dashboard + ports: + - port: 443 + targetPort: 8443 + nodePort: 30443 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: dashboard-admin +subjects: +- kind: ServiceAccount + name: kubernetes-dashboard + namespace: kubernetes-dashboard +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +``` + +## Task + +The dashboard must remain reachable only inside the cluster and must no longer hold `cluster-admin` privileges. Modify/delete only what is necessary, then verify both outcomes. + +## Section B — scored answer + +```bash +k delete clusterrolebinding dashboard-admin +k edit service kubernetes-dashboard -n kubernetes-dashboard +kubectl get service kubernetes-dashboard \ + -n kubernetes-dashboard \ + -o jsonpath='{.spec.type}{"\n"}{.spec.ports[*].nodePort}{"\n"}' +kubectl get clusterrolebinding dashboard-admin +``` + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + type: ClusterIP + selector: + k8s-app: kubernetes-dashboard + ports: + - port: 443 + targetPort: 8443 +``` + +Say **Grade CKS-010** when saved. diff --git a/submissions/CKS-010-incomplete.md b/submissions/CKS-010-incomplete.md new file mode 100644 index 0000000..de78a55 --- /dev/null +++ b/submissions/CKS-010-incomplete.md @@ -0,0 +1,66 @@ +# CKS-010 — Remove an exposed dashboard endpoint + +Domain: **1.4 Protect node metadata and endpoints** +Time: **4 minutes** · Score: **Section B only** + +## Existing objects + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + type: NodePort + selector: + k8s-app: kubernetes-dashboard + ports: + - port: 443 + targetPort: 8443 + nodePort: 30443 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: dashboard-admin +subjects: +- kind: ServiceAccount + name: kubernetes-dashboard + namespace: kubernetes-dashboard +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +``` + +## Task + +The dashboard must remain reachable only inside the cluster and must no longer hold `cluster-admin` privileges. Modify/delete only what is necessary, then verify both outcomes. + +## Section B — scored answer + +```bash +k create clusterrole dashboard-role --verb=get,list,update,delete,patch --resource=pods +k delete clusterrolebinding dashboard-admin +k create clusterrolebinding dashboard-role-binding --clusterrole=dashboard-role --serviceaccount=kubernetes-dashboard:kubernetes-dashborad +k auth can-i get pod --as=system:serviceaccount:kubernetes-cluster:kubernetes-cluster +k edit service kubernetes-dashboard -n kubernetes-dashboard +``` + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + type: ClusterIP + selector: + k8s-app: kubernetes-dashboard + ports: + - port: 443 + targetPort: 8443 +``` + +Say **Grade CKS-010** when saved. diff --git a/submissions/CKS-011-completed.md b/submissions/CKS-011-completed.md new file mode 100644 index 0000000..7ac4ffe --- /dev/null +++ b/submissions/CKS-011-completed.md @@ -0,0 +1,29 @@ +# CKS-011 — Verify and install a Kubernetes binary + +Domain: **1.5 Platform binary verification** +Time: **4 minutes** · Score: **Section B only** + +## Supplied files + +```text +/opt/release/kubectl +/opt/release/kubectl.sha256 +``` + +`kubectl.sha256` contains the official raw SHA-256 value but no filename. + +## Task + +Verify the downloaded binary against the checksum. Install it as `/usr/local/bin/kubectl` with owner `root:root` and mode `0755` only if verification succeeds. Then show its client version and installed ownership/mode. + +## Section B — scored answer + +```bash +echo "$(cat /opt/release/kubectl.sha256) /opt/release/kubectl" | sha256sum --check && cp /opt/release/kubectl /usr/local/bin/ || echo "Verification failed" +chown root:root /usr/local/bin/kubectl +chmod 0755 /usr/local/bin/kubectl +stat /usr/local/bin/kubectl +/usr/local/bin/kubectl version +``` + +Say **Grade CKS-011** when saved. diff --git a/submissions/CKS-012-clean-win.md b/submissions/CKS-012-clean-win.md new file mode 100644 index 0000000..e3e6aa4 --- /dev/null +++ b/submissions/CKS-012-clean-win.md @@ -0,0 +1,35 @@ +# CKS-012 — Block cloud metadata + +Domain: **1.4 Protect node metadata and endpoints** +Time: **3 minutes** · Reward: **100 XP clean win** + +## Task + +In namespace `build`, isolate egress for Pods labeled `role=runner`. Allow them to reach any IPv4 destination over TCP `443`, except the metadata endpoint `169.254.169.254/32`. Permit no other egress. + +## Section B — scored answer + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: metadata-protector + namespace: build +spec: + podSelector: + matchLabels: + role: runner + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 169.254.169.254/32 + ports: + - protocol: TCP + port: 443 +``` + +Say **Grade CKS-012** when saved. diff --git a/submissions/CKS-013-completed.md b/submissions/CKS-013-completed.md new file mode 100644 index 0000000..18dfc2e --- /dev/null +++ b/submissions/CKS-013-completed.md @@ -0,0 +1,33 @@ +# CKS-013 — Verify a kubelet binary + +Domain: **1.5 Platform binary verification** +Time: **2 minutes** · Reward: **100 XP clean win** + +## Task + +Files: + +```text +/opt/release/kubelet +/opt/release/kubelet.sha256 # raw official hash only +``` + +Write commands to: + +1. verify the binary with `sha256sum --check`; +2. after observing `OK`, install it at `/usr/local/bin/kubelet` as `root:root` with mode `0755` using `install`; +3. show its version and installed mode/owner. + +No conditional shell scripting is required. + +## Section B — scored answer + +```bash +echo "$(cat /opt/release/kubelet.sha256) /opt/release/kubelet" | sha256sum --check +echo "if ok" +install -o root -g root -m 0755 /opt/release/kubelet /usr/local/bin/kubelet +stat /usr/local/bin/kubelet +/usr/local/bin/kubelet --version +``` + +Say **Grade CKS-013** when saved. diff --git a/submissions/CKS-014-completed.md b/submissions/CKS-014-completed.md new file mode 100644 index 0000000..d8bcc1e --- /dev/null +++ b/submissions/CKS-014-completed.md @@ -0,0 +1,23 @@ +# CKS-014 — Verify kube-apiserver + +Domain: **1.5 Platform binary verification** +Time: **1 minute** · Reward: **100 XP + topic mastery** + +## Task + +The binary and its official raw checksum are: + +```text +/opt/kubernetes/kube-apiserver +/opt/kubernetes/kube-apiserver.sha512 +``` + +Write one command that feeds the raw hash and binary path to `sha512sum --check`. + +## Section B — scored answer + +```bash +echo "$(cat /opt/kubernetes/kube-apiserver.sha512) /opt/kubernetes/kube-apiserver" | sha512sum --check +``` + +Say **Grade CKS-014** when saved. diff --git a/submissions/CKS-015-clean-win.md b/submissions/CKS-015-clean-win.md new file mode 100644 index 0000000..a3a9950 --- /dev/null +++ b/submissions/CKS-015-clean-win.md @@ -0,0 +1,23 @@ +# CKS-015 — Verify a standard checksum file + +Domain: **1.5 Platform binary verification** +Time: **30 seconds** · Reward: **100 XP + topic mastery** + +## Task + +Directory `/opt/release` contains: + +```text +kube-scheduler +kube-scheduler.sha256 +``` + +Unlike earlier tasks, the checksum file already contains both the hash and filename in standard `sha256sum` format. Write the command or commands to verify it. + +## Section B — scored answer + +```bash +sha256sum --check kube-scheduler.sha256 +``` + +Say **Grade CKS-015** when saved. diff --git a/submissions/CKS-016-skipped-by-topic-advance.md b/submissions/CKS-016-skipped-by-topic-advance.md new file mode 100644 index 0000000..e12be8b --- /dev/null +++ b/submissions/CKS-016-skipped-by-topic-advance.md @@ -0,0 +1,41 @@ +# CKS-016 — Replace cluster-admin with namespaced RBAC + +Domain: **2.1 Least-privilege RBAC** +Time: **5 minutes** · Score: **Section B only** + +## Existing dangerous binding + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: maya-admin +subjects: +- kind: User + name: maya +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +``` + +## Task + +Remove this binding. In namespace `blue`, allow user `maya` only to: + +- `get`, `list`, and `watch` Pods; +- `get` Pod logs. + +Create namespaced Role `maya-observer` and RoleBinding `maya-observer`. Include commands that verify one allowed action and that reading Secrets is denied. + +## Section B — scored answer + +```bash +# Commands +``` + +```yaml +# Complete Role and RoleBinding +``` + +Say **Grade CKS-016** when saved. diff --git a/submissions/CKS-017-completed-verification-skipped.md b/submissions/CKS-017-completed-verification-skipped.md new file mode 100644 index 0000000..c543462 --- /dev/null +++ b/submissions/CKS-017-completed-verification-skipped.md @@ -0,0 +1,64 @@ +# CKS-017 — Remove unnecessary ServiceAccount tokens + +Domain: **2.2 Service-account safety** +Time: **5 minutes** · Score: **Section B only** + +## Existing workload + +```yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: reporter + namespace: jobs +spec: + schedule: "*/10 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: reporter + image: registry.example.com/reporter:2.1 + restartPolicy: Never +``` + +## Task + +The workload does not use the Kubernetes API. Create a dedicated ServiceAccount named `reporter`, disable token automounting on it, and update the complete CronJob Pod template to use it and explicitly disable token automounting. Verify the ServiceAccount and CronJob settings. + +## Section B — scored answer + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: reporter + namespace: jobs +automountServiceAccountToken: false +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: reporter + namespace: jobs +spec: + schedule: "*/10 * * * *" + jobTemplate: + spec: + template: + spec: + serviceAccountName: reporter + automountServiceAccountToken: false + containers: + - name: reporter + image: registry.example.com/reporter:2.1 + restartPolicy: Never +``` + +```bash +k create sa reporter -n jobs --dry-run=client -o yaml > reporter-sa.yaml +kubectl apply -f reporter-sa.yaml +``` + +Say **Grade CKS-017** when saved. diff --git a/submissions/CKS-018-completed.md b/submissions/CKS-018-completed.md new file mode 100644 index 0000000..0508dea --- /dev/null +++ b/submissions/CKS-018-completed.md @@ -0,0 +1,97 @@ +# CKS-018 — Disable anonymous API access + +Domain: **2.3 Restrict Kubernetes API access** +Time: **5 minutes** · Score: **Section B only** + +## Complete exercise manifest + +`/etc/kubernetes/manifests/kube-apiserver.yaml`: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: kube-apiserver + namespace: kube-system +spec: + hostNetwork: true + containers: + - name: kube-apiserver + image: registry.k8s.io/kube-apiserver:v1.35.0 + command: + - kube-apiserver + - --advertise-address=10.0.0.10 + - --anonymous-auth=true + - --authorization-mode=Node,RBAC + - --client-ca-file=/etc/kubernetes/pki/ca.crt + - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt + - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt + - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key + - --etcd-servers=https://127.0.0.1:2379 + - --secure-port=6443 + - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt + - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key + volumeMounts: + - name: pki + mountPath: /etc/kubernetes/pki + readOnly: true + volumes: + - name: pki + hostPath: + path: /etc/kubernetes/pki + type: DirectoryOrCreate +``` + +## Task + +Back up the manifest outside its directory, disable anonymous authentication, wait for API recovery, then verify authenticated readiness and that an unauthenticated request to `https://127.0.0.1:6443/api` returns HTTP `401`. + +## Section B — scored answer + +```bash +cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml-$(date +%Y-%m-%d) +nano /etc/kubernetes/manifests/kube-apiserver.yaml +``` + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: kube-apiserver + namespace: kube-system +spec: + hostNetwork: true + containers: + - name: kube-apiserver + image: registry.k8s.io/kube-apiserver:v1.35.0 + command: + - kube-apiserver + - --advertise-address=10.0.0.10 + - --anonymous-auth=false + - --authorization-mode=Node,RBAC + - --client-ca-file=/etc/kubernetes/pki/ca.crt + - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt + - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt + - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key + - --etcd-servers=https://127.0.0.1:2379 + - --secure-port=6443 + - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt + - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key + volumeMounts: + - name: pki + mountPath: /etc/kubernetes/pki + readOnly: true + volumes: + - name: pki + hostPath: + path: /etc/kubernetes/pki + type: DirectoryOrCreate +``` + +```bash +kubectl get --raw='/readyz' +curl -k -s -o /dev/null -w "%{http_code}\n" https://127.0.0.1:6443/api + +``` + +Say **Grade CKS-018** when saved. diff --git a/submissions/CKS-019-completed.md b/submissions/CKS-019-completed.md new file mode 100644 index 0000000..16b9d82 --- /dev/null +++ b/submissions/CKS-019-completed.md @@ -0,0 +1,24 @@ +# CKS-019 — Protect administrator kubeconfig + +Domain: **2.3 Restrict Kubernetes API access** +Time: **2 minutes** · Reward: **100 XP + topic mastery** + +## Task + +On `controlplane`: + +```text +/etc/kubernetes/admin.conf mode=0644 owner=root group=root +``` + +Back it up to `/root/admin.conf.backup`, change only its mode to `0600`, and verify numeric mode plus `owner:group`. + +## Section B — scored answer + +```bash +cp /etc/kubernetes/admin.conf /root/admin.conf.backup +chmod 0600 /etc/kubernetes/admin.conf +stat /etc/kubernetes/admin.conf +``` + +Say **Grade CKS-019** when saved.