kube-bench: Run the CIS Kubernetes Benchmark & Fix Findings
A practical guide to running kube-bench, reading its output, and remediating the most common CIS Kubernetes Benchmark FAIL and WARN findings on any cluster.
kube-bench: Running the CIS Kubernetes Benchmark (and Fixing the Findings)
Almost every Kubernetes security review eventually reaches the same milestone: “run the CIS Benchmark and show us the results.” The tool that produces those results is kube-bench, an open-source scanner from Aqua Security that checks a cluster against the CIS Kubernetes Benchmark. It is fast to run and painful to interpret — a fresh scan can return well over a hundred results, and most of them are noise for your environment.
This guide walks through what the benchmark actually is, how kube-bench maps to it, how to run it on both self-managed and managed clusters, and — the part that matters — how to remediate the FAIL and WARN findings you will genuinely see. By the end you will be able to triage a raw report down to the handful of controls that change your risk posture.
What the CIS Kubernetes Benchmark Is
The CIS Kubernetes Benchmark is a consensus-built configuration standard maintained by the Center for Internet Security. It is versioned against Kubernetes releases (for example, CIS Benchmark 1.9 targets Kubernetes 1.27), so the control numbers and expected values shift between versions — always match the benchmark to your cluster’s minor version. Each control is prescriptive: it names a file or flag, states the expected setting, and gives an audit command and a remediation.
The benchmark is organized into sections:
| Section | Scope |
|---|---|
| 1. Control Plane Components | API server, controller-manager, scheduler, etcd flags and file permissions |
| 2. etcd | etcd TLS, peer authentication, client cert auth |
| 3. Control Plane Configuration | Authentication and authorization (RBAC, admission control) |
| 4. Worker Nodes | Kubelet configuration and file permissions |
| 5. Policies | RBAC, pod security, network policies, secrets, service accounts |
Every control carries a scoring level. Level 1 controls are the baseline you should apply everywhere with minimal operational impact. Level 2 controls are stricter and may require application or platform changes — reserve them for high-security clusters. Controls are also marked Automated (a script can test them) or Manual (a human must verify). That Automated/Manual split maps directly onto kube-bench’s PASS/FAIL versus WARN output, which is the single most useful thing to understand before you read a report.
How kube-bench Maps to the Benchmark
kube-bench is a Go binary that reads the benchmark definitions from YAML and runs the audit commands against your node. For each control it emits one of:
- [PASS] — the automated test succeeded.
- [FAIL] — the automated test ran and the value was wrong.
- [WARN] — the control is Manual; kube-bench cannot decide, so it prints the command for you to check.
- [INFO] — informational, no action implied.
kube-bench inspects the running process flags (for example, the kube-apiserver command line), the static pod manifests in /etc/kubernetes/manifests, and config files like the kubelet’s config.yaml. That is why where you run it matters: control-plane checks only produce real results on a node where the control-plane processes and manifests actually live.
Running kube-bench
The cleanest way to run a one-off scan is as a Job in the cluster. Aqua publishes a manifest that runs kube-bench in a pod with the required host mounts:
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
# wait for it to finish, then read the report
kubectl logs job/kube-bench
For a self-managed cluster you usually want to run it directly on each node so it can see the control-plane manifests and kubelet config:
# on a control-plane node
docker run --rm --pid=host \
-v /etc:/etc:ro \
-v /var:/var:ro \
-t aquasec/kube-bench:latest \
run --targets=master,etcd,controlplane,policies
# on a worker node
docker run --rm --pid=host \
-v /etc:/etc:ro \
-v /var:/var:ro \
-t aquasec/kube-bench:latest \
run --targets=node,policies
Pin the benchmark version so you are testing against the right control set. Save the default human-readable output for triage, and export JSON if you plan to feed the results into a pipeline:
# human-readable report (contains the [PASS]/[FAIL]/[WARN] lines)
kube-bench run --benchmark cis-1.9 | tee kube-bench-report.txt
# machine-readable copy for a pipeline
kube-bench run --benchmark cis-1.9 --json > kube-bench-report.json
# quick triage: how many of each result?
grep -oE '\[(PASS|FAIL|WARN|INFO)\]' kube-bench-report.txt | sort | uniq -c
If you just want a fast, high-signal read on a cluster before you commit to the full kube-bench workflow — especially on managed clusters where half the sections do not apply — the open-source k8s-audit tool runs from your kubeconfig in seconds and surfaces the handful of misconfigurations that actually move your risk (public service accounts, wildcard RBAC, missing network policies, privileged pods). Think of it as the triage pass; kube-bench is the deep, node-level audit you run once you know where to look.
Managed Cluster Caveats: EKS, GKE, AKS
This trips up almost everyone. On managed Kubernetes, you do not control the control plane, so kube-bench cannot — and should not try to — evaluate Section 1, 2, or 3 control-plane flags. Attempting to run the master target on EKS produces a wall of FAILs that are not yours to fix.
kube-bench ships provider-specific benchmarks for exactly this reason:
# EKS
kube-bench run --benchmark eks-1.5.0 --targets=node,policies,managedservices
# GKE
kube-bench run --benchmark gke-1.6.0 --targets=node,policies,managedservices
# AKS
kube-bench run --benchmark aks-1.7.0 --targets=node,policies,managedservices
| Provider | What you own | What the provider owns |
|---|---|---|
| EKS | Node config, RBAC, network policies, pod security | API server, etcd, controller-manager, scheduler |
| GKE | Node config (limited), workload policies | Full control plane; many node flags on GKE-managed nodes |
| AKS | Node config, RBAC, network policies | Full control plane |
On GKE with Autopilot and on Fargate nodes you also do not control the kubelet, so even parts of Section 4 shift to the provider. The practical takeaway: on managed clusters, focus your remediation energy on Section 4 (where you still manage nodes) and Section 5 (Policies), and treat the control-plane sections as the provider’s responsibility — documented in their published CIS attestations.
Triaging the Noise
A raw report is not a to-do list. Before you touch anything, filter it:
- Drop controls that do not apply to your platform. Managed control plane? Ignore Sections 1-3. Not running etcd yourself? Ignore Section 2.
- Separate FAIL from WARN. FAILs are automated and concrete — start here. WARNs are manual items you schedule for review, not emergencies.
- Rank by scoring level and blast radius. A Level 1 FAIL on
--anonymous-author a wildcard ClusterRole outranks a Level 2 file-permission nit. - Batch by remediation surface. Many kubelet findings are fixed in one config file; many RBAC findings are fixed by editing a handful of roles. Group them.
# pull just the FAILs with their control IDs for a work queue
grep '\[FAIL\]' kube-bench-report.txt
Fixing the Most Common Findings
Below are the findings that show up on nearly every real cluster, with the remediation that actually clears them.
Control-Plane Flags (Section 1, self-managed only)
The API server manifest lives at /etc/kubernetes/manifests/kube-apiserver.yaml. Editing it triggers a restart of the static pod. Common FAILs and fixes:
# /etc/kubernetes/manifests/kube-apiserver.yaml (excerpt)
spec:
containers:
- command:
- kube-apiserver
- --anonymous-auth=false # 1.2.1
- --authorization-mode=Node,RBAC # 1.2.6 -- never just AlwaysAllow
- --profiling=false # 1.2.18
- --audit-log-path=/var/log/apiserver/audit.log # 1.2.16
- --audit-log-maxage=30 # 1.2.17
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml # 1.2.31
The most impactful of these is encryption at rest for Secrets (--encryption-provider-config), which stops etcd from storing Secrets in plaintext, and audit logging, which gives you a record of API activity. Both are frequently FAIL on kubeadm-built clusters.
Kubelet Configuration (Section 4)
Kubelet findings are the highest-value ones you can fix on managed nodes. Prefer editing the kubelet config file (/var/lib/kubelet/config.yaml) over flags:
# /var/lib/kubelet/config.yaml (excerpt)
authentication:
anonymous:
enabled: false # 4.2.1 -- disable anonymous kubelet access
webhook:
enabled: true
authorization:
mode: Webhook # 4.2.2 -- never AlwaysAllow
readOnlyPort: 0 # 4.2.4 -- close the unauthenticated read-only port
protectKernelDefaults: true # 4.2.6
tlsCertFile: /var/lib/kubelet/pki/kubelet.crt # 4.2.10
tlsPrivateKeyFile: /var/lib/kubelet/pki/kubelet.key
Then restart the kubelet:
systemctl daemon-reload && systemctl restart kubelet
An open, anonymous kubelet read-only port (10255) is a genuine exposure — it leaks pod and node metadata to anyone who can reach the node. readOnlyPort: 0 and anonymous.enabled: false are worth prioritizing.
RBAC and Service Accounts (Section 5.1)
These are automatable to check but require judgement to fix. The big three:
# 5.1.1 -- who has cluster-admin?
kubectl get clusterrolebindings -o json | \
jq -r '.items[] | select(.roleRef.name=="cluster-admin") |
.metadata.name + " -> " + (.subjects // [] | map(.name) | join(","))'
# 5.1.3 -- find wildcard verbs/resources in roles
kubectl get clusterroles -o json | \
jq -r '.items[] | select(any(.rules[]?; (.resources[]? == "*") or (.verbs[]? == "*"))) | .metadata.name'
Remediation: replace cluster-admin bindings with narrowly scoped roles, remove * verbs and resources, and stop mounting default service account tokens where they are not used:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
automountServiceAccountToken: false # 5.1.6
Network Policies (Section 5.3)
kube-bench flags namespaces with no NetworkPolicy because the Kubernetes default is allow-all. Find the gaps and apply a default-deny baseline:
# namespaces with no NetworkPolicy at all
comm -23 \
<(kubectl get ns -o name | sed 's|namespace/||' | sort) \
<(kubectl get netpol -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' | sort -u)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
Pod Security (Section 5.2)
Modern clusters (1.25+) use Pod Security Admission rather than the removed PodSecurityPolicy. kube-bench checks that you are enforcing a baseline. Label your namespaces:
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
--overwrite
The restricted profile blocks privilege escalation, host namespaces, and running as root — the controls behind most of the Section 5.2 findings.
From Findings to a Repeatable Baseline
Fixing findings by hand once is fine; the goal is to stop them from coming back. That means turning each remediation into a template you apply on every cluster — default-deny NetworkPolicies, PSA labels, tightened RBAC, kubelet config, and encryption-at-rest — and re-running kube-bench in CI to catch drift. Building that library from scratch is the slow part. The full k8s-security.pro kit ships a 50-point checklist, 25 production-ready YAML templates, and Helm/Kustomize bundles already mapped to the CIS Kubernetes Benchmark and SOC2 controls, so the remediation for each finding is a file you apply rather than a control you research.
A sensible cadence: k8s-audit for the daily/PR-time fast pass, kube-bench for the periodic deep node-level audit, and versioned templates so every fix is codified and re-provable.
FAQ
Do I need to run kube-bench on every node? For self-managed clusters, yes — run it on each control-plane node (to see the API server, scheduler, controller-manager, and etcd manifests) and each worker node (to see the kubelet config). File permissions and kubelet settings can differ per node. On managed clusters you only run the node and policy targets.
Why does kube-bench report FAILs I cannot fix on EKS?
You are almost certainly running the default or master benchmark instead of the provider benchmark. Switch to --benchmark eks-1.5.0 --targets=node,policies,managedservices; the control-plane controls you cannot access are then attributed to AWS, not to you.
How do I stop remediated findings from regressing? Codify each fix as a manifest (NetworkPolicy, PSA labels, RBAC, kubelet config) in version control and run kube-bench as a scheduled Job or CI step. Treat a new FAIL as a failed build, the same way you would a failing test.
Is kube-bench the same as a vulnerability scanner? No. kube-bench checks cluster configuration against the CIS Benchmark. Image and dependency CVEs are a different job handled by tools like Trivy. You want both: kube-bench for how the cluster is configured, Trivy for what is running inside it.
Start with the fast triage pass using open-source k8s-audit, then run kube-bench for the deep audit and remediate with the mapped templates at k8s-security.pro.
Get the Complete 50-Point Security Checklist
5 production-ready templates + audit checklist highlights, free for K8s engineers.
Get the Free K8s Security Quick-Start Kit
Get 5 essential templates + audit checklist highlights delivered to your inbox.
No spam. Unsubscribe anytime.
Secure Your Kubernetes Clusters
Get the complete 50-point audit checklist and 20+ production-ready YAML templates.
View Pricing Plans