Kubernetes Security Hardening
A default Kubernetes cluster is not secure. This page covers the layered controls needed to bring a cluster to a production security posture.
The hardening layers
Section titled “The hardening layers”Cluster infrastructure (nodes, etcd encryption, control plane) └── Authentication & authorisation (RBAC, OIDC) └── Admission control (OPA/Gatekeeper, Kyverno) └── Network policies (pod-to-pod traffic) └── Runtime security (Falco, seccomp, AppArmor) └── Secrets management (Vault, SOPS)1. RBAC — Role-Based Access Control
Section titled “1. RBAC — Role-Based Access Control”Grant the minimum permissions required. Avoid cluster-admin bindings.
# Bad — don't do thisapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: developer-adminsubjects: - kind: User name: aliceroleRef: kind: ClusterRole name: cluster-admin # ← too broad apiGroup: rbac.authorization.k8s.io
---# Good — namespace-scoped, specific verbsapiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: namespace: app-prod name: app-deployerrules: - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "update", "patch"]Audit RBAC regularly:
kubectl auth can-i --list --as=alicekubectl get rolebindings,clusterrolebindings -A | grep alice2. Network Policies
Section titled “2. Network Policies”By default, all pods can talk to all pods. Lock this down:
# Deny all ingress by default in a namespaceapiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: deny-all-ingress namespace: app-prodspec: podSelector: {} policyTypes: - Ingress
---# Allow only from the frontend to the APIapiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: allow-frontend-to-api namespace: app-prodspec: podSelector: matchLabels: app: api ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080Network policies require a CNI plugin that supports them: Calico, Cilium, or Weave.
3. Pod Security Standards
Section titled “3. Pod Security Standards”Replace deprecated PodSecurityPolicy with the built-in Pod Security Admission:
# Label the namespace to enforce the restricted profileapiVersion: v1kind: Namespacemetadata: name: app-prod labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/warn: restricted pod-security.kubernetes.io/audit: restrictedrestricted profile requires:
runAsNonRoot: true- No privilege escalation
- Drop
ALLcapabilities - Seccomp
RuntimeDefaultorLocalhost
4. Admission Control with Kyverno
Section titled “4. Admission Control with Kyverno”Kyverno lets you write policies as Kubernetes resources:
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: disallow-latest-tagspec: rules: - name: require-image-digest match: any: - resources: kinds: [Pod] validate: message: "Images must use a digest, not :latest" pattern: spec: containers: - image: "*@sha256:*"5. Secrets management
Section titled “5. Secrets management”Never store secrets in plaintext in a Git repo. Options:
| Approach | Trade-offs |
|---|---|
| Kubernetes Secrets (encrypted at rest via KMS) | Simple, native, but secrets visible to anyone with Secret read access |
| Vault + External Secrets Operator | Centralised, auditable, fine-grained TTLs — higher operational complexity |
| SOPS + Helm | Encrypted in Git, decrypted at deploy time — good for GitOps |
6. Runtime Security with Falco
Section titled “6. Runtime Security with Falco”Falco watches kernel syscalls and fires alerts on suspicious activity:
# Example Falco rule — alert if a shell is spawned inside a container- rule: Terminal shell in container desc: Detect a shell run in a container condition: > spawned_process and container and proc.name in (shell_binaries) and not container.image.repository in (trusted_images) output: > Shell spawned in container (user=%user.name container=%container.name image=%container.image.repository proc=%proc.name) priority: WARNINGCIS Benchmark
Section titled “CIS Benchmark”Run kube-bench against your cluster to get a scored report:
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yamlkubectl logs job/kube-benchAim for 0 FAIL results in the Control Plane and Node sections.