Kubernetes Security Checklist: Cluster Hardening Guide

Type: Software Reference Confidence: 0.94 Sources: 8 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

#Security DomainRisk LevelKey ControlVerification Command
1RBACCriticalLeast-privilege Roles; no system:masters for userskubectl auth can-i --list --as=<user>
2Network PoliciesCriticalDefault-deny ingress/egress in every namespacekubectl get networkpolicy -A
3Pod Security StandardsCriticalEnforce restricted or baseline per namespacekubectl label ns <ns> pod-security.kubernetes.io/enforce=restricted
4Secrets ManagementCriticalEncryption at rest; external secret manager (Vault, AWS SM)kubectl get secrets -o yaml (check no plaintext)
5Image SecurityHighScan images in CI; allow only signed/trusted imagestrivy image <image:tag>
6API ServerCriticalDisable anonymous auth; enable audit logging; use OIDCcurl -k https://<api>:6443/healthz (should require auth)
7etcdCriticalmTLS; isolate behind firewall; encrypt at restetcdctl endpoint health --cluster
8Audit LoggingHighEnable audit policy; protect log storage--audit-policy-file flag on kube-apiserver
9Runtime SecurityHighDeploy Falco or equivalent; detect anomalous syscallskubectl logs -n falco -l app=falco
10Resource LimitsMediumSet CPU/memory requests and limits on all podskubectl describe resourcequota -A
11Node SecurityHighSeccomp + AppArmor/SELinux; isolate sensitive workloadskubectl get pod -o jsonpath='{.spec.securityContext}'
12Service Mesh / mTLSMediumEncrypt all in-cluster traffic with mutual TLSistioctl analyze or mesh-specific check

Decision Tree

START: What is your primary Kubernetes security concern?
├── Setting up a new cluster from scratch?
│   ├── YES → Follow full checklist: Steps 1-8 below
│   └── NO ↓
├── Hardening an existing production cluster?
│   ├── YES → Run kube-bench first (Step 1), then fix gaps by priority
│   └── NO ↓
├── Investigating a specific security domain?
│   ├── RBAC → Step 2 (RBAC) + Anti-Pattern #1
│   ├── Network → Step 3 (NetworkPolicies)
│   ├── Pod Security → Step 4 (Pod Security Standards)
│   ├── Secrets → Step 5 (Secrets Management)
│   ├── Images → Step 6 (Image Security)
│   ├── Runtime → Step 7 (Runtime Monitoring)
│   └── Audit → Step 8 (Audit Logging)
├── Preparing for compliance audit (CIS/SOC2/PCI)?
│   ├── YES → Run kube-bench + review CIS Benchmark sections
│   └── NO ↓
└── DEFAULT → Start with kube-bench scan, address Critical findings first

Step-by-Step Guide

1. Run CIS Benchmark assessment with kube-bench

Establish a security baseline by running kube-bench against the CIS Kubernetes Benchmark. [src4]

# Run kube-bench as a Kubernetes Job
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs job/kube-bench

Verify: Review output for [FAIL] items -- these are CIS Benchmark violations. Expected: zero FAIL items for a hardened cluster.

2. Configure least-privilege RBAC

Implement RBAC with the principle of least privilege. Use namespace-scoped Roles wherever possible. [src1]

# Role: Read-only access to pods in a specific namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: production
  name: read-pods-binding
subjects:
- kind: Group
  name: "developers"
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Verify: kubectl auth can-i create pods --as=developer1 -n production → expected: no

3. Apply default-deny NetworkPolicies

Default Kubernetes allows all pod-to-pod communication. Apply default-deny in every namespace. [src2]

# Default deny all ingress and egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Verify: kubectl get networkpolicy -n production → should list default-deny-all.

4. Enforce Pod Security Standards

Apply Pod Security Standards at the namespace level. Use restricted for security-critical workloads. [src5]

apiVersion: v1
kind: Namespace
metadata:
  name: production
  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: restricted

Verify: kubectl run test --image=nginx --privileged -n production → expected: rejected by Pod Security Admission.

5. Secure secrets with encryption at rest

Enable Kubernetes secret encryption at rest and use external secret managers for production. [src3]

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: <base64-encoded-32-byte-key>
    - identity: {}

Verify: Check etcd data with hexdump -- should show encryption prefix, not plaintext.

6. Implement image scanning in CI/CD

Scan container images for vulnerabilities before deployment. Block images with critical CVEs. [src2]

# Scan with Trivy -- exit code 1 fails CI build
trivy image --severity CRITICAL,HIGH --exit-code 1 myregistry/myapp:latest

Verify: trivy image myapp:latest --severity CRITICAL → expected: 0 critical vulnerabilities.

7. Deploy runtime security monitoring (Falco)

Install Falco to detect anomalous runtime behavior: shell execution, unexpected file access, privilege escalation. [src8]

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
  --namespace falco --create-namespace \
  --set falcosidekick.enabled=true

Verify: kubectl logs -n falco -l app.kubernetes.io/name=falco → should show Falco running.

8. Enable Kubernetes audit logging

Configure API server audit logging to track all authentication, authorization, and resource operations. [src1]

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: RequestResponse
    resources:
    - group: ""
      resources: ["secrets", "configmaps"]
  - level: RequestResponse
    resources:
    - group: "rbac.authorization.k8s.io"
      resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
  - level: Metadata
    omitStages:
    - RequestReceived

Verify: cat /var/log/kubernetes/audit.log | jq '.verb' | head → should show recent API operations.

Code Examples

YAML: Complete RBAC Configuration for a Microservice

# Input:  Microservice needing ConfigMap and Secret access in its namespace
# Output: Least-privilege RBAC with separate service account

apiVersion: v1
kind: ServiceAccount
metadata:
  name: order-service
  namespace: orders
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: orders
  name: order-service-role
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["order-config"]
  verbs: ["get", "watch"]
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["order-db-creds"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: orders
  name: order-service-binding
subjects:
- kind: ServiceAccount
  name: order-service
  namespace: orders
roleRef:
  kind: Role
  name: order-service-role
  apiGroup: rbac.authorization.k8s.io

YAML: Comprehensive NetworkPolicy for Multi-Tier App

# Input:  Three-tier app (frontend, backend, database)
# Output: NetworkPolicies allowing only required paths

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          tier: database
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53

YAML: Falco Custom Runtime Security Rules

# Input:  Kubernetes cluster with security monitoring
# Output: Falco rules detecting common attack patterns

- rule: Shell Spawned in Container
  desc: Detect shell execution inside a container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh, csh, ksh)
  output: >
    Shell spawned (user=%user.name container=%container.name
    image=%container.image.repository pod=%k8s.pod.name
    ns=%k8s.ns.name shell=%proc.name)
  priority: WARNING
  tags: [container, shell, mitre_execution]

- rule: Read Sensitive File in Container
  desc: Detect reading of sensitive files
  condition: >
    open_read and container and
    (fd.name startswith /etc/shadow or
     fd.name startswith /root/.ssh)
  output: >
    Sensitive file read (file=%fd.name container=%container.name
    pod=%k8s.pod.name)
  priority: ERROR
  tags: [container, filesystem, mitre_credential_access]

Bash: Automated Security Audit Script

#!/usr/bin/env bash
# Input:  kubectl access to a Kubernetes cluster
# Output: Security posture report

set -euo pipefail
echo "=== Kubernetes Security Audit ==="
echo "Cluster: $(kubectl config current-context)"

# Check pods running as root
echo "--- Pods Running as Root ---"
kubectl get pods -A -o json | \
  jq -r '.items[] | select(.spec.securityContext.runAsNonRoot != true) |
  "\(.metadata.namespace)/\(.metadata.name)"' | head -20

# Namespaces without PSS enforcement
echo "--- Namespaces Without PSS ---"
kubectl get ns -o json | \
  jq -r '.items[] | select(.metadata.labels["pod-security.kubernetes.io/enforce"] == null) |
  .metadata.name' | grep -v "^kube-"

echo "=== Audit Complete ==="

Anti-Patterns

Wrong: Granting cluster-admin to application service accounts

# BAD -- cluster-admin gives full control over the entire cluster
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: app-admin
subjects:
- kind: ServiceAccount
  name: my-app
  namespace: default
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

Correct: Scoped Role with minimum required permissions

# GOOD -- namespace-scoped Role with only needed permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: my-app-ns
  name: my-app-role
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["my-app-config"]
  verbs: ["get", "watch"]

Wrong: No NetworkPolicy (default allow-all)

# BAD -- no NetworkPolicy means any pod can reach any other pod
# This is the default Kubernetes behavior
# (No manifest needed -- this is what you get by doing nothing)

Correct: Default-deny with explicit allow rules

# GOOD -- default deny everything, then allow specific traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Wrong: Running containers as root with all capabilities

# BAD -- running as root with privilege escalation enabled
apiVersion: v1
kind: Pod
metadata:
  name: insecure-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      privileged: true
      runAsUser: 0

Correct: Non-root with dropped capabilities and read-only filesystem

# GOOD -- restricted security context
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:1.0.0
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]

Wrong: Storing secrets in plaintext environment variables

# BAD -- plaintext secrets in manifest, exposed via env vars
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: app
    env:
    - name: DB_PASSWORD
      value: "super-secret-password"

Correct: External secret manager with volume mount

# GOOD -- secrets managed externally, mounted as read-only files
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: app
    volumeMounts:
    - name: db-creds
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: db-creds
    secret:
      secretName: db-credentials

Common Pitfalls

Diagnostic Commands

# Run CIS Benchmark check
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs job/kube-bench

# Audit RBAC permissions for a service account
kubectl auth can-i --list --as=system:serviceaccount:production:my-app -n production

# Check PSS enforcement across namespaces
kubectl get ns -o json | jq -r '.items[] | "\(.metadata.name): \(.metadata.labels["pod-security.kubernetes.io/enforce"] // "NONE")"'

# List all NetworkPolicies
kubectl get networkpolicy -A

# Check for pods running as root
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.securityContext.runAsNonRoot != true) | "\(.metadata.namespace)/\(.metadata.name)"'

# Scan cluster images for vulnerabilities
trivy k8s --report summary cluster

# Check Falco runtime alerts
kubectl logs -n falco -l app.kubernetes.io/name=falco --tail=50

# List all ClusterRoleBindings to cluster-admin
kubectl get clusterrolebindings -o json | jq -r '.items[] | select(.roleRef.name == "cluster-admin") | "\(.metadata.name): \(.subjects[].name)"'

Version History & Compatibility

Kubernetes VersionStatusKey Security Features
1.34-1.35 (2025)CurrentUser namespaces GA; finer-grained authorization; pod mTLS certificates (beta)
1.30-1.33 (2024-2025)SupportedValidating Admission Policy GA; bound SA token improvements; recursive read-only mounts
1.28-1.29 (2023-2024)LTSPod Security Admission GA; seccomp RuntimeDefault GA
1.25 (2022)EOLPodSecurityPolicy removed -- must migrate to Pod Security Standards
ToolVersionPurpose
kube-bench0.8.xCIS Benchmark compliance scanning
Falco0.37+Runtime threat detection
Trivy0.50+Image + cluster vulnerability scanning
OPA/Gatekeeper3.16+Policy enforcement via admission controller
Kyverno1.12+Kubernetes-native policy management

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Deploying any production Kubernetes clusterRunning local dev cluster (minikube, kind) with no sensitive dataBasic kubectl access controls only
Preparing for SOC 2, PCI DSS, or HIPAA complianceUsing serverless containers (Fargate, Cloud Run) without K8sCloud provider's native security controls
Multi-tenant clusters sharing infrastructureSingle-tenant cluster with full trust between all workloadsSimplified RBAC + network policies may suffice
Clusters exposed to the internet (ingress controllers)Air-gapped clusters with no external accessFocus on internal controls

Important Caveats

Related Units