Production Reference

Kubernetes Security Cheatsheet

A production-grade quick reference for securing Kubernetes clusters. Covers RBAC, PodSecurity standards, NetworkPolicies, runtime detection, secrets, image security, and audit or forensic commands with security context for every entry.

Command-firstProduction notesSecurity warningsHardened patterns
Request path

Kubernetes Authorization Chain

Every API request goes through this chain. Most security misconfigurations are failures at the authentication, authorization, or admission layer.

Clientkubectl / SDKAuthenticationx509 / OIDC / SAAuthorizationRBAC / WebhookAdmissionPSA / OPAetcdpersisted

RBAC: Audit & Least Privilege

6 commands
kubectl auth can-i --list

List every action your current credentials can perform.

Production note: Run this as the ServiceAccount your workload uses (kubectl --as=system:serviceaccount:NS:NAME) to verify least-privilege before deploy.

kubectl auth can-i create pods --as=alice@example.com -n payments

Check whether a specific user/SA can perform an action in a namespace.

kubectl get clusterrolebinding -o json | jq '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name'

Find every binding to cluster-admin.

Warning: A surprising number of clusters have human users bound to cluster-admin. Audit these aggressively — cluster-admin is Kubernetes "root."

kubectl get rolebinding,clusterrolebinding -A -o wide

List every RBAC binding cluster-wide.

kubectl create role NAME --verb=get,list --resource=pods --dry-run=client -o yaml

Generate a least-privilege Role YAML to commit.

Production note: Always start with the smallest verb set that works. Re-audit on every promotion (staging → prod).

kubectl describe clusterrole admin

Inspect what a built-in ClusterRole grants.

PodSecurity & SecurityContext

5 commands
kubectl label namespace NS pod-security.kubernetes.io/enforce=restricted

Enforce the strictest PodSecurity profile on a namespace.

Production note: Use enforce / audit / warn modes together: enforce blocks, audit logs, warn shows a deprecation-style message in kubectl output.

kubectl get pods -A -o json | jq '.items[] | select(.spec.containers[].securityContext.privileged==true) | "\(.metadata.namespace)/\(.metadata.name)"'

Find every privileged container in the cluster.

Warning: Privileged containers can break out to the host. They should be rare and tightly scoped (e.g. kube-system DaemonSets only).

kubectl get pods -A -o json | jq '.items[] | select(.spec.hostPID==true or .spec.hostNetwork==true) | .metadata.name'

Find pods sharing host PID/network namespaces.

kubectl run debug --image=alpine --rm -it --overrides='{"spec":{"securityContext":{"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}}}}' -- sh

Spawn a debug pod with a hardened securityContext.

kubectl get psa -A

List PodSecurity admission labels per namespace (if PSA enabled).

NetworkPolicy: Default-Deny & Egress

4 commands
kubectl get networkpolicy -A

List all NetworkPolicies cluster-wide.

Warning: A namespace with NO NetworkPolicy is allow-all. Default-deny + explicit allows is the production baseline.

kubectl describe networkpolicy NAME -n NS

See the resolved rules (which podSelectors match, which CIDRs are allowed).

kubectl exec -n NS POD -- nc -vz TARGET 443

Verify a NetworkPolicy from inside a pod (open or blocked).

Production note: After applying a default-deny, run this from each microservice to confirm only the intended targets are reachable. Catches typos in label selectors.

cilium connectivity test

Run Cilium's built-in policy connectivity test (if Cilium CNI).

Secrets & Certificate Management

5 commands
kubectl get secrets -A -o json | jq '.items[] | select(.type=="Opaque") | "\(.metadata.namespace)/\(.metadata.name)"'

List all custom (non-system) secrets cluster-wide.

kubectl get secret NAME -n NS -o jsonpath="{.data.password}" | base64 -d

Decode a single secret value.

Warning: Anyone with secrets/get RBAC can do this. Audit RBAC for secret access aggressively. Treat it as a credential-extraction action.

kubectl get csr

List CertificateSigningRequests (kubelet, custom certs).

kubectl auth can-i get secrets -n NS --as=system:serviceaccount:NS:SA

Verify a ServiceAccount cannot read secrets it does not own.

Production note: Most workloads should NOT be able to read other workloads' secrets. Default RBAC is too permissive here.

kubectl rollout restart deployment/NAME -n NS

Force pods to re-mount rotated secrets / certs.

Production note: Kubernetes does not auto-restart pods when a Secret changes. Use the secret-CSI driver, External Secrets Operator, or restart on rotation.

Audit, Forensics & Incident Response

5 commands
kubectl get events -A --sort-by=.lastTimestamp

Recent cluster events (failed pulls, OOMKilled, evictions).

kubectl logs -n kube-system pod/kube-apiserver-NODE | grep "audit"

Tail audit log entries from the API server (if audit logging enabled).

Production note: If you do not have audit logging on, you have no forensic trail. Configure --audit-policy-file with at least Metadata level on every prod cluster.

kubectl debug -it POD --image=busybox --target=CONTAINER

Attach an ephemeral debug container to a running pod.

Warning: Ephemeral containers run in the same pod as the target. Restrict via RBAC; treat as a privileged action in production.

kubectl get pods -A -o json | jq '.items[] | select(.status.containerStatuses[]?.restartCount > 5) | .metadata.name'

Find pods with high restart counts (often crash-loop / abuse).

kubectl top pods -A --sort-by=cpu

Pods by CPU — finds runaway processes (cryptominers, infinite loops).

Image Security & Admission

5 commands
kubectl get pods -A -o json | jq '.items[].spec.containers[].image' | sort -u

List every distinct image running in the cluster.

Production note: Pipe through `grep -E ":(latest|main|stable)"` to find mutable-tag images that bypass cryptographic provenance.

cosign verify --key cosign.pub IMAGE

Verify a container image signature with Sigstore cosign.

syft IMAGE -o spdx-json

Generate an SBOM for an image (Syft).

trivy image --severity CRITICAL,HIGH IMAGE

Scan an image for known CVEs.

Production note: Wire this into CI and gate merges. Re-scan on a schedule for already-deployed images — new CVEs are published daily.

kubectl get validatingwebhookconfigurations

List admission webhooks (Kyverno, OPA Gatekeeper, Connaisseur, etc.).

Try it yourself

Default-Deny NetworkPolicy

Drop this into a namespace to block all ingress and egress, then add explicit allow rules for the traffic that should exist.

network-policy-default-deny.yaml

Blast-radius control
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-payments-to-rds
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: payments-api
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 10.20.0.0/16
    ports:
    - protocol: TCP
      port: 5432
Hardened patterns

Common misconfigurations

The unsafe pattern, the replacement, and the reason the two are not equivalent in production.

FIXReview

Risky

# ClusterRoleBinding to cluster-admin
roleRef:
  kind: ClusterRole
  name: cluster-admin

Hardened

# Namespaced Role with only the verbs you need
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get","list","update","patch"]

Why it matters: Bind humans and ServiceAccounts to scoped, namespaced Roles — never to cluster-admin. cluster-admin is "root" of Kubernetes; one compromised credential becomes total cluster takeover.

FIXReview

Risky

# Pod with no securityContext
spec:
  containers:
  - name: app
    image: registry/app:latest

Hardened

spec:
  containers:
  - name: app
    image: registry/app@sha256:abc...
    securityContext:
      runAsNonRoot: true
      runAsUser: 10001
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
      seccompProfile:
        type: RuntimeDefault

Why it matters: PodSecurity "restricted" requires non-root, read-only FS, dropped capabilities, and the default seccomp profile. Pin to a digest, not a tag, so the image cannot be silently rewritten.

FIXReview

Risky

# No NetworkPolicy in the namespace
# (Kubernetes default: any pod can talk to any pod)

Hardened

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]

Why it matters: Kubernetes networking is allow-all by default. A default-deny in every namespace, plus explicit allows for known traffic, dramatically shrinks lateral-movement blast radius after a single pod compromise.

FIXReview

Risky

# Secret as plaintext env in Deployment
env:
- name: DB_PASSWORD
  value: "Hunter2-Prod-2024!"

Hardened

env:
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: payments-db
      key: password
# ...and ideally projected via the secrets-store CSI driver
# pulling from Vault / AWS Secrets Manager.

Why it matters: Plaintext secrets in manifests end up in Git history forever. Once leaked, the only fix is rotation — not a revert. Use external secret managers with the secrets-store CSI driver so the credential never touches source control.

Go deeper