CKS — Mock Exam Part 2
Mock Exam Instructions
This mock exam covers CKS Domains 4–6: Minimize Microservice Vulnerabilities, Supply Chain Security, and Monitoring/Logging/Runtime Security.
Rules
- Time limit: 60 minutes for all 8 questions
- Open book: Use only kubernetes.io/docs, kubernetes.io/blog, github.com/kubernetes, falco.org/docs, and aquasecurity.github.io/trivy
- No peeking at answers until you have attempted every question or time runs out
- Score yourself honestly — partial credit for partially correct answers
Estimated Time per Question
| Question | Topic | Est. Time | Points |
|---|---|---|---|
| Q1 | PSA: Enforce Restricted Level | 3 min | 6 |
| Q2 | OPA Gatekeeper: Block Untrusted Registries | 10 min | 14 |
| Q3 | Trivy: Scan Image for CRITICAL Vulns | 4 min | 8 |
| Q4 | Encryption at Rest: aescbc Provider | 8 min | 12 |
| Q5 | Falco: Detect Shell in Containers | 6 min | 10 |
| Q6 | Audit Policy: Secrets + Metadata | 8 min | 14 |
| Q7 | Immutable Pod: Full Hardening | 5 min | 10 |
| Q8 | NetworkPolicy: Block Cloud Metadata | 5 min | 8 |
| Total | 49 min | 82 |
Question 1: Enforce PSA Restricted Level on Namespace
Context: kubectl config use-context cluster1-admin@cluster1
Scenario:
The namespace restricted-ns exists. The security team requires that all pods in this namespace comply with the Pod Security Standards Restricted level. Non-compliant pods should be rejected at admission time.
Task:
- Label namespace
restricted-nsto enforce therestrictedPod Security Standard. - Also add warn and audit labels at the
restrictedlevel. - Set the version to
latestfor all three modes.
Verification:
- Attempting to create a privileged pod in
restricted-nsshould be rejected - A compliant pod (non-root, drop ALL, readOnlyRootFilesystem) should be accepted
Answer: Question 1
Step 1: Label the namespace
kubectl label namespace restricted-ns \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest
Step 2: Verify labels
kubectl get namespace restricted-ns --show-labels
Step 3: Test — non-compliant pod should be rejected
kubectl run test-priv --image=nginx --privileged=true -n restricted-ns
# Should be REJECTED with a message about violating the restricted policy
Step 4: Test — compliant pod should be accepted
# compliant-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: compliant-pod
namespace: restricted-ns
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
kubectl apply -f compliant-pod.yaml
# Should be accepted (though the pod may have runtime errors due to nginx needing write access)
Key points:
- PSA has three modes:
enforce(reject),warn(allow but warn),audit(allow and log) - The
restrictedlevel requires: non-root, no privilege escalation, drop ALL capabilities, Seccomp profile enforce-version=latestuses the latest version of the standards- This is a quick 2–3 minute question on the exam — do not overthink it
Question 2: OPA Gatekeeper — Block Untrusted Image Registries
Context: kubectl config use-context cluster1-admin@cluster1
Scenario:
The security team mandates that only images from trusted registries can be deployed in the cluster. Trusted registries are:
docker.io/library/gcr.io/mycompany/registry.internal.io/
Any pod using an image from an untrusted registry must be rejected.
Task:
- Create an OPA Gatekeeper
ConstraintTemplatenamedk8sallowedregistriesthat checks whether container images come from an allowed list of registries. - Create a
Constraintnamedallowed-registriesthat uses this template and specifies the three trusted registries.
Verification:
- A Pod with image
nginx(docker.io/library/nginx) should be accepted - A Pod with image
malicious-registry.io/backdoor:latestshould be rejected
Answer: Question 2
Step 1: Create the ConstraintTemplate
# constraint-template.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
type: object
properties:
registries:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not registry_allowed(container.image)
msg := sprintf("Image '%v' is not from an allowed registry. Allowed registries: %v", [container.image, input.parameters.registries])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not registry_allowed(container.image)
msg := sprintf("Init container image '%v' is not from an allowed registry. Allowed registries: %v", [container.image, input.parameters.registries])
}
registry_allowed(image) {
registry := input.parameters.registries[_]
startswith(image, registry)
}
kubectl apply -f constraint-template.yaml
Step 2: Wait for the template to be ready
kubectl get constrainttemplate k8sallowedregistries
# STATUS should show created/ready
Step 3: Create the Constraint
# allowed-registries-constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: allowed-registries
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
registries:
- "docker.io/library/"
- "gcr.io/mycompany/"
- "registry.internal.io/"
kubectl apply -f allowed-registries-constraint.yaml
Step 4: Verify
# Allowed image — should succeed
kubectl run test-allowed --image=docker.io/library/nginx
# OR just:
kubectl run test-allowed --image=nginx
# Note: "nginx" resolves to "docker.io/library/nginx"
# Blocked image — should be rejected
kubectl run test-blocked --image=malicious-registry.io/backdoor:latest
# Should fail with: "Image 'malicious-registry.io/backdoor:latest' is not from an allowed registry"
# Clean up
kubectl delete pod test-allowed --force --grace-period=0
Key points:
- ConstraintTemplate defines the logic (Rego policy)
- Constraint defines the parameters (which registries to allow)
- The Rego code checks both
containersandinitContainers - Short image names like
nginxresolve todocker.io/library/nginx— ensure your registry list accounts for this - This is a medium-to-hard question (8–12 min) — save it for Pass 2 or 3
Question 3: Trivy — Scan Image for CRITICAL Vulnerabilities
Context: kubectl config use-context cluster1-admin@cluster1
Scenario:
The image nginx:1.21 is being used in production deployments. The security team needs a vulnerability assessment.
Task:
- Use Trivy to scan the image
nginx:1.21. - List only CRITICAL severity vulnerabilities.
- Identify the total number of CRITICAL vulnerabilities found.
- If there are CRITICAL vulnerabilities, update the Deployment
web-deployin namespacedefaultto usenginx:1.25instead (an image with fewer known critical CVEs).
Answer: Question 3
Step 1: Scan the image with Trivy — CRITICAL only
trivy image --severity CRITICAL nginx:1.21
This outputs a table showing only CRITICAL-severity vulnerabilities, including:
- CVE ID
- Package name
- Installed version
- Fixed version (if available)
- Description
Step 2: Count CRITICAL vulnerabilities
trivy image --severity CRITICAL nginx:1.21 2>/dev/null | grep "Total:"
# Or count lines in the table
trivy image --severity CRITICAL --format json nginx:1.21 | \
python3 -c "import sys,json; data=json.load(sys.stdin); \
vulns=[v for r in data.get('Results',[]) for v in r.get('Vulnerabilities',[])]; \
print(f'CRITICAL: {len(vulns)}')"
Step 3: Update the Deployment image
kubectl set image deployment/web-deploy nginx=nginx:1.25 -n default
# OR if the container name is different:
kubectl set image deployment/web-deploy web=nginx:1.25 -n default
Or edit directly:
kubectl edit deployment web-deploy -n default
# Change image: nginx:1.21 to image: nginx:1.25
Step 4: Verify the rollout
# Check deployment status
kubectl rollout status deployment/web-deploy -n default
# Verify the new image
kubectl get deployment web-deploy -n default \
-o jsonpath='{.spec.template.spec.containers[0].image}'
# Should output: nginx:1.25
# Optionally scan the new image
trivy image --severity CRITICAL nginx:1.25
# Should show fewer CRITICAL vulnerabilities
Key points:
--severity CRITICALfilters to only the most serious vulnerabilities- Other severity flags:
HIGH,MEDIUM,LOW,UNKNOWN - You can combine:
--severity CRITICAL,HIGH trivy imagepulls and scans the image locally- On the exam, Trivy is pre-installed — you do not need to install it
- Always verify the rollout after updating the image
Question 4: Encryption at Rest — Configure aescbc Provider
Context: kubectl config use-context cluster1-admin@cluster1
Scenario:
Secrets in the cluster are currently stored in plaintext in etcd. The security team requires encryption at rest using the aescbc provider.
Task:
- Create an EncryptionConfiguration file at
/etc/kubernetes/enc/enc.yamlthat:- Encrypts
secretsusing theaescbcprovider - Uses a 32-byte base64-encoded key
- Falls back to
identity(plaintext) for reading existing unencrypted secrets
- Encrypts
-
Configure the API server to use this encryption configuration.
- Re-encrypt all existing secrets.
Verification:
- New secrets should be stored encrypted in etcd
- Existing secrets should be re-encrypted
Answer: Question 4
Step 1: Generate a 32-byte encryption key
head -c 32 /dev/urandom | base64
# Example output: aGVsbG93b3JsZHRoaXNpc2EzMmJ5dGVz
# Save this value — you need it in the next step
Step 2: Create the EncryptionConfiguration file
mkdir -p /etc/kubernetes/enc
# /etc/kubernetes/enc/enc.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: aGVsbG93b3JsZHRoaXNpc2EzMmJ5dGVz
- identity: {}
cat << 'EOF' > /etc/kubernetes/enc/enc.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: aGVsbG93b3JsZHRoaXNpc2EzMmJ5dGVz
- identity: {}
EOF
Important: The
identity: {}provider at the end allows the API server to read existing unencrypted secrets. If you omit it, the API server cannot decrypt secrets that were stored before encryption was enabled.
Step 3: Back up the API server manifest
cp /etc/kubernetes/manifests/kube-apiserver.yaml \
/etc/kubernetes/manifests/kube-apiserver.yaml.bak
Step 4: Modify the API server manifest
vi /etc/kubernetes/manifests/kube-apiserver.yaml
Add the encryption provider flag:
spec:
containers:
- command:
- kube-apiserver
# ... existing flags ...
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
Add the volume mount:
volumeMounts:
# ... existing mounts ...
- name: enc-config
mountPath: /etc/kubernetes/enc
readOnly: true
Add the volume:
volumes:
# ... existing volumes ...
- name: enc-config
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate
Step 5: Wait for API server to restart
watch crictl ps | grep kube-apiserver
# Wait until the API server is running again (30–60 seconds)
Step 6: Verify encryption is working
# Create a test secret
kubectl create secret generic test-encryption \
--from-literal=mykey=mydata
# Read the secret directly from etcd
ETCDCTL_API=3 etcdctl get /registry/secrets/default/test-encryption \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt
# The output should be garbled/binary and start with:
# k8s:enc:aescbc:v1:key1:
# This confirms encryption is active
Step 7: Re-encrypt all existing secrets
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
Step 8: Verify an old secret is now encrypted
ETCDCTL_API=3 etcdctl get /registry/secrets/kube-system/default-token-xxxxx \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt
# Should show k8s:enc:aescbc:v1:key1: prefix
Key points:
- Provider order matters: the first provider is used for writing (encryption), subsequent providers are used for reading (decryption)
aescbcfirst,identitysecond = encrypt new data, can still read old plaintext data- The encryption key must be exactly 32 bytes, base64-encoded
- Three things needed: EncryptionConfiguration file + API server flag + volume/volumeMount
- Forgetting to re-encrypt existing secrets is Common Mistake #6
Question 5: Falco — Detect Shell Spawning in Containers
Context: kubectl config use-context cluster2-admin@cluster2
Scenario:
The security team wants to detect when a shell (/bin/bash, /bin/sh, or /bin/dash) is spawned inside any container running in the production namespace.
Task:
- Create (or modify) a Falco rule that:
- Detects shell spawning (
bash,sh, ordash) inside containers - Only triggers for pods in the
productionnamespace - Outputs: timestamp, container name, pod name, namespace, parent process, and shell command
- Priority:
WARNING
- Detects shell spawning (
- Verify the rule by executing a shell in a test pod.
Answer: Question 5
Step 1: Create/edit the Falco rules file
vi /etc/falco/falco_rules.local.yaml
Add the following rule:
- rule: Shell Spawned in Production Container
desc: Detect shell execution inside containers in the production namespace
condition: >
spawned_process and
container and
proc.name in (bash, sh, dash) and
k8s.ns.name = "production"
output: >
Shell spawned in production container
(time=%evt.time container=%container.name pod=%k8s.pod.name
namespace=%k8s.ns.name parent=%proc.pname command=%proc.cmdline)
priority: WARNING
tags: [shell, production, security]
Step 2: Restart Falco to load the new rule
systemctl restart falco
# Verify Falco is running
systemctl status falco
Step 3: Verify the rule is loaded
# Check if the rule appears in Falco's rule list
falco --list 2>&1 | grep "Shell Spawned in Production"
Step 4: Test the rule
# Create a test pod in the production namespace
kubectl run test-shell --image=nginx -n production
# Wait for it to be running
kubectl wait --for=condition=Ready pod/test-shell -n production
# Exec a shell into the pod (this should trigger the Falco rule)
kubectl exec -it test-shell -n production -- /bin/sh -c "echo hello"
Step 5: Check Falco output
# Check Falco logs for the alert
journalctl -u falco | grep "Shell Spawned in Production"
# OR
cat /var/log/syslog | grep "Shell Spawned in Production"
# Should see output like:
# WARNING Shell spawned in production container
# (time=... container=test-shell pod=test-shell
# namespace=production parent=runc command=sh -c echo hello)
Step 6: Clean up
kubectl delete pod test-shell -n production --force --grace-period=0
Key points:
- Falco rules go in
/etc/falco/falco_rules.local.yaml(local overrides) spawned_process= a new process was executedcontainer= the event occurred inside a container (not on the host)proc.name in (bash, sh, dash)= match common shell binariesk8s.ns.name = "production"= Kubernetes namespace filter- Always restart Falco after editing rules
- The
priorityfield accepts:EMERGENCY,ALERT,CRITICAL,ERROR,WARNING,NOTICE,INFO,DEBUG
Question 6: Audit Policy — Log Secret Access and Metadata
Context: kubectl config use-context cluster1-admin@cluster1
Scenario:
The compliance team requires audit logging with the following policy:
- All access to Secrets in any namespace must be logged at the Request level (includes the request body)
- Everything else must be logged at the Metadata level
- Do NOT log requests to the
/healthzendpoint or kube-system lease operations
Task:
- Create an audit policy file at
/etc/kubernetes/audit/policy.yaml. - Configure the API server to use this audit policy with log backend:
- Log path:
/var/log/kubernetes/audit.log - Max age: 30 days
- Max backups: 10
- Max size: 100 MB
- Log path:
- Verify audit logging is working.
Answer: Question 6
Step 1: Create the audit policy file
mkdir -p /etc/kubernetes/audit
mkdir -p /var/log/kubernetes
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# Do not log healthz requests
- level: None
nonResourceURLs:
- "/healthz*"
- "/livez*"
- "/readyz*"
# Do not log kube-system lease operations
- level: None
resources:
- group: "coordination.k8s.io"
resources: ["leases"]
namespaces: ["kube-system"]
# Log all Secret access at Request level
- level: Request
resources:
- group: ""
resources: ["secrets"]
# Everything else at Metadata level
- level: Metadata
cat << 'EOF' > /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
- level: None
nonResourceURLs:
- "/healthz*"
- "/livez*"
- "/readyz*"
- level: None
resources:
- group: "coordination.k8s.io"
resources: ["leases"]
namespaces: ["kube-system"]
- level: Request
resources:
- group: ""
resources: ["secrets"]
- level: Metadata
EOF
Step 2: Back up the API server manifest
cp /etc/kubernetes/manifests/kube-apiserver.yaml \
/etc/kubernetes/manifests/kube-apiserver.yaml.bak
Step 3: Modify the API server manifest
vi /etc/kubernetes/manifests/kube-apiserver.yaml
Add the audit flags to the command:
spec:
containers:
- command:
- kube-apiserver
# ... existing flags ...
- --audit-policy-file=/etc/kubernetes/audit/policy.yaml
- --audit-log-path=/var/log/kubernetes/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
Add volume mounts:
volumeMounts:
# ... existing mounts ...
- name: audit-policy
mountPath: /etc/kubernetes/audit
readOnly: true
- name: audit-log
mountPath: /var/log/kubernetes
readOnly: false
Add volumes:
volumes:
# ... existing volumes ...
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
- name: audit-log
hostPath:
path: /var/log/kubernetes
type: DirectoryOrCreate
Step 4: Wait for API server to restart
watch crictl ps | grep kube-apiserver
# Wait until the API server is running (30–60 seconds)
Step 5: Verify audit logging
# Generate some audit events
kubectl get secrets -n default
kubectl get pods -n default
# Check the audit log
tail -20 /var/log/kubernetes/audit.log
# Check that Secret access is logged at Request level
cat /var/log/kubernetes/audit.log | grep '"resource":"secrets"' | \
python3 -c "import sys,json; [print(json.loads(l).get('level')) for l in sys.stdin]"
# Should show "Request"
# Check that other resources are logged at Metadata level
cat /var/log/kubernetes/audit.log | grep '"resource":"pods"' | \
python3 -c "import sys,json; [print(json.loads(l).get('level')) for l in sys.stdin]"
# Should show "Metadata"
Key points:
- Audit rules are evaluated in order — first match wins
level: Nonerules must come first to exclude noisy/unnecessary events- Four audit levels:
None<Metadata<Request<RequestResponse Requestlevel includes the request body (critical for Secret auditing)- Three things needed in API server: flags + volumeMounts + volumes (Common Mistake #11)
omitStages: ["RequestReceived"]avoids duplicate logging
Question 7: Create a Fully Hardened Immutable Pod
Context: kubectl config use-context cluster1-admin@cluster1
Scenario:
Create a maximally hardened pod that follows all CKS security best practices.
Task:
Create a Pod named hardened-pod in namespace default with these specifications:
- Image:
nginx:1.25 - Container name:
app - Security requirements:
- Read-only root filesystem
- Drop ALL capabilities
- Do not allow privilege escalation
- Run as non-root user (UID 1000)
- Run as non-root group (GID 1000)
- Seccomp profile:
RuntimeDefault
- The pod should not mount the ServiceAccount token
Answer: Question 7
Step 1: Create the Pod YAML
# hardened-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: hardened-pod
namespace: default
spec:
automountServiceAccountToken: false
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:1.25
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: nginx-cache
mountPath: /var/cache/nginx
- name: nginx-run
mountPath: /var/run
volumes:
- name: tmp
emptyDir: {}
- name: nginx-cache
emptyDir: {}
- name: nginx-run
emptyDir: {}
kubectl apply -f hardened-pod.yaml
Note: nginx needs write access to
/tmp,/var/cache/nginx, and/var/run. SincereadOnlyRootFilesystem: trueprevents writing to these paths, we mountemptyDirvolumes to provide writable temporary storage.
Step 2: Verify the pod is running
kubectl get pod hardened-pod
# May take a moment; if it fails, check events:
kubectl describe pod hardened-pod
Step 3: Verify security settings
# Check security context
kubectl get pod hardened-pod -o yaml | grep -A 15 securityContext
# Verify readOnlyRootFilesystem
kubectl exec hardened-pod -- touch /test-file 2>&1
# Should fail: "Read-only file system"
# Verify writable tmp works
kubectl exec hardened-pod -- touch /tmp/test-file
# Should succeed
# Verify running as non-root
kubectl exec hardened-pod -- id
# Should show: uid=1000 gid=1000
# Verify no SA token mounted
kubectl exec hardened-pod -- ls /var/run/secrets/kubernetes.io/serviceaccount/ 2>&1
# Should show: No such file or directory
# Verify capabilities dropped
kubectl exec hardened-pod -- cat /proc/1/status | grep -i cap
# CapBnd should show 0000000000000000 (all dropped)
Key points:
- Pod-level securityContext:
runAsUser,runAsGroup,runAsNonRoot,seccompProfile - Container-level securityContext:
readOnlyRootFilesystem,allowPrivilegeEscalation,capabilities automountServiceAccountToken: falseat the pod level prevents token mounting- nginx requires writable directories — use
emptyDirvolumes for these - This pod meets all requirements of the PSS Restricted profile
- On the CKS exam, you may not need the emptyDir volumes if the question only asks for the security fields (the pod will crash but the security specs are correct)
Question 8: Block Egress to Cloud Metadata Endpoint
Context: kubectl config use-context cluster2-admin@cluster2
Scenario:
Pods in the app namespace must be prevented from accessing the cloud provider metadata service at 169.254.169.254. This is critical because the metadata endpoint can expose IAM credentials and other sensitive information.
Task:
- Create a NetworkPolicy named
deny-metadata-egressin namespaceappthat:- Applies to all pods in the namespace
- Blocks egress traffic to
169.254.169.254/32on all ports - Allows all other egress traffic
Answer: Question 8
Step 1: Create the NetworkPolicy
To block traffic to a specific IP while allowing everything else, use an egress rule with an ipBlock that covers all IPs except the metadata endpoint:
# deny-metadata-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-metadata-egress
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# Allow all egress EXCEPT to 169.254.169.254
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32
kubectl apply -f deny-metadata-egress.yaml
Step 2: Verify
# Test that the metadata endpoint is blocked
kubectl exec -n app <pod-name> -- wget -qO- --timeout=2 http://169.254.169.254/latest/meta-data/
# Should timeout or be blocked
# Test that other egress works
kubectl exec -n app <pod-name> -- wget -qO- --timeout=2 http://nginx.default.svc
# Should succeed (if there is an nginx service in default namespace)
# Test DNS still works
kubectl exec -n app <pod-name> -- nslookup kubernetes
# Should resolve
Key points:
cidr: 0.0.0.0/0matches ALL IPv4 addressesexcept: ["169.254.169.254/32"]carves out the metadata endpoint- This approach allows all egress EXCEPT to the blocked IP
- The metadata endpoint
169.254.169.254is used by AWS (IMDS), GCP, and Azure for instance metadata - Blocking this endpoint prevents pods from stealing cloud IAM credentials
- Note: this policy does not block cluster-internal traffic (pod-to-pod) because that uses cluster IPs, not the metadata IP
- If you also need to block IPv6 metadata, add
fe80::/10exclusions separately
Scoring Guide
| Question | Max Points | Your Score |
|---|---|---|
| Q1: PSA Restricted | 6 | ___ |
| Q2: OPA Gatekeeper | 14 | ___ |
| Q3: Trivy Scan | 8 | ___ |
| Q4: Encryption at Rest | 12 | ___ |
| Q5: Falco Rule | 10 | ___ |
| Q6: Audit Policy | 14 | ___ |
| Q7: Immutable Pod | 10 | ___ |
| Q8: Metadata Block | 8 | ___ |
| Total | 82 | ___ |
How to Interpret Your Score
| Score | Readiness |
|---|---|
| 70–82 | Excellent — Domains 4–6 are solid |
| 55–69 | Good — Review the questions you missed |
| 35–54 | Needs work — Revisit the relevant chapters |
| Below 35 | Not ready — Go through Domains 4–6 tutorial material again |
Combined Score (Part 1 + Part 2)
| Combined Score | CKS Readiness |
|---|---|
| 120–152 | Ready to take the exam — schedule it this week |
| 100–119 | Almost ready — do one more review cycle |
| 80–99 | Needs practice — take the mock exams again in 3–5 days |
| Below 80 | Significant gaps — review the full tutorial before retaking |