Skip to content
Back to Blog
Security11 min read

Kubernetes Security Best Practices: 7 Policies to Deploy

Lock down your K8s cluster with pod security standards, RBAC rules, and network policies that stop container escapes and privilege escalation before they happen.

Written by Abdul AbrorTechnical Hosting Support Engineer
Kubernetes Security Best Practices: 7 Policies to Deploy
On this page

Running workloads in Kubernetes shifts your security perimeter from static servers to ephemeral containers, namespaces, and service accounts. A misconfigured pod can escape its sandbox, escalate to cluster-admin, or exfiltrate secrets from the entire cluster. I've watched post-mortems where a single forgotten privileged: true flag opened the door to root access on every node.

The seven policies below form the minimum viable defense for any production cluster. They cover pod security standards, role-based access control, network segmentation, and admission control—mechanisms built into Kubernetes that you activate with YAML and command-line flags.

1. Enforce Pod Security Standards with admission controllers

Pod Security Standards replaced PodSecurityPolicies in Kubernetes 1.25. Three profiles exist: Privileged (unrestricted), Baseline (minimal restrictions), and Restricted (hardened, defense-in-depth). Most production namespaces should run Restricted; only infrastructure namespaces (kube-system, monitoring) need Privileged.

Label your namespace to enforce a standard:

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

Restricted mode blocks privileged containers, host namespaces (PID, IPC, network), hostPath volumes, and the default root user. Every pod must define a non-root securityContext.

Start with warn mode to see violations without blocking deployments, then graduate to enforce after fixing manifests. Check violations with kubectl describe on denied pods.

2. Drop all Linux capabilities by default

Containers inherit a surprising number of Linux capabilities even when not running as root. CAP_NET_RAW lets a container packet-sniff the node network. CAP_SYS_ADMIN is nearly equivalent to root.

Drop every capability, then add back only what the application needs:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE  # Only if binding to port <1024

Most web apps need zero capabilities. If your container legitimately needs CAP_SYS_ADMIN or CAP_SYS_PTRACE, isolate it in a dedicated namespace and apply extra monitoring.

3. Restrict service account tokens and RBAC bindings

Every pod mounts a service account token by default. That token carries RBAC permissions—often more than the pod needs. In clusters with overly broad roles, a compromised pod can list secrets, modify deployments, or exec into other pods.

Disable automatic token mounting for pods that don't call the Kubernetes API:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: webapp
automountServiceAccountToken: false

For pods that do need API access, create a dedicated ServiceAccount with a narrow Role:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: log-reader
  namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: read-logs
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: log-reader-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: log-reader
roleRef:
  kind: Role
  name: read-logs
  apiGroup: rbac.authorization.k8s.io

Never bind cluster-admin or wildcard permissions (*) to service accounts. Audit existing bindings with:

kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.kind=="ServiceAccount") | {name: .metadata.name, role: .roleRef.name, subjects: .subjects}'

Look for service accounts in kube-system or default with admin-level roles.

4. Isolate workloads with NetworkPolicies

Kubernetes allows all pod-to-pod traffic by default. A frontend pod can connect to the database, the CI runner, and every other service in the cluster. Container escapes become lateral movement opportunities.

NetworkPolicies define firewall rules at the pod level using label selectors. Start with a default-deny policy for each namespace:

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

Then allow only necessary traffic. For example, let the frontend talk to the API but nothing else:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-to-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: api
      ports:
        - protocol: TCP
          port: 8080
    - to:  # DNS
        - namespaceSelector:
            matchLabels:
              name: kube-system
      ports:
        - protocol: UDP
          port: 53

Remember to allow DNS (port 53 UDP to kube-dns or CoreDNS) or every egress policy will break name resolution.

5. Enable admission webhooks to enforce custom policies

Pod Security Standards catch common misconfigurations, but they can't enforce organization-specific rules like "no images from Docker Hub" or "all ingresses must use our TLS issuer." Admission controllers intercept create/update requests and validate or mutate them before they land in etcd.

Two popular policy engines are OPA Gatekeeper and Kyverno. Kyverno uses native Kubernetes YAML, making it easier to adopt:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-registry-prefix
spec:
  validationFailureAction: enforce
  rules:
    - name: check-image-registry
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Images must come from registry.company.com"
        pattern:
          spec:
            containers:
              - image: "registry.company.com/*"

Deploy Kyverno as a Helm chart, then create ClusterPolicies for your requirements. Start with validationFailureAction: audit to gather violations without blocking deploys.

Common policies to enforce: - Require resource requests and limits on every container - Block latest image tags - Require signed images (via Cosign or Notary) - Disallow hostPath, hostNetwork, and hostPID

6. Scan container images and block vulnerable workloads

A container image is a tarball of file layers plus metadata. Vulnerabilities hide in outdated base images, unpatched libraries, and abandoned dependencies. I've seen critical CVEs in images that shipped two years ago and never received updates.

Integrate image scanning into your CI pipeline and cluster admission. Trivy is open-source and scans both filesystem layers and language-specific dependencies:

trivy image --severity HIGH,CRITICAL registry.company.com/app:v1.2.3

For runtime enforcement, pair an admission webhook with a scanning service. For example, configure Trivy to reject images with HIGH or CRITICAL findings:

trivy image --exit-code 1 --severity CRITICAL registry.company.com/app:v1.2.3

If the scan fails (exit code 1), your CI rejects the build. Alternatively, deploy an admission controller that queries a vulnerability database before allowing the pod to schedule.

Scan regularly—new CVEs appear daily. Automate rescanning of running images with a CronJob and alert on new findings.

7. Harden the control plane and enable audit logging

The API server, etcd, and kubelet are high-value targets. An attacker who gains etcd access can read every secret in the cluster. Kubelet compromise yields root on the node.

API server hardening: - Disable anonymous authentication: --anonymous-auth=false - Enable RBAC: --authorization-mode=Node,RBAC - Restrict kubelet API access: --kubelet-certificate-authority and client certs - Rotate service account signing keys regularly

Etcd encryption:
Secrets are stored in etcd in plaintext by default. Enable encryption at rest with an EncryptionConfiguration:

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

Pass --encryption-provider-config=/etc/kubernetes/enc.yaml to kube-apiserver.

Audit logging:
Configure the API server to log every request with --audit-log-path and --audit-policy-file. Ship logs to a SIEM or centralized log collector. Watch for privilege escalation attempts, secret reads, and failed authentication.

Example audit policy:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: RequestResponse
    verbs: ["create", "update", "patch", "delete"]
    resources:
      - group: ""
        resources: ["secrets", "configmaps"]
  - level: Metadata
    omitStages: ["RequestReceived"]

This logs full request/response bodies for secret modifications and metadata for everything else.

How do you audit your current cluster?

Before deploying new policies, assess what's already running. Use kubectl and open-source scanners to find misconfigurations.

Check for privileged pods:

kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.containers[]?.securityContext?.privileged==true) | {name: .metadata.name, namespace: .metadata.namespace}'

Find pods running as root:

kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.securityContext.runAsUser==0 or .spec.containers[]?.securityContext?.runAsUser==0) | {name: .metadata.name, namespace: .metadata.namespace}'

List service accounts with cluster-admin:

kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name=="cluster-admin") | {name: .metadata.name, subjects: .subjects}'

Scan for missing NetworkPolicies:

kubectl get networkpolicies --all-namespaces

If a namespace appears empty, all traffic is allowed.

For comprehensive auditing, run kubesec, kube-bench, or Polaris against your manifests and cluster state. Each tool outputs a scored report of misconfigurations.

What happens when a policy blocks a legitimate workload?

You'll see events in kubectl describe pod explaining the denial. Common messages:

  • violates PodSecurity "restricted:latest" — Pod Security Standard blocked the pod. Check securityContext settings.
  • admission webhook denied the request — Your policy engine (Kyverno, OPA) rejected it. Review the policy name in the error.
  • Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:pod-reader" cannot create resource "pods" — RBAC issue. The service account lacks the necessary Role or ClusterRole.

Start with warn or audit mode for new policies. Monitor logs for a week, fix manifests, then switch to enforce.

Start with the quick wins

Pod Security Standards and RBAC are configuration flags—no new components to install. Apply them namespace by namespace starting with your least critical workload. Drop Linux capabilities and disable token automounting in the same pass.

NetworkPolicies and admission webhooks require a CNI plugin and policy engine, respectively. If your cluster lacks them, schedule installation in the next sprint. Image scanning integrates into CI/CD and costs nothing but pipeline minutes.

The control plane hardening (etcd encryption, audit logs) touches the API server flags, so coordinate with your platform team if you're on a managed Kubernetes service. Most providers (GKE, EKS, AKS) enable some of these by default.

You don't need all seven on day one, but you do need a plan to deploy them before the next pentest or compliance audit. Pick two policies this week and schedule the rest.

FAQ

Do I need all seven policies for a dev cluster?

Development clusters should still enforce RBAC and Pod Security Standards (Baseline minimum). Skip restrictive NetworkPolicies and admission webhooks if they slow iteration, but protect production with all seven.

Can I run privileged containers in a restricted namespace?

No. Create a dedicated namespace with the Privileged profile for infrastructure pods (CNI plugins, log collectors) that genuinely require host access. Isolate it from application workloads.

How do I rotate service account tokens after shortening TTL?

Set --service-account-max-token-expiration on the API server to a shorter duration (default is one year). Existing tokens remain valid until expiry; new tokens get the shorter TTL. Pods automatically refresh tokens before expiry.

What's the performance cost of NetworkPolicies?

Negligible on modern CNI plugins (Calico, Cilium, Antrea). They compile policies into efficient eBPF or iptables rules. Test with realistic traffic patterns, but expect single-digit millisecond overhead.

Should I disable the default service account in each namespace?

Yes, if no pods need it. Set automountServiceAccountToken: false on the default ServiceAccount or delete it and create named accounts for each workload.