Kubernetes Security: Complete K8s Hardening Guide — From Cluster to Pod

SCR Team
April 13, 2026
26 min read
466 words
Share

The Kubernetes Security Challenge

Kubernetes is now the de facto orchestration platform — 92% of organizations use it in production as of 2026. But its complexity creates a massive attack surface.

Kubernetes Attack Surface — From Cluster to Container
Kubernetes Attack Surface — From Cluster to Container

The 2025 Red Hat State of Kubernetes Security Report found:

  • 67% of organizations had a K8s security incident in the past 12 months
  • 45% delayed deploying applications due to security concerns
  • 78% of clusters run with default (overpermissive) RBAC settings

1. RBAC: The First Line of Defense

RBAC misconfigurations are the single biggest K8s vulnerability.

Audit Current RBAC Permissions

# Find all cluster-admin bindings (should be minimal)
kubectl get clusterrolebindings -o json | jq -r '
  .items[] |
  select(.roleRef.name == "cluster-admin") |
  "\(.metadata.name): \(.subjects[]?.name) (\(.subjects[]?.kind))"
'

# Find all roles with wildcard permissions
kubectl get clusterroles -o json | jq -r '
  .items[] |
  select(.rules[]?.verbs? | index("*")) |
  .metadata.name
'

# Check what a specific service account can do
kubectl auth can-i --list \
  --as=system:serviceaccount:default:my-app

Minimal RBAC for Application Workloads

# Role: Only read pods and configmaps in own namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-reader
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["pods", "configmaps", "services"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch"]

---
# RoleBinding: Bind to app service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-reader-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: my-app
    namespace: production
roleRef:
  kind: Role
  name: app-reader
  apiGroup: rbac.authorization.k8s.io

Disable Automounted Service Account Tokens

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
automountServiceAccountToken: false

2. Pod Security Standards (PSS)

Pod Security Standards replaced PodSecurityPolicy in K8s 1.25+:

Enforce Restricted Profile

# Label namespace to enforce restricted profile
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Hardened Pod Spec

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: secure-app
  template:
    metadata:
      labels:
        app: secure-app
    spec:
      serviceAccountName: my-app
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: my-registry/app:v1.2.3@sha256:abc123...
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          ports:
            - containerPort: 8080
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir:
            sizeLimit: 100Mi

What this prevents:

  • Container escape via privileged mode
  • Root filesystem tampering
  • Privilege escalation via setuid binaries
  • Cryptomining via unlimited resources
  • Image tag mutation (uses digest)

3. Network Policies: Microsegmentation

By default, all pods can communicate with all other pods. This is disastrous for lateral movement.

Default Deny All Traffic

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

Allow Only Specific Traffic

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-app-traffic
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: web-api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: production
          podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432
    - to:  # Allow DNS
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53

4. Admission Controllers: OPA Gatekeeper

Admission controllers intercept API requests before persistence:

Block Privileged Containers

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sblockprivileged
spec:
  crd:
    spec:
      names:
        kind: K8sBlockPrivileged
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sblockprivileged
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          container.securityContext.privileged == true
          msg := sprintf("Privileged container not allowed: %v", [container.name])
        }
        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          container.securityContext.privileged == true
          msg := sprintf("Privileged init container not allowed: %v", [container.name])
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockPrivileged
metadata:
  name: block-privileged-containers
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces: ["production", "staging"]

Require Image Digests (No :latest)

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredigest
spec:
  crd:
    spec:
      names:
        kind: K8sRequireDigest
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredigest
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not contains(container.image, "@sha256:")
          msg := sprintf("Image must use digest, not tag: %v", [container.image])
        }

5. Runtime Monitoring with Falco

Falco detects anomalous activity at runtime:

# falco-rules.yaml
- rule: Terminal shell in container
  desc: Detect shell in a container (potential exec)
  condition: >
    spawned_process and
    container and
    proc.name in (bash, sh, zsh, dash) and
    not proc.pname in (cron, supervisord)
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name
    image=%container.image.repository
    shell=%proc.name parent=%proc.pname)
  priority: WARNING

- rule: Read sensitive file in container
  desc: Detect reads of sensitive files
  condition: >
    open_read and
    container and
    fd.name in (/etc/shadow, /etc/passwd, /proc/1/environ)
  output: >
    Sensitive file read in container
    (file=%fd.name container=%container.name
    image=%container.image.repository)
  priority: CRITICAL

- rule: Outbound connection to crypto pool
  desc: Detect potential cryptomining
  condition: >
    outbound and
    container and
    fd.sip.name contains "pool" or
    fd.sport in (3333, 4444, 5555, 8888, 9999)
  output: >
    Crypto mining connection detected
    (container=%container.name dest=%fd.sip:%fd.sport)
  priority: CRITICAL

6. Secrets Management

Never store secrets in plain YAML:

# BAD: Plain text secret
apiVersion: v1
kind: Secret
metadata:
  name: db-creds
type: Opaque
data:
  password: cGFzc3dvcmQxMjM=  # base64 is NOT encryption!

---
# GOOD: External Secrets Operator with Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-creds
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-creds
  data:
    - secretKey: password
      remoteRef:
        key: secret/data/production/db
        property: password

K8s Security Audit Checklist

Cluster Level

  • API server not publicly accessible
  • RBAC enabled (default since 1.8+)
  • No cluster-admin bindings to user accounts
  • etcd encrypted at rest
  • Audit logging enabled
  • Admission controllers configured
  • K8s version is latest stable (patch < 30 days)

Network Level

  • Default deny NetworkPolicy in all namespaces
  • Ingress controller has WAF (ModSecurity)
  • Service mesh with mTLS (Istio/Linkerd)
  • No NodePort services in production
  • CNI supports NetworkPolicy (Calico/Cilium)

Workload Level

  • Pod Security Standards: restricted
  • All containers non-root
  • Read-only root filesystem
  • All capabilities dropped
  • Resource limits on all containers
  • Image digests (not tags)
  • Private registry only
  • Seccomp profiles enabled
  • No hostPath mounts
  • No host networking

Key Takeaways

  1. RBAC is broken by default — 78% of clusters have overpermissive roles. Audit with kubectl auth can-i --list
  2. Network policies are mandatory — without them, one compromised pod = full cluster access
  3. Pod Security Standards replace PSP — enforce "restricted" profile on production namespaces
  4. Admission controllers are your last gate — use OPA Gatekeeper or Kyverno to prevent misconfigs
  5. Runtime monitoring catches what static analysis misses — Falco detects container escapes, cryptomining, and shell access in real time

Scan your Kubernetes manifests and Helm charts with ShieldX — detect RBAC misconfigurations, missing security contexts, and CIS benchmark violations before deployment.

Editorial standards

Published by SecureCodeReviews

This article is part of our original AI security and cybersecurity content library. We show publish and update dates, keep company and policy pages public, and update important guidance when material changes affect readers.

Named author: SCR Team
Published: Apr 13, 2026
Update status: current publication version

Questions or corrections?

Review our editorial standards, learn more about the company, or contact us if a page needs clarification.

AI Security Audit

Planning an AI feature launch or security review?

We assess prompt injection paths, data leakage, tool use, access control, and unsafe AI workflows before they become production problems.

Manual review for agent, prompt, and retrieval attack paths
Actionable remediation guidance for your AI stack
Coverage for LLM apps, MCP integrations, and internal AI tools

Talk to SecureCodeReviews

Get a scoped review path fast

Manual review
Actionable fixes
Fast turnaround
Security-focused

Advertisement