CertGrid CertGrid

CKS task cheat sheet

The command for each thing a CKS task asks you to do - cut a Role down, stop a token being mounted, refuse a Pod at admission, deny a namespace's traffic, encrypt what is in etcd, scan and sign what you ship, and find out afterwards who did it. Every output below is a real run on this path's clusters, not an illustration.

Find out who can do what

  • kubectl auth can-i --list -n NS --as=system:serviceaccount:NS:SA

    Everything one identity may do, without holding it. The only honest answer to "what does this ServiceAccount actually have".

    Full guide
  • kubectl get clusterrolebindings -o jsonpath='{range .items[?(@.roleRef.name=="cluster-admin")]}{.metadata.name}{" -> "}{.subjects[*].kind}{" "}{.subjects[*].name}{"\n"}{end}'

    Every cluster-admin subject. On a fresh kubeadm cluster this is one line, so anything else is an addition somebody made.

    Full guide
  • kubectl get clusterroles -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.rules[*].verbs}{"\n"}{end}' | grep -E 'escalate|bind|impersonate'

    The three verbs that turn a small grant into a large one. None of them looks dangerous in a YAML review.

    Full guide
  • kubectl get clusterroles -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.rules[*].verbs}{"\n"}{end}' | grep -F '["*"]'

    Wildcard verbs, cluster-wide. Read this list before you add anything to it.

    Full guide

Cut a Role down to what is needed

  • kubectl create role NAME --verb=get,list --resource=configmaps -n NS && kubectl create rolebinding NAME --role=NAME --serviceaccount=NS:SA -n NS

    The imperative pair. Faster than writing YAML and far harder to get subtly wrong under time pressure.

    Full guide
  • kubectl create role NAME --verb=get --resource=secrets --resource-name=ONE-SECRET -n NS

    `--resource-name` narrows a grant to a single object. It works on get, but not on list - which is the distinction the exam likes.

    Full guide
  • kubectl -n NS get secret NAME --as=system:serviceaccount:NS:SA

    Prove the refusal with a real request, not only with can-i. Both directions, or you have not tested it.

    Full guide

ServiceAccounts and their tokens

  • kubectl -n NS patch serviceaccount default -p '{"automountServiceAccountToken":false}'

    Stop the token being mounted at all. Set it on the ServiceAccount for every Pod that uses it, or on the Pod spec for just one.

    Full guide
  • kubectl -n NS exec POD -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d. -f2 | base64 -d

    What is actually in the projected token - audience, expiry, and the Pod it was bound to. It is a JWT, so the middle segment is readable.

    Full guide
  • kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token

    Long-lived token Secrets, which modern clusters no longer create. Any that exist were made deliberately and never expire.

    Full guide
  • kubectl -n NS create token SA --audience=https://kubernetes.default.svc --duration=10m

    A short-lived token on demand, with an audience. This is what replaced the permanent Secret.

    Full guide

The API server and the node

  • sudo grep -E 'anonymous-auth|authorization-mode|--bind-address|--secure-port' /etc/kubernetes/manifests/kube-apiserver.yaml

    The four flags that decide who may reach the API and how they are authorised. Read them before changing anything.

    Full guide
  • kubectl get clusterrole system:public-info-viewer -o jsonpath='{range .rules[*]}{.nonResourceURLs}{" "}{.verbs}{"\n"}{end}'

    Exactly what an unauthenticated caller is allowed. It is a short list of non-resource URLs, and it is a binding you can remove.

    Full guide
  • sudo stat -c '%a %U:%G %n' /var/lib/kubelet/config.yaml /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf

    File permissions on the kubelet's configuration - a whole family of CIS findings, and the easiest marks in the benchmark.

    Full guide
  • sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|permitemptypasswords)'

    The effective sshd settings, not what the file says. `sshd -T` resolves every include and every default.

    Full guide

Harden a Pod

  • kubectl label ns NS pod-security.kubernetes.io/enforce=restricted --overwrite

    The only one of the three modes that refuses anything. warn and audit admit the Pod and tell you about it.

    Full guide
  • kubectl get ns NS -o jsonpath='{.metadata.labels}' | tr ',' '\n' | grep pod-security

    Which of enforce, warn and audit are actually set, and at what level. Check this before you believe a namespace is protected.

    Full guide
  • securityContext: runAsNonRoot / runAsUser / seccompProfile.type: RuntimeDefault / allowPrivilegeEscalation: false / capabilities.drop: ["ALL"] / readOnlyRootFilesystem: true

    The block that passes `restricted`. Note which fields are Pod-level and which are container-level - readOnlyRootFilesystem and capabilities are the latter.

    Full guide
  • kubectl -n NS exec POD -- id -u && kubectl -n NS exec POD -- grep CapEff /proc/1/status

    Prove it from inside the container rather than from the manifest. The uid and the capability mask are the two that matter.

    Full guide
  • kubectl -n NS exec POD -- grep Seccomp: /proc/1/status

    Whether a seccomp filter is actually loaded. `Seccomp: 2` is a filter, `0` is none - and the manifest can say one thing while the process says another.

    Full guide

Isolate it on the network

  • NetworkPolicy: podSelector: {} + policyTypes: [Ingress] - per namespace

    The one every task starts from. An empty podSelector means every Pod in the namespace; listing a policyType with no rules denies it.

    Full guide
  • NetworkPolicy egress to kube-system + ports 53 UDP and TCP

    The first thing default-deny breaks. Add DNS back explicitly or nothing in the namespace can resolve a name.

    Full guide
  • NetworkPolicy ingress from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: OTHER-NS

    Allowing one namespace in. Every namespace carries `kubernetes.io/metadata.name` automatically, so you do not need to label it first.

    Full guide
  • kubectl get networkpolicy -A

    Policies are ADDITIVE - a connection is allowed if any policy allows it. Reading them one at a time is how people get this wrong.

    Full guide

Secrets, etcd and what you ship

  • kubectl -n kube-system exec etcd-NODE -- etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=... --key=... get /registry/secrets/NS/NAME

    Read a Secret straight out of etcd. This is the check that proves encryption at rest is on - or that it is not.

    Full guide
  • kubectl -n NS get secrets -o json | kubectl replace -f -

    Encryption applies on WRITE, so existing Secrets stay in plaintext until you rewrite them. Forgetting this is the classic half-finished task.

    Full guide
  • trivy image --scanners vuln --severity CRITICAL --ignore-unfixed --exit-code 1 IMAGE

    A scan that can gate a build. `--ignore-unfixed` is what makes the gate usable, because most findings in a base image have no published fix.

    Full guide
  • trivy config --severity HIGH,CRITICAL DIR

    Static analysis of a manifest, before a cluster is involved. Each finding carries a KSV identifier you can look up.

    Full guide
  • cosign verify-blob --key cosign.pub --signature FILE.sig FILE

    Verify a signature against a public key. Change one character in the file and this fails, which is the entire point.

    Full guide

Find out afterwards, and check before you move on

  • grep '"name":"OBJECT"' /var/log/kubernetes/audit/audit.log

    Who touched one object, and with which verb. Filter by NAME - filtering by resource returns mostly the control plane watching its own.

    Full guide
  • hubble observe --to-namespace NS --last 20

    Who has been talking to the thing that matters. Run it from the cilium-agent on the node, and note that flows are a ring buffer.

    Full guide
  • kubectl apply -f FILE --dry-run=server

    Client dry-run never contacts the apiserver, so it cannot know about admission. Use server when the question is whether the cluster will accept it.

    Full guide
  • kubectl -n NS auth can-i VERB RESOURCE --as=system:serviceaccount:NS:SA

    Without `--as` you are asking about yourself, and you are admin. Every RBAC task is about somebody else.

    Full guide
  • kubectl get netpol -A

    A manifest with no namespace in its metadata goes to your current context. Check with -A before you call any namespaced task done.

    Full guide
  • six read-only checks: audit-log-path in the apiserver manifest, a falco/tetragon DaemonSet, hubble Pods, validatingadmissionpolicy, pod-security enforce labels, and netpol -A

    Whether the cluster can answer "who did it" at all. Six read-only checks that give you a cluster's detection posture in about a minute.

    Full guide