Security cheatsheet.

Pod Security Admission (PSA)

apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Levels: privileged < baseline < restricted.

restricted requires:

  • runAsNonRoot: true.
  • allowPrivilegeEscalation: false.
  • All capabilities dropped.
  • Seccomp profile.
  • No host network/PID/IPC.

securityContext

spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    runAsNonRoot: true
    seccompProfile: { type: RuntimeDefault }
  
  containers:
    - name: app
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: [ALL]
          add: [NET_BIND_SERVICE]

NetworkPolicy

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

Default-deny baseline. Then allow specifics.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: web-allow, namespace: prod }
spec:
  podSelector: { matchLabels: { app: web } }
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - namespaceSelector: { matchLabels: { name: ingress-nginx } }
      ports: [{ port: 8000 }]
  egress:
    - to:
        - podSelector: { matchLabels: { app: db } }
      ports: [{ port: 5432 }]
    - ports: [{ port: 53, protocol: UDP }]      # DNS

Requires CNI that supports policy (Calico, Cilium, etc).

Kyverno (policy engine)

helm install kyverno kyverno/kyverno -n kyverno --create-namespace
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: require-labels }
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-labels
      match: { any: [{ resources: { kinds: [Deployment, Pod] } }] }
      validate:
        message: "label 'app' is required"
        pattern:
          metadata:
            labels:
              app: "?*"

Block latest tag

spec:
  rules:
    - name: no-latest
      match: { any: [{ resources: { kinds: [Pod] } }] }
      validate:
        message: ":latest tag is forbidden"
        pattern:
          spec:
            containers:
              - image: "!*:latest"

OPA Gatekeeper (alternative)

Same idea, uses Rego DSL.

Image signing (cosign)

# Sign at build
cosign sign --key cosign.key myreg/myapp:v1

# Verify policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: verify-images }
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify
      match: { any: [{ resources: { kinds: [Pod] } }] }
      verifyImages:
        - imageReferences: ["myreg/myapp:*"]
          attestors:
            - entries:
                - keys: { publicKeys: |- ...cosign public key... }

Image scanning

  • trivy operator: in-cluster vulnerability scanning.
  • Falco: runtime detection (suspicious syscalls).
  • kubescape: posture + scanning.

RBAC review

kubectl auth can-i --list --as system:serviceaccount:prod:default

Audit what SAs can do.

Secrets at rest encryption

# kube-apiserver flag
--encryption-provider-config=/etc/kubernetes/encryption.yaml
# encryption.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources: [secrets]
    providers:
      - aescbc:
          keys:
            - { name: key1, secret: <base64-32-bytes> }
      - identity: {}

Re-encrypt: kubectl get secrets -A -o json | kubectl replace -f -.

Audit logs

Already in RBAC cheatsheet. Enable + ship to SIEM.

Disable default ServiceAccount auto-mount

spec:
  automountServiceAccountToken: false

Pod can’t reach API. Reduces blast radius.

ResourceQuotas

apiVersion: v1
kind: ResourceQuota
metadata: { name: prod-quota, namespace: prod }
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    limits.cpu: "20"
    limits.memory: 40Gi
    pods: "50"
    persistentvolumeclaims: "10"

Caps namespace usage. Prevents noisy neighbor.

LimitRange

apiVersion: v1
kind: LimitRange
metadata: { name: defaults }
spec:
  limits:
    - type: Container
      default: { cpu: 500m, memory: 256Mi }
      defaultRequest: { cpu: 100m, memory: 128Mi }
      max: { cpu: 2, memory: 1Gi }
      min: { cpu: 50m, memory: 64Mi }

Defaults + ceilings for pods without explicit resources.

Falco (runtime)

helm install falco falcosecurity/falco

Detects:

  • Shell spawned in container.
  • File writes to /etc.
  • Unexpected outbound connections.
  • Privileged escalations.

kube-bench (CIS benchmark)

docker run --rm --pid=host -v /etc:/etc:ro -v /var:/var:ro aquasec/kube-bench

Scans cluster against CIS Kubernetes Benchmark.

Cluster hardening checklist

  1. PSA restricted on workload namespaces.
  2. Default-deny NetworkPolicy + explicit allow.
  3. RBAC: least privilege; per-app ServiceAccounts.
  4. Encryption at rest for Secrets.
  5. Image signing + verification.
  6. Pod security context: non-root, no privilege escalation.
  7. Audit logging enabled.
  8. Falco for runtime detection.
  9. Updated K8s + nodes.
  10. Resource quotas per namespace.

Common mistakes

  • privileged: true for convenience.
  • Mounting docker.sock into pods.
  • Default ServiceAccount with cluster-admin.
  • No network policy → lateral movement.
  • Plaintext secrets in YAML committed to git.

Read this next

If you want my Kyverno policy bundle + hardening checklist, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .