Skip to content
Back to Blog
Security11 min read

Kubernetes Security Hardening: 8 Critical Fixes for 2026

Production K8s clusters are being compromised through preventable misconfigurations. Here are eight specific policy gaps and the remediation steps that close them.

Written by Abdul AbrorTechnical Hosting Support Engineer
Kubernetes Security Hardening: 8 Critical Fixes for 2026
On this page

Most Kubernetes compromises trace back to the same handful of misconfigurations. I see them in incident reports, in customer clusters, and in public post-mortems. The attack paths are predictable.

This article walks through eight specific fixes that address the policy gaps showing up in production breaches right now. Each one includes the commands and manifests you need to lock down your cluster.

1. Lock down RBAC to the minimum scope

Default service accounts get more permissions than they need. An application pod rarely needs to list secrets across namespaces or create new deployments, but too many clusters grant those permissions anyway.

Start by auditing what each service account can do:

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

That command shows every API action the default service account in the default namespace is allowed to perform. If you see cluster-wide permissions for verbs like create, delete, or patch on sensitive resources, tighten them.

Create a least-privilege role for each application. Here's a minimal read-only role for a service that only needs to read ConfigMaps and Secrets in its own namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-reader
  namespace: production
rules:
- apiGroups: [""]
  resources: ["configmaps", "secrets"]
  verbs: ["get", "list"]

Bind it to a dedicated service account, not the default one:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-reader-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: myapp-sa
  namespace: production
roleRef:
  kind: Role
  name: app-reader
  apiGroup: rbac.authorization.k8s.io

Disable automounting of service account tokens for pods that don't need API access at all:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: no-api-sa
automountServiceAccountToken: false

In support tickets I've handled, the usual path from container escape to cluster takeover was an overprivileged service account token mounted into a compromised pod.

2. Enforce Pod Security Standards at the namespace level

Kubernetes replaced PodSecurityPolicy with Pod Security Standards. These are three levels: Privileged, Baseline, and Restricted.

Most workloads should run under Restricted. That profile blocks privileged containers, host namespace access, and insecure capabilities.

Label your namespaces to enforce the policy:

kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

The enforce label rejects non-compliant pods. The audit and warn labels log violations without blocking them, useful during a transition period.

If a workload legitimately needs host access or extra capabilities, create a separate namespace with a Baseline policy and document why. Don't lower the bar cluster-wide.

Check what the Restricted profile actually blocks:

kubectl explain podsecuritystandards.restricted

Common failures after applying Restricted: containers running as root, missing runAsNonRoot: true, or requesting CAP_NET_RAW. Fix those in your Deployment manifests.

3. Segment workloads with Network Policies

By default, every pod can talk to every other pod. An attacker who compromises one service can immediately pivot to your database, your secrets store, or your control plane.

Network policies act as a firewall inside the cluster. Start with a default-deny rule in each namespace:

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

That blocks all traffic unless explicitly allowed. Then add rules for legitimate flows.

Allow your frontend to reach your API:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080

Allow egress to DNS and external HTTPS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-and-https
  namespace: production
spec:
  podSelector: {}
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system
    ports:
    - protocol: UDP
      port: 53
  - to:
    - podSelector: {}
    ports:
    - protocol: TCP
      port: 443

Test your policies by exec-ing into a pod and trying to curl a service that should be blocked. If the connection hangs or times out, the policy is working.

Not all CNI plugins enforce Network Policies. Check yours first.

What if runtime protection isn't in place?

You can block known-bad manifests at admission time, but runtime security catches threats after they're inside the cluster. A container might start clean and then download malicious binaries, spawn a shell, or exfiltrate data.

4. Deploy a runtime security tool

Runtime security monitors system calls, process trees, and file access inside containers. Tools like Falco detect anomalous behavior even if the initial image was clean.

Install Falco with Helm:

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

Falco ships with default rules that catch common exploits: unexpected shells, writes to /etc, privilege escalation attempts. You can add custom rules for your environment.

Example rule to alert on any shell spawned in a production namespace:

- rule: Shell Spawned in Production
  desc: Detects shell execution in production pods
  condition: >
    spawned_process and
    container and
    k8s.ns.name = "production" and
    proc.name in (sh, bash, dash, zsh)
  output: >
    Shell spawned in production (user=%user.name command=%proc.cmdline
    container=%container.name namespace=%k8s.ns.name)
  priority: WARNING

Connect Falco alerts to your monitoring stack so they don't get lost in logs. A shell in a production pod is worth waking someone up.

5. Restrict API server access

The API server is the front door to your cluster. If an attacker can reach it from the internet without authentication, they'll try credential brute-forcing and known exploits.

First, check if your API server is publicly exposed:

kubectl cluster-info

If the control plane endpoint is a public IP, lock it down. Use a firewall or security group to allow access only from trusted networks: your office, your VPN, your CI/CD runner IPs.

Disable anonymous auth:

kubectl get configmap kube-apiserver -n kube-system -o yaml | grep anonymous-auth

You want --anonymous-auth=false in the API server flags. Anonymous users shouldn't be able to query the API at all.

Enable audit logging so you have a record of who did what:

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --audit-log-path=/var/log/kube-audit.log
    - --audit-log-maxage=30
    - --audit-log-maxbackup=10
    - --audit-log-maxsize=100
    - --audit-policy-file=/etc/kubernetes/audit-policy.yaml

Audit logs are verbose. Ship them to a SIEM or log aggregator, don't just leave them on the control plane node.

6. Scan images before they run

A vulnerability scanner finds known CVEs in your container images. Scanning at build time stops bad images from reaching the cluster in the first place.

Integrate a scanner into your CI pipeline. Most tools support this:

trivy image --severity HIGH,CRITICAL myregistry.com/myapp:latest

Fail the build if critical vulnerabilities are present. Don't deploy the image until they're patched.

Use an admission controller to enforce scanning at deploy time too. Something like OPA Gatekeeper or Kyverno can block images that lack a recent scan result or contain high-severity CVEs.

Example Kyverno policy:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-image-scan
spec:
  validationFailureAction: enforce
  rules:
  - name: check-scan-annotation
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Image must have a scan annotation"
      pattern:
        metadata:
          annotations:
            image-scan-status: "passed"

Scanning doesn't catch zero-day exploits or logic bugs, but it stops you from deploying last year's Bash or OpenSSL vulnerabilities.

7. Rotate secrets and limit their lifespan

Hardcoded secrets in manifests show up in Git history and ConfigMaps. Use a secrets management system instead: Sealed Secrets, External Secrets Operator, or a vault.

External Secrets Operator pulls secrets from an external source at runtime:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: db-secret
  data:
  - secretKey: password
    remoteRef:
      key: database/production
      property: password

Secrets never live in Git. They're fetched from Vault or AWS Secrets Manager when the pod starts.

Rotate secrets on a schedule. A database password that hasn't changed in three years is a time bomb. Automate rotation where possible.

Limit secret access with RBAC. Not every service account needs to read every secret in the namespace.

8. Harden the underlying nodes

Kubernetes runs on Linux. If the host OS is misconfigured, container escapes get a lot easier.

Disable unnecessary services on worker nodes. You don't need a print server or Avahi running there.

systemctl list-unit-files --type=service --state=enabled

Patch the kernel regularly. Container escapes often exploit kernel vulnerabilities.

Use a minimal OS designed for containers: Flatcar, Bottlerocket, or Talos. These distributions strip out everything that isn't needed to run Kubernetes, reducing the attack surface.

Enable kernel hardening flags:

sysctl -w kernel.dmesg_restrict=1
sysctl -w kernel.kptr_restrict=2
sysctl -w net.ipv4.conf.all.rp_filter=1

Restrict SSH access to nodes. Better yet, use an immutable infrastructure pattern where nodes are replaced, not patched. If you SSH into a node to debug, document what you did and update your automation.

How do I prioritize these fixes?

Start with RBAC and Pod Security Standards. Those two changes block the most common privilege escalation paths.

Add Network Policies next. Lateral movement is how small breaches become total compromises.

Then layer in runtime security and image scanning. Those catch threats that slip through the earlier controls.

Finally, audit your API server access, rotate secrets, and harden the nodes. These are important but have less immediate impact than the first four.

Do I need all eight fixes?

Yes, if you're running production workloads. Each control addresses a different stage of the attack chain.

Can I test these changes in a non-prod cluster first?

You should. Apply each policy to a dev or staging namespace first. Check that your workloads still start, that CI/CD pipelines still deploy, and that nothing breaks in unexpected ways.

What if my CNI doesn't support Network Policies?

Switch to one that does: Calico, Cilium, or Weave Net. Running without network segmentation is a risk you shouldn't accept.

How often should I audit RBAC permissions?

Every quarter at a minimum. Service accounts accumulate permissions over time as teams add features. Regular audits catch drift.

What's the performance impact of runtime security tools?

Minimal if configured correctly. Falco's overhead is typically under five percent CPU. Image scanning happens at build and deploy time, not during runtime.

What to check first

If you're not sure where your cluster stands, run these three checks right now.

List all ClusterRoles bound to default service accounts:

kubectl get clusterrolebindings -o json | \
  jq '.items[] | select(.subjects[]?.name=="default") | .metadata.name'

Check if Pod Security Standards are enforced anywhere:

kubectl get namespaces -o json | \
  jq '.items[] | select(.metadata.labels["pod-security.kubernetes.io/enforce"] != null)'

See if default-deny Network Policies exist:

kubectl get networkpolicies --all-namespaces | grep default-deny

If any of those commands return empty results, start there. Those gaps are the ones attackers look for first.