CKS_FINALE
This commit is contained in:
197
submissions/CKS-001-attempt-1.md
Normal file
197
submissions/CKS-001-attempt-1.md
Normal file
@@ -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**.
|
||||
63
submissions/CKS-002-skipped.md
Normal file
63
submissions/CKS-002-skipped.md
Normal file
@@ -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**.
|
||||
43
submissions/CKS-003-completed.md
Normal file
43
submissions/CKS-003-completed.md
Normal file
@@ -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**.
|
||||
43
submissions/CKS-004-completed.md
Normal file
43
submissions/CKS-004-completed.md
Normal file
@@ -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**.
|
||||
51
submissions/CKS-005-completed.md
Normal file
51
submissions/CKS-005-completed.md
Normal file
@@ -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**.
|
||||
77
submissions/CKS-006-completed.md
Normal file
77
submissions/CKS-006-completed.md
Normal file
@@ -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**.
|
||||
26
submissions/CKS-007-completed.md
Normal file
26
submissions/CKS-007-completed.md
Normal file
@@ -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**.
|
||||
27
submissions/CKS-008-skipped.md
Normal file
27
submissions/CKS-008-skipped.md
Normal file
@@ -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.
|
||||
85
submissions/CKS-009-reviewed.md
Normal file
85
submissions/CKS-009-reviewed.md
Normal file
@@ -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.
|
||||
67
submissions/CKS-010-completed.md
Normal file
67
submissions/CKS-010-completed.md
Normal file
@@ -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.
|
||||
66
submissions/CKS-010-incomplete.md
Normal file
66
submissions/CKS-010-incomplete.md
Normal file
@@ -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.
|
||||
29
submissions/CKS-011-completed.md
Normal file
29
submissions/CKS-011-completed.md
Normal file
@@ -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.
|
||||
35
submissions/CKS-012-clean-win.md
Normal file
35
submissions/CKS-012-clean-win.md
Normal file
@@ -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.
|
||||
33
submissions/CKS-013-completed.md
Normal file
33
submissions/CKS-013-completed.md
Normal file
@@ -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.
|
||||
23
submissions/CKS-014-completed.md
Normal file
23
submissions/CKS-014-completed.md
Normal file
@@ -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.
|
||||
23
submissions/CKS-015-clean-win.md
Normal file
23
submissions/CKS-015-clean-win.md
Normal file
@@ -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.
|
||||
41
submissions/CKS-016-skipped-by-topic-advance.md
Normal file
41
submissions/CKS-016-skipped-by-topic-advance.md
Normal file
@@ -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.
|
||||
64
submissions/CKS-017-completed-verification-skipped.md
Normal file
64
submissions/CKS-017-completed-verification-skipped.md
Normal file
@@ -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.
|
||||
97
submissions/CKS-018-completed.md
Normal file
97
submissions/CKS-018-completed.md
Normal file
@@ -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.
|
||||
24
submissions/CKS-019-completed.md
Normal file
24
submissions/CKS-019-completed.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user