CKSKubernetes 1.35

Mock exams · Mock paper B — mixed and timed

Answers — Mock paper B — mixed and timed

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.

Task 1

spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
  - to: [{ipBlock: {cidr: 10.0.0.0/8, except: ["10.0.5.0/24"]}}]
  - to: [{namespaceSelector: {}}]
    ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]

Note the two separate to items: an ipBlock cannot live in the same item as a
namespaceSelector.

Task 2

# /var/lib/kubelet/config.yaml
authentication:
  anonymous: {enabled: false}
  webhook: {enabled: true}
authorization: {mode: Webhook}
readOnlyPort: 0
sudo systemctl daemon-reload && sudo systemctl restart kubelet
ss -tulpn | grep 10255          # nothing
kubectl get nodes               # still Ready

Task 3

    - --tls-min-version=VersionTLS12

Verify with
openssl s_client -connect 127.0.0.1:6443 -tls1 </dev/null → handshake failure,
and the same with -tls1_2 → success. Removing --tls-max-version also matters
if the cluster had pinned TLS 1.3 only.

Task 4

apiVersion: v1
kind: ServiceAccount
metadata: {name: report, namespace: prod}
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: report, namespace: prod}
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: {name: report, namespace: prod}
subjects: [{kind: ServiceAccount, name: report}]
roleRef: {kind: Role, name: report, apiGroup: rbac.authorization.k8s.io}

Verify with kubectl auth can-i --list --as=system:serviceaccount:prod:report.

Task 5

kubectl get clusterrole wide -o yaml > /tmp/wide.yaml
# replace rules with: apiGroups [""], resources ["configmaps"], verbs ["get"]
kubectl apply -f /tmp/wide.yaml
kubectl auth can-i get secrets --as=system:serviceaccount:prod:app   # must be no
kubectl auth can-i get configmaps --as=system:serviceaccount:prod:app

The trap: if a workload actually reads Secrets, "reduce" means reduce to what it
uses — check the Pods in prod before deleting the whole rule.

Task 6

      securityContext:
        seccompProfile: {type: RuntimeDefault}

Pod-level is enough for every container. Verify
kubectl get pod -n prod -l app=api -o jsonpath='{..seccompProfile.type}'.

Task 7

readOnlyRootFilesystem: true plus two emptyDir volumes mounted at
/var/cache/app and /tmp. Verify Running and that a write outside those paths
fails.

Task 8

kubectl label ns ml \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted --overwrite

A hostNetwork: true Pod is admitted (baseline allows it in this scenario's
version of the standard) and kubectl prints `Warning: would violate PodSecurity
"restricted"`. If the Pod is rejected instead, the enforce level is wrong.

Task 9

ConstraintTemplate k8sallowedrepos with startswith(container.image, repo),
Constraint with parameters.repos: ["registry.example.com/"] and
match.kinds: [{apiGroups: [""], kinds: ["Pod"]}], scoped by
match.namespaces: ["prod"]. Verify with a kubectl run using nginx:1.27 —
it must be rejected with comes from an untrusted registry.

Task 10

    volumeMounts:
    - {name: db, mountPath: /etc/db, readOnly: true}
  volumes:
  - name: db
    secret:
      secretName: db
      defaultMode: 0400

Remove the env.valueFrom.secretKeyRef entries. Verify the files exist with mode
-r-------- and that no Secret value appears in kubectl exec -- env.

Task 11

spec:
  runtimeClassName: gvisor

Verify kubectl get pod isolated -n prod -o jsonpath='{.spec.runtimeClassName}'
and, on the node, crictl inspect <id> | grep -i runtimeType.

Task 12

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: {name: default, namespace: prod}
spec:
  mtls: {mode: STRICT}

Verify kubectl get peerauthentication -n prod -o yaml and that every Pod has
2/2 containers.

Task 13

gcr.io/distroless/nodejs22-debian12@sha256:<digest>, or the smallest image that
still carries the runtime the app needs. "Pinned by digest" is checked literally:
the image: string must contain @sha256:.

Task 14

bom generate --image registry.example.com/app:1.4.2 -o /tmp/sbom.spdx
bom document query /tmp/sbom.spdx 'name:openssl'
echo yes > /root/sbom-answer.txt     # or: no

bom document outline also works; the answer is the presence of the package, not
the format.

Task 15

sudo grep '"verb":"delete"' /var/log/kubernetes/audit.log \
  | jq -r 'select(.objectRef.resource=="secrets") | .user.username' | sort -u > /root/deleters.txt
cat /root/deleters.txt

If the log is not JSON-per-line on the node, jq -c each line separately. Watch
for system:serviceaccount:... users — those are the interesting ones.

Task 16

# /etc/falco/falco_rules.local.yaml
- rule: Terminal shell in container
  desc: a shell was spawned in a container with an attached terminal
  condition: >
    spawned_process and container and shell_procs and proc.tty != 0
    and container_entrypoint and k8s.ns.name = "prod"
  output: "Shell in container (pod=%k8s.pod.name ns=%k8s.ns.name image=%container.image.repository)"
  priority: NOTICE
  tags: [container, shell]

Keep the whole rule and change only condition and output;
systemctl restart falco; trigger with kubectl exec -it -n prod <pod> -- bash
and tail /var/log/falco/events.log.

Task 17

kubectl get pods -A -o json | jq -r '
 .items[]
 | select(.spec.hostNetwork==true or .spec.hostPID==true
          or any(.spec.containers[]; .securityContext.privileged==true)
          or any(.spec.containers[]; (.securityContext.runAsNonRoot//false)==false))
 | "\(.metadata.namespace)/\(.metadata.name)"' | sort -u > /root/insecure-pods.txt

Read the task literally: privileged, hostNetwork, or root — not "missing
readOnlyRootFilesystem".

← Back to the questions