CKS Cheat Sheet
One-page printable reference for the Certified Kubernetes Security Specialist exam — verified against Kubernetes 1.36. Print it, drill it, then sit the exam.
Last reviewed:
Print tip: Use your browser's "Print" function (Ctrl/Cmd + P). The page is styled to print cleanly on letter or A4 paper. Practice tasks live elsewhere — this is a reference card.
Shell Setup (First Minute of the Exam)
alias k=kubectl
source <(kubectl completion bash)
complete -o default -F __start_kubectl k
# Fast manifest scaffolding
export do='--dry-run=client -o yaml'
export now='--grace-period=0 --force'
# Switch context / namespace quickly
k config use-context <name>
k config set-context --current --namespace=<ns>Pod Security Admission
Apply per-namespace via labels. Three modes: enforce, audit, warn.
apiVersion: v1
kind: Namespace
metadata:
name: prod
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedProfiles: privileged (no restrictions), baseline (block known escapes), restricted (heavily hardened — non-root, no caps, etc.).
Hardened Pod SecurityContext
apiVersion: v1
kind: Pod
metadata:
name: hardened
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: gcr.io/distroless/static:nonroot
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefaultDefault to dropping every capability and adding back only what the workload strictly needs. NET_BIND_SERVICE for ports below 1024 is the most common exception.
Default-Deny NetworkPolicy
Apply to every namespace before any allow rules.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: prod
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressAllow Ingress From a Namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-frontend
namespace: prod
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: frontend
ports:
- protocol: TCP
port: 8080Allow Egress to DNS Only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: prod
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53RBAC
Least-Privilege Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: prod
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]Bind to a ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-pod-reader
namespace: prod
subjects:
- kind: ServiceAccount
name: app
namespace: prod
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioRBAC Audit Commands
# Who can do X?
k auth can-i get secrets --as=system:serviceaccount:prod:app -n prod
# What can a SA do?
k auth can-i --list --as=system:serviceaccount:prod:app -n prod
# Find ClusterRoleBindings using cluster-admin
k get clusterrolebindings -o jsonpath='{range .items[?(@.roleRef.name=="cluster-admin")]}{.metadata.name}{"\n"}{end}'Common verbs: get list watch create update patch delete deletecollection. Avoid "*" in production roles.
ServiceAccount Token Hardening
apiVersion: v1
kind: ServiceAccount
metadata:
name: app
namespace: prod
automountServiceAccountToken: false
---
# Pod-level override when the SA defaults to automount
apiVersion: v1
kind: Pod
metadata:
name: no-token
spec:
serviceAccountName: app
automountServiceAccountToken: falseBound tokens are projected with audience and TTL. Avoid creating long-lived Secret-based tokens unless absolutely necessary.
Seccomp
Save profile to /var/lib/kubelet/seccomp/profiles/audit.json on every node, then reference by relative path:
apiVersion: v1
kind: Pod
metadata:
name: seccomp-audit
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/audit.json
containers:
- name: app
image: nginxUse type: RuntimeDefault for the runtime's default profile. Cluster-wide default-on is enabled with the SeccompDefault feature gate (default since 1.27 when set).
AppArmor (Structured Field, GA in 1.31)
apiVersion: v1
kind: Pod
metadata:
name: apparmor-pod
spec:
containers:
- name: app
image: nginx
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-apparmor-example-deny-writetype values: RuntimeDefault, Localhost, Unconfined. Profiles must be loaded on every node before referencing.
Sandboxed Runtime (gVisor / Kata)
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
metadata:
name: untrusted
spec:
runtimeClassName: gvisor
containers:
- name: app
image: nginxImage Verification with Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-images
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences:
- "registry.example.com/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
<YOUR_KEY>
-----END PUBLIC KEY-----Audit Policy
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- RequestReceived
rules:
# Don't log read-only kubelet probes
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
# Log auth-related changes at RequestResponse
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "configmaps", "serviceaccounts"]
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
# Catch-all for all other requests
- level: MetadataWire into the API server with --audit-policy-file=/etc/kubernetes/audit/policy.yaml and --audit-log-path=/var/log/kubernetes/audit.log.
kube-bench (CIS)
# Run the relevant benchmark on a control-plane node
kube-bench run --targets master
# Worker node
kube-bench run --targets node
# Pin to a specific benchmark version
kube-bench run --benchmark cis-1.9
# Run as a Job in-cluster
k apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
k logs job/kube-benchTrivy
# Image scan
trivy image nginx:1.27
# Filesystem / IaC scan
trivy fs --security-checks vuln,config,secret .
# Kubernetes cluster scan
trivy k8s --report summary cluster
# Generate SBOM
trivy image --format spdx-json --output sbom.json nginx:1.27Cosign — Sign and Verify
# Generate a key pair
cosign generate-key-pair
# Sign an image
cosign sign --key cosign.key registry.example.com/app:1.0
# Verify
cosign verify --key cosign.pub registry.example.com/app:1.0
# Keyless (OIDC) signing — common in CI
cosign sign registry.example.com/app:1.0
cosign verify \
--certificate-identity 'https://github.com/owner/repo/.github/workflows/ci.yml@refs/heads/main' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
registry.example.com/app:1.0Falco — Custom Rule
- rule: Shell in container
desc: Detect a shell spawning inside a container
condition: >
container.id != host
and proc.name in (bash, sh, zsh, ash)
and proc.tty != 0
output: >
Shell spawned in container (user=%user.name container=%container.name
image=%container.image.repository proc=%proc.cmdline)
priority: WARNING
tags: [shell, container]Encryption at Rest (KMS v2)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
apiVersion: v2
name: kms-provider
endpoint: unix:///var/run/kmsplugin/socket.sock
timeout: 3s
- identity: {}Pass to the API server via --encryption-provider-config=/etc/kubernetes/enc/enc.yaml. After changes, re-encrypt existing Secrets with k get secrets -A -o json | k replace -f -.
API Server Hardening Flags
| Flag | Recommended |
|---|---|
--anonymous-auth | false (or use --anonymous-auth-config in 1.33+) |
--authorization-mode | Node,RBAC |
--enable-admission-plugins | NodeRestriction,PodSecurity,... |
--audit-policy-file | Path to a structured policy file |
--audit-log-path | /var/log/kubernetes/audit.log |
--encryption-provider-config | KMS v2 config file |
--tls-min-version | VersionTLS12 or VersionTLS13 |
--profiling | false |
kubectl Debug Without Adding a Toolbox
# Debug a running pod with an ephemeral container
k debug -it mypod --image=busybox:1.36 --target=app
# Copy a pod for offline debugging (no privileges granted)
k debug mypod --image=busybox:1.36 --copy-to=mypod-debug --share-processes
# Debug a node
k debug node/<node> -it --image=busybox:1.36Quick YAML Validation
Use the in-browser YAML Security Analyzer to spot privileged workloads, host mounts, missing capabilities, and other CKS red flags without leaving this site.
Time-Saving kubectl Idioms
# Generate a Pod manifest, edit, apply
k run nginx --image=nginx $do > pod.yaml
# Generate a Deployment
k create deploy web --image=nginx $do > deploy.yaml
# Generate an RBAC Role
k create role pod-reader --verb=get,list --resource=pods $do
# Generate a RoleBinding
k create rolebinding rb --role=pod-reader \
--serviceaccount=prod:app $do
# Force-delete a stuck pod
k delete pod stuck $now