CKSKubernetes 1.35

Mock exams · Mock paper A — the six domains in order

Answers — Mock paper A — the six domains in order

updated 2026-10-22 by seed

One answer per task, numbered like the paper. Commands assume a RHEL 10 system; check the result before you move on.

Marking notes

6 points per task: 3 for the object existing with the right values, 3 for it
actually working. A task that "looks right" but was applied on the wrong node
scores zero.

Task 1

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny, namespace: prod}
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: allow-dns, namespace: prod}
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
  - to: [{namespaceSelector: {}}]
    ports:
    - {protocol: UDP, port: 53}
    - {protocol: TCP, port: 53}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: frontend-to-backend, namespace: prod}
spec:
  podSelector: {matchLabels: {tier: backend}}
  policyTypes: [Ingress]
  ingress:
  - from: [{podSelector: {matchLabels: {tier: frontend}}}]
    ports: [{protocol: TCP, port: 3000}]

Verify: kubectl exec -n prod deploy/frontend -- wget -qO- http://backend:3000
succeeds; the same from any other Pod times out.

Task 2

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: block-imds, namespace: prod}
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
  - to: [{ipBlock: {cidr: 0.0.0.0/0, except: ["169.254.169.254/32"]}}]
  - to: [{namespaceSelector: {}}]
    ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]

Verify: curl -m2 http://169.254.169.254/ times out from inside a Pod.

Task 3

# /etc/kubernetes/manifests/kube-apiserver.yaml
    - --anonymous-auth=false
    - --profiling=false
sleep 20 && kubectl get --raw /healthz

Backup first: cp kube-apiserver.yaml /root/apiserver.bak. If the API server does
not return, crictl logs the container and fix the YAML.

Task 4

openssl req -x509 -nodes -newkey rsa:2048 -days 365 -keyout /tmp/tls.key -out /tmp/tls.crt -subj "/CN=shop.example.com"
kubectl create secret tls shop-tls --cert=/tmp/tls.crt --key=/tmp/tls.key -n shop
kubectl create ingress shop --class=nginx --rule="shop.example.com/*=shop:80,tls=shop-tls" -n shop

Verify: kubectl describe ingress shop -n shop shows the host and the secret.

Task 5

curl -sLO https://dl.k8s.io/v1.35.0/bin/linux/amd64/kubelet.sha256
echo "$(cat kubelet.sha256)  /usr/local/bin/kubelet" | sha256sum --check

OK → echo ok > /root/kubelet-check.txt; FAILED → echo tampered > ….

Task 6

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: jane-reader, namespace: prod}
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: {name: jane-reader, namespace: prod}
subjects: [{kind: User, name: jane, apiGroup: rbac.authorization.k8s.io}]
roleRef: {kind: Role, name: jane-reader, apiGroup: rbac.authorization.k8s.io}

Verify all four directions with kubectl auth can-i.

Task 7

kubectl patch sa api -n prod -p '{"automountServiceAccountToken": false}'
kubectl patch deployment client -n prod --type=strategic -p \
 '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'
kubectl rollout status deployment/client -n prod

Verify the token file is absent inside the Pod.

Task 8

    - --enable-admission-plugins=NodeRestriction
    - --authorization-mode=Node,RBAC

Verify with ps -ef | grep kube-apiserver (the flags must be visible) and
kubectl get --raw /healthz.

Task 9

sudo tee /etc/apparmor.d/deny-write >/dev/null <<'EOF'
#include <tunables/global>
profile deny-write flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>
  file,
  deny /tmp/** w,
}
EOF
sudo apparmor_parser -r /etc/apparmor.d/deny-write
    securityContext:
      appArmorProfile: {type: Localhost, localhostProfile: deny-write}

Verify cat /proc/self/attr/current inside the Pod prints deny-write.

Task 10

{"defaultAction":"SCMP_ACT_ALLOW",
 "architectures":["SCMP_ARCH_X86_64","SCMP_ARCH_X86","SCMP_ARCH_X32"],
 "syscalls":[{"names":["mkdir","mkdirat"],"action":"SCMP_ACT_ERRNO"}]}

At /var/lib/kubelet/seccomp/profiles/mkdir-deny.json, referenced as
localhostProfile: profiles/mkdir-deny.json. Verify mkdir fails and
/proc/self/status shows Seccomp: 2.

Task 11

The five container-level fields plus runAsNonRoot/runAsUser: 101, and the two
emptyDir mounts nginx needs (/var/cache/nginx, /var/run). Verify Running
after 60 s and that touch /x fails.

Task 12

kubectl label ns locked pod-security.kubernetes.io/enforce=restricted --overwrite
kubectl apply -f bad.yaml --dry-run=server

Then fix the Pod template until the server-side dry run passes: non-root,
allowPrivilegeEscalation: false, drop: ["ALL"], seccompProfile: RuntimeDefault.

Task 13

EncryptionConfiguration with aescbc first and identity second, mounted into
the API server as a hostPath plus --encryption-provider-config, then
kubectl get secrets -A -o json | kubectl replace -f -. Proof:

sudo ETCDCTL_API=3 etcdctl --cacert /etc/kubernetes/pki/etcd/ca.crt \
  --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/default/<name> | strings | head -1
# k8s:enc:aescbc:v1:key1

Task 14

kubectl get pods -n prod -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[*].image}{"\n"}{end}'
trivy image --severity CRITICAL <image>
kubectl get pod <name> -n prod -o jsonpath='{.metadata.ownerReferences[*].kind}'
kubectl delete deployment <name> -n prod     # or: delete pod, if it is a bare Pod

Deleting the Pod of a Deployment is the classic mistake: it comes straight back.

Task 15

Policy with omitStages: ["RequestReceived"], a RequestResponse rule scoped to
config-data for secrets/configmaps, then Metadata catch-all; static pod mounts
for both the policy file (type: File) and the log directory
(type: DirectoryOrCreate, mounted read-write); --audit-log-path,
--audit-log-maxage=30. Verify with a kubectl create secret and jq on the last
line of the log.

Task 16

Whole rule, same name, in /etc/falco/falco_rules.local.yaml, output including
%container.id; file_output enabled at /var/log/falco/events.log,
stdout_output: false; systemctl restart falco; trigger with a write to /etc.

Task 17

kubectl exec -n prod suspect -- ps aux --sort=-%cpu | head
kubectl exec -n prod suspect -- ls -l /proc/1/exe
sudo crictl ps | grep suspect

Write the in-container path of the miner to /root/suspect.txt. A `strace -f -e
trace=execve` on the node against the container PID shows the same answer with the
arguments.

← Back to the questions