CertGrid CertGrid

KCSA security review cheat sheet

The command that answers each question a security review asks of a Kubernetes cluster - who can do what, what the control plane is configured to do, what a workload is allowed, where the secrets leak, what the network permits, what gets admitted, and what evidence exists afterwards. Every output below is a real run on the path's cluster, not an illustration.

Who can do what

  • kubectl auth whoami

    Your own identity and groups. There is no user object behind it - the certificate in your kubeconfig IS the credential.

    Full guide
  • 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 have".

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

    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 auth can-i get secrets -n NS --as=system:serviceaccount:NS:SA

    The view-against-edit difference, settled. edit reads Secrets and view does not, which makes edit far closer to admin than its name suggests.

    Full guide

What the control plane is set to do

  • sudo grep -E 'admission-plugins' /etc/kubernetes/manifests/kube-apiserver.yaml

    The only admission flag kubeadm sets. Everything else - PodSecurity, LimitRanger, ResourceQuota - is one of 27 defaults.

    Full guide
  • sudo grep -cE 'audit-log-path|audit-policy-file' /etc/kubernetes/manifests/kube-apiserver.yaml

    Whether anything is recorded about who did what. 0 is the kubeadm default and the largest compliance gap on a fresh cluster.

    Full guide
  • sudo find /etc/kubernetes/pki -name '*ca.crt' | sort

    How many certificate authorities the cluster has. Three, not one - and etcd having its own is the important one.

    Full guide
  • sudo kubeadm certs check-expiration

    Every certificate and when it dies. One year on the leaves, which is why a cluster left un-upgraded for a year stops working.

    Full guide

What a workload is allowed

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

    The difference between baseline and restricted, printed by the cluster: the refusal names every field restricted wants.

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

    The effective capability set. Compare the hex between a hardened Pod and a default one - root in a container is not root on the node.

    Full guide
  • kubectl -n NS get pods -o custom-columns='NAME:.metadata.name,QOS:.status.qosClass'

    QoS class per Pod. Guaranteed needs cpu AND memory equal on EVERY container - memory alone leaves you Burstable.

    Full guide
  • kubectl -n NS exec POD -- sh -c 'nproc; cat /sys/fs/cgroup/cpu.max; cat /sys/fs/cgroup/memory.max'

    The ceiling at the kernel interface. `max` means there is none, and the container sees every node CPU.

    Full guide

Where the data lives, and what outlives it

  • kubectl get storageclass,csidrivers; kubectl get csinodes -o custom-columns='NODE:.metadata.name,DRIVERS:.spec.drivers[*].name'

    What the cluster can provision, and which nodes register a driver. A CSI driver is a privileged DaemonSet that mounts filesystems into other people's Pods.

    Full guide
  • kubectl get pv -o custom-columns='NAME:.metadata.name,RECLAIM:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase,PATH:.spec.hostPath.path'

    The reclaim policy and the path. A PV is cluster-scoped and can name a directory on a node - which Pod Security never sees.

    Full guide
  • sudo cat /PATH/FROM/THE/PV/file

    Data remanence, proved. Deleting a PVC leaves the volume Released and the bytes on disk - removing them is a separate manual step.

    Full guide

Where the credentials are

  • kubectl -n NS describe secret NAME

    Byte counts, never values - the one place Kubernetes withholds data it holds. `get -o yaml` gives you the base64 without hesitation.

    Full guide
  • kubectl -n NS exec POD -- sh -c 'tr "\0" "\n" < /proc/1/environ'

    A Secret consumed as an environment variable, readable by any process in the container. A volume-mounted Secret does not appear here.

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

    The identity inside a mounted token. Decoding needs no permission at all - the payload is signed, not encrypted.

    Full guide
  • automountServiceAccountToken: false

    The credential is simply not there. Most workloads never call the API, so this is free attack surface to remove.

    Full guide
  • kubectl -n NS get secret NAME -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d

    A registry login in clear. The inner `auth` field is base64 of username:password - two decodes and you can push.

    Full guide

What the network permits

  • kubectl -n NS exec POD -- curl -s --max-time 5 http://SVC.OTHER-NS.svc:PORT/

    A namespace is not a network boundary. Every Pod reaches every Pod by default, and cluster DNS hands over the address.

    Full guide
  • kubectl apply -f - # NetworkPolicy, podSelector {}, policyTypes ["Ingress"]

    The default-deny idiom, and what a policy drop looks like: exit 28, a timeout, not a connection refused.

    Full guide
  • kubectl -n kube-system exec CILIUM-POD -c cilium-agent -- hubble observe -n NS --verdict DROPPED

    Why the connection timed out, named: `Policy denied`, with both ends as Pod names. The difference between a timeout you can explain and one you cannot.

    Full guide
  • kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg status

    Whether pod-to-pod traffic is encrypted. Encapsulation is not encryption, and the default here is Disabled.

    Full guide

What gets admitted, and what it is made of

  • kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations

    What is currently able to rewrite or refuse objects. Run this first on a cluster you inherit - a mutating webhook sees everything.

    Full guide
  • kubectl -n NS run NAME --image=IMG --restart=Never

    What a ValidatingAdmissionPolicy refusal looks like: it names the policy, the binding and the message a human wrote.

    Full guide
  • kubectl -n NS get pod NAME -o jsonpath='{.spec.containers[0].image}{"\n"}{.status.containerStatuses[0].imageID}{"\n"}'

    What you asked for against what ran. The tag is the request; the digest in status is the answer, and only it is stable.

    Full guide
  • kubectl -n NS get pods -o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[0].image,PULLPOLICY:.spec.containers[0].imagePullPolicy'

    The pull policy nobody set. `:latest` defaults to Always, every other tag to IfNotPresent.

    Full guide

What the evidence says

  • kubectl logs job/kube-bench-master | grep -A5 '== Summary master =='

    The CIS Benchmark score for the control plane. Read WARN separately - every one of them means a human must check it.

    Full guide
  • kubectl logs job/kube-bench-policies | grep -E '^\[FAIL\]'

    The six policy checks a benchmark can automate: cluster-admin, secrets, wildcards, create pods, default ServiceAccounts, mounted tokens.

    Full guide
  • trivy image --scanners vuln --format json IMAGE

    Known vulnerabilities in an image, by severity. Most are OS packages from the base image, so the first fix is usually a rebuild.

    Full guide
  • kubectl get pods -A -o jsonpath='{range .items[*].spec.containers[*]}{.resources.limits}{"\n"}{end}' | grep -c '^$'

    How many containers have no ceiling at all, and whether anything exists to default them. Availability is part of the model.

    Full guide