kube-bench у Kubernetes: аудит CIS Benchmark у CI/CD 2026

Практичний посібник з kube-bench у 2026: CIS Benchmark v1.10, Job і CronJob шаблони, інтеграція з GitHub Actions і GitLab CI, усунення FAIL.

Оновлено: 28 серпня 2026

kube-bench, це open-source CLI-інструмент від Aqua Security, який перевіряє Kubernetes-кластер на відповідність CIS Kubernetes Benchmark і повідомляє PASS/FAIL/WARN для кожного контролю з control-plane, etcd, kubelet і policies. У 2026 році kube-bench підтримує CIS Benchmark v1.10 для kubeadm 1.29–1.31, а також окремі профілі для EKS, GKE, AKS, RKE2 і k3s. У цьому посібнику я покажу, як встановити його як CronJob, підключити до CI/CD і (найважливіше) не потонути в шумі результатів.

  • kube-bench v0.9+ реалізує CIS Kubernetes Benchmark v1.10 (жовтень 2025) для kubeadm 1.29–1.31.
  • Найкращий спосіб запуску в production, це Kubernetes Job або DaemonSet зі змонтованими /etc, /var і hostPID.
  • JSON-вивід (--json) плюс jq-фільтр перетворюють kube-bench у прохідний gate для GitHub Actions і GitLab CI.
  • Managed-кластери (EKS, GKE, AKS) вимагають окремих target-профілів, бо control-plane перевірки там повертають скіп, і це нормально.
  • Реалістична стратегія: відсіювати accepted-risk чеки через --check/--skip і фейлити pipeline тільки на нових FAIL.

Що таке kube-bench і навіщо CIS-аудит

kube-bench, це Go-бінарник, який зчитує YAML-набір тестів під cfg/, виконує ps, читає файли конфігурацій (/etc/kubernetes/manifests/*.yaml, /var/lib/kubelet/config.yaml) і зіставляє результати з контролями CIS Kubernetes Benchmark. Кожен контроль повертає PASS, FAIL, WARN або INFO разом з remediation-полем, у якому CIS описує, як саме виправити знахідку.

Чому це важливо у 2026? Більшість регуляторних баз (PCI DSS 4.0, HIPAA Security Rule, EU CRA) вимагають підтверджувати, що control-plane і kubelet налаштовані згідно з визнаним промисловим стандартом. CIS у цьому сенсі, найдешевша й найшвидша ланка: жодних платних сканерів, жодних SaaS-агентів, тільки бінарник плюс YAML.

З точки зору DevSecOps-інженера kube-bench цікавий тим, що він майже нульової залежності: сам бінарник, YAML-тести й ps. Немає runtime-агента, немає webhook-контролера, немає постійного скану. Це робить його ідеальним для запуску як Kubernetes Job у CI-конвеєрі або як CronJob для регулярних щоденних перевірок. Я особисто починаю кожен новий кластер із kube-bench run, ще до того як розгортати Falco для runtime-безпеки на eBPF. Базовий хардненінг control-plane важливіший за eBPF-детектори, бо якщо apiserver дірявий, ніяка runtime-телеметрія його не врятує.

Які версії CIS Benchmark підтримує kube-bench у 2026

Станом на серпень 2026 актуальний реліз, це kube-bench v0.9.5, який покриває CIS Kubernetes Benchmark v1.10 (опубліковано CIS у жовтні 2025) для kubeadm 1.29, 1.30 і 1.31. Разом з ним постачаються окремі target-набори:

  • cis-1.10, стандартний kubeadm-профіль
  • eks-1.5.0, Amazon EKS
  • gke-1.6.0, Google GKE Standard і GKE Autopilot
  • aks-1.5.0, Azure AKS
  • rke2-cis-1.24, k3s-cis-1.8 для Rancher-стеків
  • ack-1.0, Alibaba ACK

Кожен target виключає ті контролі, які не мають сенсу на цій платформі. Наприклад, у EKS ви фізично не маєте доступу до etcd або kube-apiserver-хосту, тому kube-bench для EKS пропускає весь Master-розділ і концентрується на node-checks, IAM ролі та Secrets Manager.

Це важливо. Якщо ви запускаєте kube-bench run на EKS без явного --targets node,policies, ви отримаєте купу FAIL типу «cannot read /etc/kubernetes/manifests» і невірно вирішите, що ваш кластер небезпечний. Я сам колись півдня згаяв на це, поки не перечитав офіційну документацію по targets.

Як встановити kube-bench у Kubernetes

Є три способи розгорнути kube-bench, і в кожного своє місце. Для одноразового аудиту з ноутбука, статичний бінарник. Для CI-pipeline, контейнер. Для регулярного моніторингу production, Job або CronJob всередині кластера.

Локальний бінарник для швидкого тесту

# Завантаження останньої версії з GitHub Releases
KB_VERSION=0.9.5
curl -sSL -o kube-bench.tar.gz \
  https://github.com/aquasecurity/kube-bench/releases/download/v${KB_VERSION}/kube-bench_${KB_VERSION}_linux_amd64.tar.gz
tar -xzf kube-bench.tar.gz
sudo mv kube-bench /usr/local/bin/
sudo mkdir -p /etc/kube-bench && sudo cp -r cfg /etc/kube-bench/

# Аудит вузла (потрібен sudo для читання kubelet конфігів)
sudo kube-bench run --targets node,policies --benchmark cis-1.10

Запуск як Kubernetes Job

# job-kube-bench.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: kube-bench-master
  namespace: kube-system
spec:
  template:
    spec:
      hostPID: true
      nodeSelector:
        node-role.kubernetes.io/control-plane: ""
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
        - name: kube-bench
          image: docker.io/aquasec/kube-bench:v0.9.5
          command:
            - kube-bench
            - run
            - --targets=master,etcd,controlplane,policies
            - --benchmark=cis-1.10
            - --json
          volumeMounts:
            - { name: var-lib-etcd, mountPath: /var/lib/etcd, readOnly: true }
            - { name: var-lib-kubelet, mountPath: /var/lib/kubelet, readOnly: true }
            - { name: etc-systemd, mountPath: /etc/systemd, readOnly: true }
            - { name: etc-kubernetes, mountPath: /etc/kubernetes, readOnly: true }
            - { name: usr-bin, mountPath: /usr/local/mount-from-host/bin, readOnly: true }
      restartPolicy: Never
      volumes:
        - { name: var-lib-etcd, hostPath: { path: /var/lib/etcd } }
        - { name: var-lib-kubelet, hostPath: { path: /var/lib/kubelet } }
        - { name: etc-systemd, hostPath: { path: /etc/systemd } }
        - { name: etc-kubernetes, hostPath: { path: /etc/kubernetes } }
        - { name: usr-bin, hostPath: { path: /usr/bin } }

Змонтуйте hostPath-и тільки в read-only, бо kube-bench нічого не пише на хост. Прапорець hostPID: true потрібен, щоб ps aux усередині Pod показував процеси хоста (kube-apiserver, etcd), інакше половина перевірок повернеться WARN. Так, це страшнувато для security-мислення (запускати щось із hostPID), але kube-bench, це read-only процес і образ від Aqua підписаний.

Як запустити kube-bench як Job і DaemonSet

Для одноразового CI-pipeline достатньо Job. Для щоденного моніторингу production я використовую CronJob плюс DaemonSet-варіант. DaemonSet гарантує, що перевірка node-checks запуститься на кожному worker, а CronJob обгортає master/etcd-профілі раз на добу. Ось шаблон CronJob:

# cronjob-kube-bench-node.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: kube-bench-node
  namespace: kube-system
spec:
  schedule: "30 3 * * *"   # 03:30 UTC щодня
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      backoffLimit: 1
      template:
        spec:
          hostPID: true
          restartPolicy: Never
          containers:
            - name: kube-bench
              image: docker.io/aquasec/kube-bench:v0.9.5
              args:
                - run
                - --targets=node,policies
                - --benchmark=cis-1.10
                - --json
                - --outputfile=/reports/kube-bench-$(NODE_NAME).json
              env:
                - name: NODE_NAME
                  valueFrom: { fieldRef: { fieldPath: spec.nodeName } }
              volumeMounts:
                - { name: reports, mountPath: /reports }
                - { name: var-lib-kubelet, mountPath: /var/lib/kubelet, readOnly: true }
                - { name: etc-kubernetes, mountPath: /etc/kubernetes, readOnly: true }
                - { name: etc-systemd, mountPath: /etc/systemd, readOnly: true }
          volumes:
            - name: reports
              persistentVolumeClaim: { claimName: kube-bench-reports }
            - { name: var-lib-kubelet, hostPath: { path: /var/lib/kubelet } }
            - { name: etc-kubernetes, hostPath: { path: /etc/kubernetes } }
            - { name: etc-systemd, hostPath: { path: /etc/systemd } }

Звіти складаємо у PVC kube-bench-reports, а зверху навішуємо sidecar Fluent Bit або promtail, який відправляє JSON у Loki/Elastic. У такому режимі ви отримуєте trending на дашборді Grafana: скільки FAIL сьогодні порівняно з минулим тижнем, який worker раптово регресує. У мене був випадок, коли новий AMI на EKS-нодах змінив дефолтний seccomp-профіль, і kube-bench підсвітив це вже через 4 години після rolling-update, ще до того як security-команда встигла помітити.

Як інтегрувати kube-bench у CI/CD pipeline

Головна ідея pipeline-first підходу: не «kube-bench колись пробіжить у cron», а «PR, який ламає CIS-контроль, не мержиться». Реалізується це двома кроками: (1) запустити kube-bench у ephemeral-кластері (kind, minikube або k3d), (2) розпарсити JSON і зафейлити job, якщо з'явилися нові FAIL, порівняно з baseline у репозиторії.

GitHub Actions приклад

# .github/workflows/kube-bench.yml
name: kube-bench CIS audit
on:
  pull_request:
    paths:
      - "manifests/**"
      - "cluster/**"
      - ".github/workflows/kube-bench.yml"

jobs:
  cis-audit:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

      - name: Set up kind cluster
        uses: helm/kind-action@v1
        with:
          cluster_name: cis-test
          version: v0.24.0
          kubectl_version: v1.31.0

      - name: Deploy manifests under test
        run: kubectl apply -f manifests/

      - name: Run kube-bench
        run: |
          docker run --rm --pid=host \
            -v /etc:/etc:ro -v /var:/var:ro \
            docker.io/aquasec/kube-bench:v0.9.5 \
            run --targets=node,policies --benchmark=cis-1.10 --json \
            > kube-bench.json

      - name: Fail on new CIS violations
        run: |
          NEW_FAIL=$(jq '[.Controls[].tests[].results[] | select(.status=="FAIL")] | length' kube-bench.json)
          BASELINE=$(jq '.baseline_fail // 0' cluster/kube-bench-baseline.json)
          echo "FAIL count: $NEW_FAIL (baseline: $BASELINE)"
          if [ "$NEW_FAIL" -gt "$BASELINE" ]; then
            jq '.Controls[].tests[].results[] | select(.status=="FAIL") | {id: .test_number, desc: .test_desc}' kube-bench.json
            exit 1
          fi

      - uses: actions/upload-artifact@v4
        with:
          name: kube-bench-report
          path: kube-bench.json

GitLab CI приклад

# .gitlab-ci.yml
cis-audit:
  stage: security
  image: docker.io/aquasec/kube-bench:v0.9.5
  variables:
    KUBECONFIG: /tmp/kubeconfig
  before_script:
    - echo "$K3D_KUBECONFIG" > "$KUBECONFIG"
  script:
    - kube-bench run --targets=node,policies --benchmark=cis-1.10 --json --outputfile report.json
    - |
      FAIL=$(jq '[.Controls[].tests[].results[] | select(.status=="FAIL")] | length' report.json)
      [ "$FAIL" -le "${CIS_MAX_FAIL:-0}" ] || { jq '.' report.json; exit 1; }
  artifacts:
    when: always
    paths: [report.json]
    reports:
      junit: report.xml
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

І ще одна пораду про --junit. Починаючи з v0.8, kube-bench підтримує вивід у форматі JUnit XML через прапорець --junit. Це критично для GitLab, GitHub Actions test-reporter, Jenkins і Azure DevOps: усі вони автоматично рендерять кожен CIS-контроль як тест і показують дифер на PR. Такий досвід у 10 разів приємніший, ніж копирсатися у JSON-звіті вручну.

Як усунути типові failed checks kube-bench

Після першого запуску у свіжому kubeadm-кластері ви типово побачите 15–25 FAIL. Більшість з них, це швидкі виправлення в маніфестах kube-apiserver і kubelet. Чесно, я стикаюся з тими самими 4–5 контролями у майже кожному новому кластері. Ось найпоширеніші у 2026:

1.2.16 Ensure that the admission control plugin PodSecurity is set

З виходом Kubernetes 1.25 PodSecurityPolicy видалено, і CIS вимагає ввімкнути вбудований admission controller PodSecurity. Виправлення полягає у додаванні рядка у /etc/kubernetes/manifests/kube-apiserver.yaml:

- --enable-admission-plugins=NodeRestriction,PodSecurity
- --admission-control-config-file=/etc/kubernetes/policies/pod-security.yaml

4.2.6 Ensure that the --protect-kernel-defaults argument is set to true

Kubelet за замовчуванням дозволяє sysctl-и, які перевизначають kernel-параметри. Відредагуйте /var/lib/kubelet/config.yaml:

protectKernelDefaults: true

Далі виконайте systemctl restart kubelet. Якщо kubelet не стартує, значить ваш pod намагається змінити sysctl без allowedUnsafeSysctls, і це власне те, що CIS і хоче відловити.

5.1.5 Ensure that default service accounts are not actively used

Створіть окремий SA для кожного workload, а в дефолтному встановіть automountServiceAccountToken: false:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: myapp
automountServiceAccountToken: false

4.2.13 Ensure that a limit is set on pod PIDs

Додайте у kubelet-конфіг podPidsLimit: 4096, це запобігає fork-bomb атакам всередині одного pod. Значення 4096 достатнє для більшості робочих навантажень; для JVM-важких workload-ів підніміть до 8192.

Для системного контексту цих виправлень раджу подивитись посібник з auditd на Linux і MITRE ATT&CK правил: kubelet і apiserver-події корисно паралельно логувати через auditd, щоб мати аудит-слід поза самим Kubernetes.

kube-bench vs kube-hunter vs Trivy K8s: порівняння

Три інструменти часто плутають, хоча вони розв'язують різні задачі. Ось коротка порівняльна таблиця, яку я використовую під час onboarding-ів:

Критерійkube-benchkube-hunterTrivy K8s
Тип перевіркиКонфігурація (CIS)Penetration probingCVE + misconfig
Джерело правилCIS Benchmark v1.10Kube-hunter DBTrivy DB + Kubernetes NSA
Runtime у кластеріJob/DaemonSetJob або external scanCronJob через trivy-operator
Формат виводуJSON, JUnit, plainJSON, plainJSON, SARIF, table
Найкраще дляCompliance аудит control-planeАтакерська перспективаVulnerability + policy як у SAST
Активність (2026)Активний, v0.9.5Архівний з 2024Активний, v0.55+

Мій робочий сетап у команді: kube-bench у CI + CronJob для compliance-звітності, Trivy K8s Operator для CVE/misconfig-скану runtime-workload-ів, а kube-hunter я більше не тримаю. З березня 2024 Aqua Security перевела його у read-only архів, тож CVE у самому інструменті вже не патчаться. Для pentest-перспективи краще брати peirates або платний Panoptica scan.

Як зменшити шум від kube-bench у production

Після другого-третього циклу вам обов'язково доведеться навчитися глушити accepted-risk контролі. kube-bench має три механізми для цього:

  • --check 1.2.16,1.2.20, запустити тільки перелічені контролі.
  • --skip 4.2.6,5.1.5, пропустити перелічені (позначаються INFO у звіті).
  • Кастомізація YAML у cfg/cis-1.10/master.yaml: можна відредагувати text, audit, tests і навіть додати власні контролі.

Найправильніший підхід, це тримати файл cluster/kube-bench-allowlist.yaml у git з коментарем «чому пропускаємо» і посиланням на Jira/GitHub Issue. Приклад:

# cluster/kube-bench-allowlist.yaml
skip:
  - id: "3.2.1"
    reason: "Audit log ingested by Vector via journald, not via --audit-log-path"
    ticket: "SEC-482"
  - id: "1.2.22"
    reason: "OIDC provider is not required for internal clusters"
    ticket: "SEC-501"

У CI-скрипті парсимо цей YAML і збираємо аргумент --skip динамічно. Так кожен «пропуск» проходить code review і не забувається на роки. Для розширеної стратегії reduce-noise у security-tooling див. посібник з Lynis для host-хардненінгу. Там детально розглядається філософія baseline-diff підходу, яка чудово переноситься на Kubernetes.

Метрики kube-bench у Prometheus і Grafana

Готового exporter-а офіційно немає, але спільнота підтримує kube-bench-exporter як sidecar, який парсить JSON і публікує метрики у форматі Prometheus. Приклад PromQL для дашборду:

# Кількість FAIL за node
sum by (node) (kube_bench_control_status{status="FAIL"})

# Trend FAIL за 7 днів
increase(kube_bench_control_status{status="FAIL"}[7d])

# Топ 10 найчастіших порушень
topk(10, count by (control_id, control_desc) (kube_bench_control_status{status="FAIL"}))

Такий дашборд закриває питання «а покажи мені drift compliance-и за квартал» і рятує від годинного пошуку в JSON. Для налаштування SIEM-конвеєра, який паралельно споживає kube-bench JSON плюс runtime-alert-и, я тримаю відкритим у сусідній вкладці офіційний Kubernetes Security Checklist. Він добре доповнює CIS практичними порадами, яких у самому benchmark іноді не вистачає.

Часті питання

Чи потрібен root-доступ для запуску kube-bench?

Так, для читання kubelet-конфігів (/var/lib/kubelet/config.yaml), маніфестів control-plane (/etc/kubernetes/manifests/) і systemd-юнітів потрібен root або privileged-контейнер з hostPID: true. Read-only mount кореневих директорій хоста плюс hostPID достатні, жодних змін на файловій системі kube-bench не робить.

Як часто треба запускати kube-bench у продакшн-кластері?

Мінімум щодня через CronJob плюс кожен PR, який чіпає маніфести control-plane чи kubelet-конфіги. Для регульованих середовищ (PCI DSS 4.0, HIPAA) корисно тримати weekly full-report з архівом за 12 місяців, це закриває вимогу до безперервного моніторингу конфігураційних змін.

Чи працює kube-bench у managed Kubernetes (EKS, GKE, AKS)?

Так, але треба явно вказати відповідний target: --benchmark eks-1.5.0, gke-1.6.0 або aks-1.5.0. У managed-кластерах control-plane закритий, тому перевіряються тільки node-компоненти й Kubernetes-об'єкти. Cloud-provider частину CIS-контролів (IAM, KMS, VPC) kube-bench не покриває, для цього використовуйте окремі cloud-native інструменти на кшталт Prowler або Steampipe.

Як інтегрувати результати kube-bench у Grafana або SIEM?

Найпростіше, вивантажувати JSON у PVC й перекидати Fluent Bit-ом у Loki/Elastic або S3. Для Grafana-дашбордів спільнотний kube-bench-exporter публікує kube_bench_control_status метрику з лейблами control_id, node, status. У SIEM (Wazuh, Splunk, Sentinel) відправляйте JUnit XML, більшість парсить його одразу.

Що робити, якщо kube-bench повертає FAIL для контролю, який ми свідомо не виконуємо?

Задокументуйте виняток у git-репозиторії (kube-bench-allowlist.yaml) з причиною та ticket-посиланням, і додайте контроль у --skip у CI-джобі. Ніколи не редагуйте YAML-тести всередині контейнера, бо це втратиться при апгрейді. Регулярно (раз на квартал) переглядайте allowlist: обставини змінюються, і accepted risk може стати непотрібним.

Raj Patel
Про Автора Raj Patel

DevSecOps engineer who's gradually turning every CI pipeline he sees into a security-checking machine.