Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

salt-k8s-compliance

CIS Kubernetes Benchmark controls, packaged as a SEC/Locke tarball for direct import into RaaS. This repo is a focused extraction of the Kubernetes-specific pieces of saltstack-lock — it does not use the locke.py --build pipeline; scripts/create_k8s_cis_tarball.py is a standalone packager.

Repo layout

scripts/create_k8s_cis_tarball.py       # the tarball builder
benchmarks/CIS_Kubernetes_v2.0.1.meta   # top-level benchmark descriptor
salt/locke/system/CIS_Kubernetes_v2_0_1/
    <control_id>.meta                   # one per control — audit/remediation text (source of truth)
    <control_id>.sls                    # one per control — Salt state implementing the audit
    <control_id>_bad.sls                # optional — noncompliant test fixture (a few controls have one)
helm/kube-bench-job/                    # reference Helm chart for deploying kube-bench (see helm/kube-bench-job/README.md)
_build/, _dist/                         # generated by the script, gitignored — do not edit or commit

Every control's .sls calls salt['kube_bench_cache.status_for_check'](test_number=...) to read aggregated kube-bench results. That module lives outside this repo, in the saltext-kubernetes Salt extension (saltext/kubernetes/modules/kube_bench_cache.py) — it must be installed on any minion that actually runs these controls. This repo only needs .sls source text at build time; it does not execute the states, so the tarball builder itself has no dependency on that extension.

Setup

The builder needs two Python packages: msgpack and pyyaml.

cd salt-k8s-compliance
python3 -m venv .venv
source .venv/bin/activate
pip install msgpack pyyaml

(or uv venv && uv pip install msgpack pyyaml, or uv run --with msgpack --with pyyaml ... per-invocation if you have PyPI network access — see below). .venv/ is already gitignored.

Building the tarball

source .venv/bin/activate   # if not already active
python3 scripts/create_k8s_cis_tarball.py <target_version>

Example:

python3 scripts/create_k8s_cis_tarball.py 2_0_2
# -> _dist/k8s_cis_v2_0_2_format.tar.gz
  • <target_version> is required (e.g. 2_0_2 or 2.0.2). It's used to label the output benchmark and is stamped into every rewritten state ID and benchmark_id.
  • --src-version defaults to 2.0.1 (the current baseline in this repo) — only pass it if you've added a differently-versioned control directory.
  • --repo-root defaults to ..
  • --keep-build skips wiping _build//_dist/ first (they're wiped by default to avoid stale files leaking into the tarball).

Why bump the version on every build: RaaS rejects re-importing a benchmark with the same name ("cannot override existing custom benchmark"). Each new target version forces fresh rows instead of colliding with a previous import — bump it every time you re-upload (2_0_1 → 2_0_2 → 2_0_3 ...).

Upload with:

kubectl cp _dist/k8s_cis_v<target>_format.tar.gz salt-raas/<pod>:/opt/saltstack/raas/
kubectl exec -it salt-raas/<pod> -n salt-raas -- bash
cd /opt/saltstack/raas
salt-ssc compliance import k8s_cis_v<target>_format.tar.gz
salt-ssc compliance list | grep -i kubernetes   # verify

Downloading a prebuilt release

Customers who don't want to build the tarball themselves can grab a prebuilt one from this repo's GitHub Releases page — each release is tagged with the benchmark version (e.g. v2.0.2) and has the corresponding k8s_cis_v<version>_format.tar.gz attached as a downloadable asset.

Via the gh CLI:

gh release download v2.0.2 --repo <org>/salt-k8s-compliance --pattern '*.tar.gz'

Or via the web UI: open the Releases page, pick a version, and download the .tar.gz asset directly.

Once downloaded, skip straight to the upload steps above (kubectl cp / salt-ssc compliance import) — no Python setup or build step required.

Adding a new control

Each control is a <control_id>.meta + <control_id>.sls pair, where control_id mirrors the CIS section number with dots replaced by underscores (e.g. CIS 1.2.31 → 1_2_31).

1. Write <control_id>.meta

This is the source of truth — audit/remediation text, applicability, and the benchmark linkage. Follow the shape of an existing file, e.g. 1_2_1.meta:

version: 1
display_name: |
  1.2.31 <CIS control title>

global_description: |
  <one-line description>

osfinger:
  - Kubernetes

refs:
  - CIS: 1.2.31

benchmark_id:
  CIS_Kubernetes_v2.0.1:        # must match benchmarks/CIS_Kubernetes_v2.0.1.meta's name: exactly
    type: cis
    control_id: 1.2.31
    desc: |
      <same as display_name>
    profile:
      master: level1
    information: |
      <why this matters>
    audit: |
      <the exact audit mechanism — kube-bench test_number, or a command>
    remediation: |
      <how to fix it>

2. Write <control_id>.sls

Almost every control just aggregates the matching kube-bench check via kube_bench_cache.status_for_check. Copy the pattern from 1_1_1.sls:

{%- set UNIQUE_SLS_STR = UNIQUE_SLS_STR | default('local') %}
{%- set unique_id = UNIQUE_SLS_STR %}
{%- set kube_bench_report_path = kube_bench_report_path | default('/var/log/kube-bench.json') %}

{%- set result = salt['kube_bench_cache.status_for_check'](
    test_number='1.2.31',
    namespace=pillar.get('kube_bench', {}).get('namespace', 'kube-system'),
    label=pillar.get('kube_bench', {}).get('label', 'app=kube-bench'),
    cache_path=kube_bench_report_path,
    ttl_seconds=pillar.get('kube_bench', {}).get('ttl_seconds', 900),
    kubeconfig=pillar.get('kube_bench', {}).get('kubeconfig') or None,
    auth_mode=pillar.get('kube_bench', {}).get('auth_mode') or None,
    collection_strategy=pillar.get('kube_bench', {}).get('collection_strategy') or None,
    cronjob_name=pillar.get('kube_bench', {}).get('cronjob_name') or None,
    job_timeout=pillar.get('kube_bench', {}).get('job_timeout') or None
) %}
{%- set is_compliant = (result.status == 'PASS') %}
{%- set is_warn = (result.status == 'WARN') %}
{%- set diagnostic = result.status ~ ': 1.2.31 <CIS control title>\n' ~ result.comment %}

CIS_Kubernetes_v2_0_1-1_2_31_<short_slug>_{{ unique_id }}:
  locke.notify:
    - text: {{ diagnostic | tojson }}
    - result: {{ (is_compliant or is_warn) | tojson }}
    - status: {{ 'compliant' if is_compliant else ('notapplicable' if is_warn else 'noncompliant') }}

Only the state ID (must be CIS_Kubernetes_v2_0_1-<control_id>_..._{{ unique_id }}, matching the filename stem) and test_number/title text change per control. test_number must be the exact ID kube-bench itself reports for that check — verify it against a real kube-bench --json run or docs/kube-bench-output.json in saltstack-lock if you're unsure it lines up with the CIS number.

A handful of controls implement their own audit logic directly in Jinja instead of calling status_for_check (e.g. controls that check something kube-bench doesn't cover) — check neighboring .sls files in the same CIS section before assuming every control fits the aggregation pattern above.

3. (Optional) Write <control_id>_bad.sls

Only a few controls have one (1_1_1, 1_1_2, 1_2_1, 4_2_1, 4_2_10). It's a test fixture that puts the system in a known noncompliant state — since kube-bench results aren't something this repo can safely fake by mutating a live cluster, existing examples just emit a locke.notify with status: noncompliant matching the audit's failure shape. See 1_1_1_bad.sls. Add one only if you have a real way to exercise the noncompliant path.

4. Add the control ID to the benchmark file

Add <major>.<minor>[.<patch>] (dotted form, e.g. 1.2.31) to the ids: list in benchmarks/CIS_Kubernetes_v2.0.1.meta.

The build script does not validate this against the control directory — it will happily process every .meta/.sls pair it finds regardless of whether the control ID is listed here, so a missed ids: entry produces no error or warning. It matters downstream: RaaS associates a check to its benchmark by matching the check's benchmark_id: against the benchmark's own identity, and ids: is part of how the benchmark advertises its control set. Don't rely on the build output to catch a missing entry — check the ids: list by hand.

5. Rebuild and verify

python3 scripts/create_k8s_cis_tarball.py <target_version>

Check the console output for ✓ Processing control: <control_id> and confirm no ⚠ Warning: <control_id>.sls not found lines appear.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages