Scanners

kube-bench-job

On this page
  • Control: kubernetes
  • Tool: Aqua kube-bench, as a container image, https://github.com/aquasecurity/kube-bench
  • Status: ✅ implemented (CIS sections 1–4)
  • Target: a Kubernetes cluster (KubernetesTarget)
  • License / terms: Apache-2.0 (permissive). Run as a pod from the published image.
  • Effects: mutate (creates a Job), privilege (hostPID, host path mounts)

What it does

Creates a short-lived Job in the cluster, waits for it, reads its output, and deletes it.

kube-bench run --json --targets master,node,etcd,controlplane

Selected with kubeBenchJob: { enabled: true } on the control. The default scanner, kube-bench, stays read-only and covers section 5.

Why a separate scanner rather than a mode

The two have genuinely different contracts, and the difference is the kind a user should be asked about rather than discover:

kube-benchkube-bench-job
Touches the clusterreads through the APIcreates a Job
Needs locallykube-bench, kubectlnothing. The image carries both
Node accessnonehostPID, host paths mounted read-only
CIS sections5 (policies)1–4 (master, node, etcd, controlplane)

Effects are declared per scanner, so keeping them apart is what lets the read-only path run unguarded while this one has to be accepted first:

config:
  allowEffects: [mutate, privilege]
  controls:
    kubernetes:
      enabled: true
      kubeBenchJob:
        enabled: true

Without that, the run stops before it creates anything and says what it would have done.

What the Job looks like

It follows kube-bench's own manifest, because the checks read files that exist only on a node:

  • hostPID: true, several checks inspect control-plane processes.
  • Twelve host paths mounted read-only, /etc/kubernetes, /var/lib/kubelet, /var/lib/etcd and the rest. Read-only throughout: a scan has no business being able to change what it inspects.
  • tolerations: [{operator: Exists}]. A control-plane node is tainted, and its configuration is most of what these sections check. Without this the Job is unschedulable exactly where it is most useful.
  • backoffLimit: 0. A failing benchmark is a result, not something to retry.
  • No service account or RBAC. These sections read the host filesystem, not the API, so the Job needs no permissions in the cluster.

This is a privileged pod. On a cluster enforcing the restricted Pod Security Standard it will be rejected, which is the standard working as intended. Run it in a namespace whose policy permits it.

Scope

The sections this Job runs read a node's own filesystem, which has no namespace, so a namespace scope is not unimplemented here, it is meaningless. The findings always describe the whole cluster.

A component that sets namespaces on its kubernetes entry therefore cannot be served by this scanner, so no Job is created for it and the report says so under Caveats as not-measured. A scanner that quietly does not run reads exactly like one that ran and found nothing.

Nothing needs disabling by hand. Declare the cluster twice, once narrowed to the namespaces a component owns, once whole, and the Job runs against the one that claims the whole cluster. draugr-k8s-policies reads the Kubernetes API and can be narrowed, so it serves both.

The namespace option below is unrelated: it is where the Job runs, not what it looks at.

Configuration

config:
  controls:
    kubernetes:
      kubeBenchJob:
        enabled: true
        namespace: default              # where the Job is created
        image: docker.io/aquasec/kube-bench:v0.16.0@sha256:75506f2…
        targets: master,node,etcd,controlplane
        nodeSelector: node-role.kubernetes.io/control-plane=
        timeout: 5m

The image is pinned by digest by default, and the digest is the part that matters. A tag is a mutable pointer that can be repushed to different content, so a tag alone leaves you with a scan whose result can change while nothing in the descriptor does. The digest makes the pull reproducible and lets the runtime reject content that does not match, which is the same guarantee draugr tools install gets from verifying a checksum before putting a binary on your PATH.

The tag is kept alongside it for readability: @sha256:… on its own says nothing about which version is running, and someone reading the descriptor should be able to tell.

The default image is the kube-bench release draugr tools install pins for the kube-bench scanner.

Override it for a private registry or an air-gapped mirror. If you do, pin yours by digest too, and use the same kube-bench release so both scanners audit a cluster against the same benchmark definitions. This is the one setting where a convenient value quietly weakens the report.

Cleanup

The Job is deleted on every path, including a failed or canceled scan, using a context that survives the caller's cancellation, a Job left behind in someone's cluster is the worst thing this scanner could do. If the wait times out, the Job is removed and the error says so.

Notes

  • Managed clusters. On GKE, EKS, AKS and ACK the control plane is not yours, so master, etcd and controlplane cannot be inspected by any tool. targets: node is the useful setting there.
  • Findings are located at the cluster (kubernetes/<name>), the same as the read-only scanner.

Data

Nothing fetched. The benchmark definitions ship with the binary, and the image the Job runs is pulled by the cluster from wherever the descriptor names.