Kubernetes Security Hardening: A Checklist for Production Clusters
A default Kubernetes installation is not secure. It is functional — designed to get you started quickly — but production readiness requires deliberate hardening across multiple layers. We have audited dozens of Kubernetes clusters (as part of our infrastructure audit practice), and the same security gaps appear repeatedly.
This post is the checklist we wish every team had before going to production. Each section includes concrete YAML snippets and configuration you can apply today.
graph TD
A[RBAC - Who can do what] --> B[Pod Security Standards - How pods run]
B --> C[Network Policies - Who can talk to whom]
C --> D[Image Scanning - What gets deployed]
D --> E[Runtime Security - What happens at runtime]
E --> F[Audit Logging - What happened]
F --> G[CIS Benchmarks - Are we compliant]
1. RBAC: Principle of Least Privilege
Role-Based Access Control is Kubernetes’ authorization layer. The most common mistake is over-permissioning — granting cluster-admin to service accounts that only need read access to a single namespace.
Design principles:
- No human user should have
cluster-adminin production. Use time-limited escalation (e.g., through a tool like Teleport or a custom approval workflow). - Every application should have its own ServiceAccount with a Role scoped to exactly what it needs.
- Audit who has what access regularly.
A properly scoped Role for an application that reads ConfigMaps and manages its own Deployments:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-deployer
namespace: myapp
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-deployer-binding
namespace: myapp
subjects:
- kind: ServiceAccount
name: myapp-deployer
namespace: myapp
roleRef:
kind: Role
name: app-deployer
apiGroup: rbac.authorization.k8s.io
Quick audit: Run kubectl auth can-i --list --as=system:serviceaccount:myapp:default for each namespace to see what the default ServiceAccount can do. If it can do anything beyond the basics, you have a problem.
2. Pod Security Standards
Pod Security Standards (PSS) replaced the deprecated PodSecurityPolicy. They define three profiles: Privileged, Baseline, and Restricted. Every production namespace should enforce at minimum Baseline, and ideally Restricted.
Apply enforcement at the namespace level:
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/warn: restricted
pod-security.kubernetes.io/audit: restricted
The Restricted profile requires pods to:
- Run as non-root
- Drop all capabilities
- Use a read-only root filesystem
- Set
seccompProfiletoRuntimeDefault
A compliant pod spec:
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myregistry.io/myapp:v1.2.3@sha256:abc123...
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Note the image reference uses a digest (@sha256:...) rather than a mutable tag. This prevents tag-based supply chain attacks where an attacker pushes a malicious image to the same tag.
3. Network Policies
By default, every pod can communicate with every other pod in the cluster. This is the network equivalent of having no firewall. Network Policies are your in-cluster firewall rules.
Start with a default-deny policy for each namespace, then explicitly allow required traffic:
# 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
---
# Allow DNS resolution (required for almost everything)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# Allow frontend to talk to backend on port 8080
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Network policies require a CNI plugin that supports them. Calico, Cilium, and Antrea all implement the full NetworkPolicy spec. The default kubenet does not. If you are running Kubernetes on Proxmox or bare metal, Cilium is our recommended CNI for its combined networking, security, and observability capabilities.
4. Image Scanning in CI/CD
Do not let vulnerable images reach your cluster. Integrate image scanning into your CI/CD pipeline so that builds fail if critical vulnerabilities are detected:
# GitLab CI example with Trivy
scan-image:
stage: security
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image --exit-code 1 --severity CRITICAL,HIGH
--ignore-unfixed
${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
allow_failure: false
For GitHub Actions:
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE }}
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
Beyond scanning, enforce that only images from trusted registries can run in your cluster. Use an admission controller like Kyverno or OPA Gatekeeper:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
validationFailureAction: Enforce
rules:
- name: validate-registries
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Images must come from the trusted registry."
pattern:
spec:
containers:
- image: "myregistry.io/*"
5. Runtime Security with Falco
Image scanning catches known vulnerabilities before deployment. Runtime security catches anomalous behavior during execution — things like a container spawning a shell, reading /etc/shadow, or making unexpected network connections.
Falco is the de facto standard for Kubernetes runtime security. It monitors system calls and alerts on suspicious activity:
# Falco custom rule example
- rule: Terminal shell in container
desc: Detect a shell spawned in a container
condition: >
spawned_process and container and
proc.name in (bash, sh, zsh, dash) and
not proc.pname in (cron, crond, supervisord)
output: >
Shell spawned in container
(user=%user.name container=%container.name
shell=%proc.name parent=%proc.pname
image=%container.image.repository)
priority: WARNING
tags: [container, shell]
- rule: Unexpected outbound connection
desc: Detect outbound connections to non-whitelisted IPs
condition: >
outbound and container and
not fd.sip in (10.0.0.0/8, 172.16.0.0/12)
output: >
Unexpected outbound connection
(container=%container.name image=%container.image.repository
connection=%fd.name)
priority: NOTICE
tags: [container, network]
Deploy Falco using the Helm chart with falcosidekick to route alerts to Slack, PagerDuty, or your SIEM.
6. Audit Logging
Kubernetes API server audit logs record every request to the API. Without them, you cannot investigate security incidents. Enable a comprehensive audit policy:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all changes to secrets at Metadata level (no secret data)
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# Log all changes to RBAC
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
# Log pod exec/attach (potential interactive access)
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# Log all other write operations at Request level
- level: Request
verbs: ["create", "update", "patch", "delete"]
# Ignore read-only requests to reduce volume
- level: None
verbs: ["get", "list", "watch"]
Ship these logs to a centralized system (Loki, Elasticsearch, or a cloud SIEM) where they cannot be tampered with. Log retention should be at least 90 days, longer if compliance requires it.
7. CIS Benchmark Compliance
The CIS Kubernetes Benchmark provides a comprehensive list of security configurations. Rather than checking them manually, automate it:
# Run kube-bench against your cluster
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
# Check results
kubectl logs job/kube-bench
Common CIS findings we see in audits:
- Anonymous authentication enabled on the API server
--kubelet-certificate-authoritynot set- etcd not encrypted at rest
- Admission controllers not enabled (specifically
NodeRestriction,PodSecurity)
Address these in your cluster provisioning automation so that every new cluster starts CIS-compliant.
The Hardening Checklist Summary
| Area | Action | Priority |
|---|---|---|
| RBAC | No default cluster-admin, scoped Roles per app | Critical |
| Pod Security | Restricted PSS on all production namespaces | Critical |
| Network Policies | Default deny, explicit allow per service | Critical |
| Image Security | Scan in CI/CD, enforce trusted registries | High |
| Runtime Security | Deploy Falco with custom rules | High |
| Audit Logging | Enable API audit logs, ship to central SIEM | High |
| CIS Benchmarks | Automate checks, remediate findings | Medium |
| Secrets Management | Use external secrets operator (Vault, cloud KMS) | High |
| etcd Encryption | Enable encryption at rest | High |
| API Server | Disable anonymous auth, enable admission controllers | Critical |
Conclusion
Kubernetes security is not a one-time task. It is a continuous practice that evolves as your cluster grows and threats change. The checklist above covers the essentials, but security also depends on the surrounding infrastructure — your network design, your cloud architecture, and your team’s operational discipline.
If you are unsure where your clusters stand, a focused security audit can identify the gaps quickly. At robto, we run these assessments regularly and provide actionable remediation plans — not just a report, but a working pull request with the fixes.