diff --git a/CKS-DASHBOARD.html b/CKS-DASHBOARD.html
new file mode 100644
index 0000000..20314ee
--- /dev/null
+++ b/CKS-DASHBOARD.html
@@ -0,0 +1,3 @@
+
CKS mastery dashboard CKS mastery dashboard · updated 27 August 2026
Build confidence into evidence. Lab ready · Kubernetes v1.34 · Linux tools installed · 2 CIS labs passed today
22 of 26 topics have practical training evidence. Hero is deliberately strict: tool drill, capstone, evidence, scope review, and cold retest.
22 training
4 not started
0 heroes—yet
Coverage by domain training / total
CS-02 Hero checklist CIS components Tool drills kube-bench, etcd, and kubelet practised
Timed capstone multi-component remediation
Evidence & cold retest saved PASS groups, then repeat later
Next win
CS-02.Q4 — secure CoreDNS component review Deployment, ServiceAccount, RBAC, ConfigMap, image, and security context.
Open checklist
diff --git a/CKS-MASTERY-CHECKLIST.md b/CKS-MASTERY-CHECKLIST.md
index e5d872d..4f5f1e1 100644
--- a/CKS-MASTERY-CHECKLIST.md
+++ b/CKS-MASTERY-CHECKLIST.md
@@ -58,18 +58,18 @@ Minor syntax corrections during training are expected. A cold retest fails when
## Next Task
-Finish the one-time local lab installation after Windows restarts, then run `CS-02.Q1` as revised Lesson 0036 on `kind-cks-lab`. Do not use the learner's Kubernetes v1.36 production cluster: kube-bench v0.16.0 has automatic version mappings only through v1.34.
+Run `CS-02.Q4`: review CoreDNS deployment, ServiceAccount, RBAC, ConfigMap, image, and security context; distinguish CIS-relevant component configuration from workload hardening. Do not use the learner's Kubernetes v1.36 production cluster.
## Local Lab Environment
-Status: `WINDOWS RESTART REQUIRED`
+Status: `READY`
- Platform: Ubuntu 24.04 on WSL2, Docker Engine, two-node Kind cluster, and Calico.
- Kubernetes: pinned to v1.34.0; kube-bench: pinned to v0.16.0.
- Cluster and context: `cks-lab` / `kind-cks-lab`.
- Kubeconfig: `/opt/cks-lab/kubeconfig`; it is never merged with a production config.
- Setup and recovery files: `lab-environment/`.
-- Resume after restart: run `lab-environment\\02-install-and-build.ps1`, then `lab-environment\\verify-lab.ps1`.
+- Enter the ready lab with `lab-environment\\enter-lab.ps1`; verify or rebuild it with `lab-environment\\verify-lab.ps1` and `lab-environment\\02-install-and-build.ps1`.
- Lesson scope: use Kind nodes for Kubernetes tasks and the disposable Ubuntu WSL2 VM for host/kernel hardening tasks.
---
@@ -603,6 +603,9 @@ Append one row after every graded attempt.
| Date | Task | Topic | Time | Result | Critical errors | Evidence | Next action |
|---|---|---|---:|---|---:|---|---|
+| 2026-08-27 | Lesson 0038 | CS-02 | Not recorded | Training pass | 0 after verifier correction | `lab/lesson-38-verify.sh /tmp/lesson-38-kubelet.yaml`: all PASS | Continue CS-02.Q4 |
+| 2026-08-27 | Lesson 0037 | CS-02 | Not recorded | Training pass | 0 after one correction | `lab/lesson-37-verify.sh /tmp/lesson-37-etcd.yaml`: all PASS | Continue CS-02.Q3 |
+| 2026-08-27 | Lesson 0036 | CS-02 | 90 minutes | Partial training evidence | etcd and worker checks not captured | `lab/lesson-36-controlplane-after.txt` shows 1.2.1 and 1.2.7 PASS | Run focused CS-02.Q2, then require PASS evidence |
| 2026-08-26 | Lesson 0035 | CH-03 / MR-05 | Not recorded | Training pass | 0 after review | `lab/lesson-35-kube-apiserver-answer.yaml`, `lab/lesson-35-audit-policy.yaml` | Cold retest later |
| 2026-08-26 | Lesson 0034 | CS-01 / MM-01 | Not recorded | Training pass | 0 after review | `lab/lesson-34-answer.yaml` | Cold retest later |
| 2026-08-25 | Lesson 0032 | MR-03 | Not recorded | Training; evidence incomplete | 0 substantive; command omitted | `lab/lesson-32-answer.md` | Require structured query next time |
diff --git a/README.md b/README.md
index e69de29..359b175 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,3 @@
+# CKS training workspace
+
+Start with the [CKS Mastery Dashboard](CKS-DASHBOARD.html). The [Mastery Checklist](CKS-MASTERY-CHECKLIST.md) is the canonical record of evidence, Hero gates, and the next task.
diff --git a/lab-environment/bootstrap.sh b/lab-environment/bootstrap.sh
index 47051d7..bc73dcb 100644
--- a/lab-environment/bootstrap.sh
+++ b/lab-environment/bootstrap.sh
@@ -20,7 +20,15 @@ EOF
export DEBIAN_FRONTEND=noninteractive
apt-get update
-apt-get install -y ca-certificates curl gnupg jq vim git iproute2 iptables conntrack socat
+apt-get install -y \
+ bash-completion ca-certificates conntrack curl dnsutils git gnupg iproute2 \
+ iptables iputils-ping jq less lsof man-db nano netcat-openbsd procps psmisc \
+ socat strace tcpdump tree vim wget yq
+
+cat >/etc/profile.d/cks-lab.sh <<'EOF'
+alias k=kubectl
+complete -o default -F __start_kubectl k 2>/dev/null || true
+EOF
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
@@ -45,20 +53,20 @@ curl -fsSLo "$tmp_dir/kubectl.sha256" "https://dl.k8s.io/release/${KUBECTL_VERSI
printf '%s %s\n' "$(cat "$tmp_dir/kubectl.sha256")" "$tmp_dir/kubectl" | sha256sum --check --status
install -m 0755 "$tmp_dir/kubectl" /usr/local/bin/kubectl
-curl -fsSLo "$tmp_dir/kind" "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64"
+curl -fsSLo "$tmp_dir/kind-linux-amd64" "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64"
curl -fsSLo "$tmp_dir/kind.sha256sum" "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64.sha256sum"
(cd "$tmp_dir" && sha256sum --check --status kind.sha256sum)
-install -m 0755 "$tmp_dir/kind" /usr/local/bin/kind
+install -m 0755 "$tmp_dir/kind-linux-amd64" /usr/local/bin/kind
-curl -fsSLo "$tmp_dir/kube-bench.tar.gz" \
+curl -fsSLo "$tmp_dir/kube-bench_${KUBE_BENCH_VERSION}_linux_amd64.tar.gz" \
"https://github.com/aquasecurity/kube-bench/releases/download/v${KUBE_BENCH_VERSION}/kube-bench_${KUBE_BENCH_VERSION}_linux_amd64.tar.gz"
curl -fsSLo "$tmp_dir/checksums.txt" \
"https://github.com/aquasecurity/kube-bench/releases/download/v${KUBE_BENCH_VERSION}/kube-bench_${KUBE_BENCH_VERSION}_checksums.txt"
(cd "$tmp_dir" && grep " kube-bench_${KUBE_BENCH_VERSION}_linux_amd64.tar.gz$" checksums.txt | sha256sum --check --status)
rm -rf /opt/cks-lab/tools/kube-bench
install -d /opt/cks-lab/tools/kube-bench
-tar -xzf "$tmp_dir/kube-bench.tar.gz" -C /opt/cks-lab/tools/kube-bench
+tar -xzf "$tmp_dir/kube-bench_${KUBE_BENCH_VERSION}_linux_amd64.tar.gz" -C /opt/cks-lab/tools/kube-bench
ln -sfn /opt/cks-lab/tools/kube-bench/kube-bench /usr/local/bin/kube-bench
-printf 'Installed kubectl %s, Kind %s, and kube-bench %s.\n' \
+printf 'Installed CKS command-line tools plus kubectl %s, Kind %s, and kube-bench %s.\n' \
"$KUBECTL_VERSION" "$KIND_VERSION" "$KUBE_BENCH_VERSION"
diff --git a/lab-environment/create-lab.sh b/lab-environment/create-lab.sh
index a30226f..b230884 100644
--- a/lab-environment/create-lab.sh
+++ b/lab-environment/create-lab.sh
@@ -31,9 +31,9 @@ if [[ "$context" != "kind-${CLUSTER_NAME}" ]]; then
exit 1
fi
-kubectl apply -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/operator-crds.yaml"
-kubectl apply -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/tigera-operator.yaml"
-kubectl apply -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/custom-resources.yaml"
+kubectl apply --server-side --validate=false -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/operator-crds.yaml"
+kubectl apply --server-side --validate=false -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/tigera-operator.yaml"
+kubectl apply --server-side --validate=false -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/custom-resources.yaml"
kubectl wait --for=condition=Available deployment/tigera-operator -n tigera-operator --timeout=300s
kubectl wait --for=condition=Ready nodes --all --timeout=600s
@@ -43,4 +43,3 @@ for node in "${CLUSTER_NAME}-control-plane" "${CLUSTER_NAME}-worker"; do
docker cp /opt/cks-lab/tools/kube-bench "$node":/opt/kube-bench
docker exec "$node" /opt/kube-bench/kube-bench version
done
-
diff --git a/lab-environment/enter-lab.ps1 b/lab-environment/enter-lab.ps1
index 69ec1f5..75ba54d 100644
--- a/lab-environment/enter-lab.ps1
+++ b/lab-environment/enter-lab.ps1
@@ -1,17 +1,9 @@
$ErrorActionPreference = 'Stop'
$distribution = 'Ubuntu-24.04'
+$linuxScript = (& wsl.exe --distribution $distribution --user root --exec wslpath -a "$PSScriptRoot/enter-lab.sh").Trim()
+if (-not $linuxScript) { throw 'Could not locate the Linux lab entry script.' }
-& wsl.exe --distribution $distribution --user root --exec bash -lc @'
-export KUBECONFIG=/opt/cks-lab/kubeconfig
-context="$(kubectl config current-context 2>/dev/null || true)"
-if [[ "$context" != "kind-cks-lab" ]]; then
- echo "Safety check failed: expected kind-cks-lab, got ${context:-none}." >&2
- exit 1
-fi
-echo "CKS lab: $(kubectl version --output=json | jq -r .serverVersion.gitVersion)"
-echo "Context: $context"
-exec bash
-'@
+& wsl.exe --distribution $distribution --user root --exec bash $linuxScript
if ($LASTEXITCODE -ne 0) { throw 'Could not enter the isolated CKS lab.' }
diff --git a/lab-environment/enter-lab.sh b/lab-environment/enter-lab.sh
new file mode 100644
index 0000000..5ef4584
--- /dev/null
+++ b/lab-environment/enter-lab.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+export KUBECONFIG=/opt/cks-lab/kubeconfig
+expected_context=kind-cks-lab
+context="$(kubectl config current-context 2>/dev/null || true)"
+
+if [[ "$context" != "$expected_context" ]]; then
+ echo "Safety check failed: expected $expected_context, got ${context:-none}." >&2
+ exit 1
+fi
+
+echo "CKS lab: $(kubectl version --output=json | jq -r .serverVersion.gitVersion)"
+echo "Context: $context"
+required_tools=(kubectl kube-bench nano vim curl wget jq yq dig nc ss ps less)
+missing_tools=()
+for tool in "${required_tools[@]}"; do
+ command -v "$tool" >/dev/null 2>&1 || missing_tools+=("$tool")
+done
+if (( ${#missing_tools[@]} )); then
+ echo "Lab setup incomplete; missing: ${missing_tools[*]}. Run ./02-install-and-build.ps1." >&2
+ exit 1
+fi
+echo "Tools: ready (nano, curl, wget, jq, yq, dig, nc, tcpdump, ss); alias k=kubectl"
+exec bash
diff --git a/lab/lesson-36-controlplane-after.txt b/lab/lesson-36-controlplane-after.txt
new file mode 100644
index 0000000..bb3124c
--- /dev/null
+++ b/lab/lesson-36-controlplane-after.txt
@@ -0,0 +1,17 @@
+root@cks-lab-control-plane:/opt/kube-bench# ./kube-bench --benchmark cis-1.12 --config-dir cfg run --targets master --check 1.2.1,1.2.7,2.2,2.3
+[INFO] 1 Control Plane Security Configuration
+[INFO] 1.2 API Server
+[PASS] 1.2.1 Ensure that the --anonymous-auth argument is set to false (Manual)
+[PASS] 1.2.7 Ensure that the --authorization-mode argument includes Node (Automated)
+
+== Summary master ==
+2 checks PASS
+0 checks FAIL
+0 checks WARN
+0 checks INFO
+
+== Summary total ==
+2 checks PASS
+0 checks FAIL
+0 checks WARN
+0 checks INFO
\ No newline at end of file
diff --git a/lab/lesson-36-controlplane-before.txt b/lab/lesson-36-controlplane-before.txt
index 374e577..597c2e1 100644
--- a/lab/lesson-36-controlplane-before.txt
+++ b/lab/lesson-36-controlplane-before.txt
@@ -1,6 +1,7 @@
[INFO] 1 Control Plane Security Configuration
[INFO] 1.2 API Server
[WARN] 1.2.1 Ensure that the --anonymous-auth argument is set to false (Manual)
+[PASS] 1.2.7 Ensure that the --authorization-mode argument includes Node (Automated)
== Remediations master ==
1.2.1 Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml
@@ -9,191 +10,13 @@ on the control plane node and set the below parameter.
== Summary master ==
-0 checks PASS
+1 checks PASS
0 checks FAIL
1 checks WARN
0 checks INFO
-[INFO] 2 Etcd Node Configuration
-
-== Summary etcd ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 3 Control Plane Configuration
-
-== Summary controlplane ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 4 Worker Node Security Configuration
-
-== Summary node ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 5 Kubernetes Policies
-
-== Summary policies ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
== Summary total ==
-0 checks PASS
+1 checks PASS
0 checks FAIL
1 checks WARN
0 checks INFO
-
-
-[INFO] 1 Control Plane Security Configuration
-[INFO] 1.2 API Server
-[PASS] 1.2.7 Ensure that the --authorization-mode argument includes Node (Automated)
-
-== Summary master ==
-1 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 2 Etcd Node Configuration
-
-== Summary etcd ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 3 Control Plane Configuration
-
-== Summary controlplane ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 4 Worker Node Security Configuration
-
-== Summary node ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 5 Kubernetes Policies
-
-== Summary policies ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-== Summary total ==
-1 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-
-[INFO] 1 Control Plane Security Configuration
-
-== Summary master ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 2 Etcd Node Configuration
-[INFO] 2 Etcd Node Configuration
-[PASS] 2.2 Ensure that the --client-cert-auth argument is set to true (Automated)
-
-== Summary etcd ==
-1 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 3 Control Plane Configuration
-
-== Summary controlplane ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 4 Worker Node Security Configuration
-
-== Summary node ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 5 Kubernetes Policies
-
-== Summary policies ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-== Summary total ==
-1 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 1 Control Plane Security Configuration
-
-== Summary master ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 2 Etcd Node Configuration
-[INFO] 2 Etcd Node Configuration
-[PASS] 2.3 Ensure that the --auto-tls argument is not set to true (Automated)
-
-== Summary etcd ==
-1 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 3 Control Plane Configuration
-
-== Summary controlplane ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 4 Worker Node Security Configuration
-
-== Summary node ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-[INFO] 5 Kubernetes Policies
-
-== Summary policies ==
-0 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
-
-== Summary total ==
-1 checks PASS
-0 checks FAIL
-0 checks WARN
-0 checks INFO
diff --git a/lab/lesson-36-worker-before.txt b/lab/lesson-36-worker-before.txt
new file mode 100644
index 0000000..a8c834d
--- /dev/null
+++ b/lab/lesson-36-worker-before.txt
@@ -0,0 +1,15 @@
+root@cks-lab-worker:/opt/kube-bench# ./kube-bench --benchmark cis-1.12 --config-dir cfg run --targets master --check 4.2.1,4.2.2,4.2.3
+Warning: Kubernetes version was not auto-detected because kubectl could not connect to the Kubernetes server. This may be because the kubeconfig information is missing or has credentials that do not match the server. Assuming default version 1.18
+[INFO] 1 Control Plane Security Configuration
+
+== Summary master ==
+0 checks PASS
+0 checks FAIL
+0 checks WARN
+0 checks INFO
+
+== Summary total ==
+0 checks PASS
+0 checks FAIL
+0 checks WARN
+0 checks INFO
diff --git a/lab/lesson-37-etcd-insecure.yaml b/lab/lesson-37-etcd-insecure.yaml
new file mode 100644
index 0000000..792e5a2
--- /dev/null
+++ b/lab/lesson-37-etcd-insecure.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: etcd
+ namespace: kube-system
+ labels:
+ component: etcd
+spec:
+ hostNetwork: true
+ containers:
+ - name: etcd
+ image: registry.k8s.io/etcd:3.6.4-0
+ command:
+ - etcd
+ - --name=controlplane
+ - --data-dir=/var/lib/etcd
+ - --listen-client-urls=http://127.0.0.1:2379,https://10.0.0.10:2379
+ - --advertise-client-urls=https://10.0.0.10:2379
+ - --initial-advertise-peer-urls=https://10.0.0.10:2380
+ - --listen-peer-urls=https://10.0.0.10:2380
+ - --initial-cluster=controlplane=https://10.0.0.10:2380
+ - --client-cert-auth=false
+ - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
+ - --cert-file=/etc/kubernetes/pki/etcd/server.crt
+ - --key-file=/etc/kubernetes/pki/etcd/server.key
+ - --auto-tls=true
+ - --peer-client-cert-auth=false
+ - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
+ - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
+ - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
+ - --peer-auto-tls=true
+ - --listen-metrics-urls=http://0.0.0.0:2381
+ volumeMounts:
+ - name: etcd-data
+ mountPath: /var/lib/etcd
+ - name: etcd-certs
+ mountPath: /etc/kubernetes/pki/etcd
+ readOnly: true
+ volumes:
+ - name: etcd-data
+ hostPath:
+ path: /var/lib/etcd
+ type: DirectoryOrCreate
+ - name: etcd-certs
+ hostPath:
+ path: /etc/kubernetes/pki/etcd
+ type: DirectoryOrCreate
diff --git a/lab/lesson-37-verify.sh b/lab/lesson-37-verify.sh
new file mode 100644
index 0000000..1b5fc4a
--- /dev/null
+++ b/lab/lesson-37-verify.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+manifest=${1:?Usage: lesson-37-verify.sh PATH_TO_MANIFEST}
+[[ -f "$manifest" ]] || { echo "FAIL manifest not found"; exit 1; }
+
+required=(
+ '--client-cert-auth=true'
+ '--auto-tls=false'
+ '--peer-client-cert-auth=true'
+ '--peer-auto-tls=false'
+ '--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt'
+ '--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt'
+ '--listen-metrics-urls=http://127.0.0.1:2381'
+)
+for flag in "${required[@]}"; do
+ if grep -Fqx " - $flag" "$manifest"; then
+ echo "PASS $flag"
+ else
+ echo "FAIL $flag"
+ fi
+done
+
+if grep -Eq 'http://0\.0\.0\.0:2381|http://127\.0\.0\.1:2379' "$manifest"; then
+ echo 'FAIL insecure etcd listener remains'
+else
+ echo 'PASS no insecure etcd listener'
+fi
diff --git a/lab/lesson-38-effective-args.txt b/lab/lesson-38-effective-args.txt
new file mode 100644
index 0000000..0015a09
--- /dev/null
+++ b/lab/lesson-38-effective-args.txt
@@ -0,0 +1 @@
+/usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/tmp/lesson-38-kubelet.yaml --anonymous-auth=false --authentication-token-webhook=true --authorization-mode=Webhook --read-only-port=0 --rotate-certificates=true
diff --git a/lab/lesson-38-kubelet-config.yaml b/lab/lesson-38-kubelet-config.yaml
new file mode 100644
index 0000000..5133f61
--- /dev/null
+++ b/lab/lesson-38-kubelet-config.yaml
@@ -0,0 +1,11 @@
+apiVersion: kubelet.config.k8s.io/v1beta1
+kind: KubeletConfiguration
+authentication:
+ anonymous:
+ enabled: true
+ webhook:
+ enabled: false
+authorization:
+ mode: AlwaysAllow
+readOnlyPort: 10255
+rotateCertificates: false
diff --git a/lab/lesson-38-verify.sh b/lab/lesson-38-verify.sh
new file mode 100644
index 0000000..21503ff
--- /dev/null
+++ b/lab/lesson-38-verify.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+config=${1:?Usage: lesson-38-verify.sh PATH_TO_CONFIG}
+[[ -f "$config" ]] || { echo "FAIL configuration not found"; exit 1; }
+
+check() {
+ local label=$1 expected=$2 query=$3 actual
+ actual="$(yq -r "$query" "$config")"
+ if [[ "$actual" == "$expected" ]]; then echo "PASS $label"; else echo "FAIL $label (got: $actual)"; fi
+}
+
+check 'anonymous authentication disabled' false '.authentication.anonymous.enabled'
+check 'webhook authentication enabled' true '.authentication.webhook.enabled'
+check 'webhook authorization selected' Webhook '.authorization.mode'
+check 'read-only port disabled' 0 '.readOnlyPort'
+check 'certificate rotation enabled' true '.rotateCertificates'
diff --git a/learning-records/0011-etcd-transport-and-metrics-hardening.md b/learning-records/0011-etcd-transport-and-metrics-hardening.md
new file mode 100644
index 0000000..76b45f2
--- /dev/null
+++ b/learning-records/0011-etcd-transport-and-metrics-hardening.md
@@ -0,0 +1,7 @@
+# etcd transport and metrics hardening
+
+The learner independently remediated an etcd static manifest to require client and peer certificates, disable automatic TLS, eliminate HTTP client exposure, and bind metrics to loopback while preserving the existing CA and certificate paths. This establishes a reliable baseline for the next CIS drill: reconciling kubelet configuration with its effective process.
+
+## Evidence
+
+`lab/lesson-37-verify.sh /tmp/lesson-37-etcd.yaml` reported every required control as PASS on 2026-08-27.
diff --git a/learning-records/0012-kubelet-effective-configuration.md b/learning-records/0012-kubelet-effective-configuration.md
new file mode 100644
index 0000000..e7ab06d
--- /dev/null
+++ b/learning-records/0012-kubelet-effective-configuration.md
@@ -0,0 +1,7 @@
+# Kubelet configuration and effective flags
+
+The learner reconciled an insecure KubeletConfiguration with approved effective flags, independently setting anonymous authentication off, webhook authentication and authorization on, the read-only port to zero, and certificate rotation on. Future CIS practice can now combine configuration-file review with process-argument verification.
+
+## Evidence
+
+`lab/lesson-38-verify.sh /tmp/lesson-38-kubelet.yaml` reported all five controls as PASS on 2026-08-27.
diff --git a/lessons/0037-etcd-static-manifest-hardening.html b/lessons/0037-etcd-static-manifest-hardening.html
new file mode 100644
index 0000000..b777eb9
--- /dev/null
+++ b/lessons/0037-etcd-static-manifest-hardening.html
@@ -0,0 +1,4 @@
+Lesson 37: etcd static-manifest hardening Lesson 0037 · 15 minutes · Linux performance lab · Cluster Setup
Harden etcd without breaking membership This is a focused CIS remediation drill. It deliberately removes the cluster-entry and package-installation friction from the previous attempt.
Scope discipline: change only the insecure transport, authentication, and metrics flags. Preserve identity, data, listen/advertise peer addresses, initial cluster membership, certificates, mounts, and image.
Task On the Linux lab shell, create /tmp/lesson-37-etcd.yaml from lab/lesson-37-etcd-insecure.yaml. Make it pass the supplied verifier.
Require client and peer client certificates. Disable automatic TLS for both client and peer traffic. Keep the existing explicit CA, certificate, and key paths. Ensure client traffic uses HTTPS only. Bind the metrics listener only to 127.0.0.1:2381. Do not edit the original fixture or verifier. Do not use kubectl apply; this is a static-manifest file exercise.
Start and prove it cd /mnt/c/Cloud-Team/04_Learning_and_Reference/Training/CKS
+cp lab/lesson-37-etcd-insecure.yaml /tmp/lesson-37-etcd.yaml
+nano /tmp/lesson-37-etcd.yaml
+bash lab/lesson-37-verify.sh /tmp/lesson-37-etcd.yamlCompletion means every line reports PASS. Send that output only.
Documentation Use the official etcd transport security guide if needed. The CKS objective is preserving the required configuration while closing only the proven exposure.
diff --git a/lessons/0038-kubelet-effective-configuration.html b/lessons/0038-kubelet-effective-configuration.html
new file mode 100644
index 0000000..71dfa46
--- /dev/null
+++ b/lessons/0038-kubelet-effective-configuration.html
@@ -0,0 +1,6 @@
+Lesson 38: kubelet effective configuration Lesson 0038 · 15 minutes · Linux performance lab · Cluster Setup
Reconcile kubelet configuration with effective flags Kubelet security faults often come from believing a configuration file is the whole truth. Inspect the effective arguments, then make the structured configuration agree with the required boundary.
Scope: the effective arguments in this task are the approved cluster policy. Correct the supplied KubeletConfiguration; do not change the argument file or add unrelated fields.
Task On the Linux lab shell, create /tmp/lesson-38-kubelet.yaml from lab/lesson-38-kubelet-config.yaml. It must match the security posture expressed by lab/lesson-38-effective-args.txt.
Bring all five controls into agreement: anonymous authentication, webhook authentication, authorization mode, read-only port, and certificate rotation.
Start and verify cd /mnt/c/Cloud-Team/04_Learning_and_Reference/Training/CKS
+cat lab/lesson-38-effective-args.txt
+cp lab/lesson-38-kubelet-config.yaml /tmp/lesson-38-kubelet.yaml
+nano /tmp/lesson-38-kubelet.yaml
+bash lab/lesson-38-verify.sh /tmp/lesson-38-kubelet.yamlCompletion means five PASS lines. Send only that output.
Exam-speed inspection docker exec cks-lab-worker ps -ef | grep '[k]ubelet'
+docker exec cks-lab-worker sed -n '1,120p' /var/lib/kubelet/config.yamlUse these after the task to compare the fixture with the real disposable worker. Do not modify the live worker configuration for this drill.