#!/usr/bin/env bash
# check-checkmk-kube-rules.sh: strictly read-only, offline check of Checkmk rules.mk files for Kubernetes special agent rules
# whose "Exclude nodes based on their role" list holds a role that starts with a dash (CVE-2026-121374, werk 17994).
# Checkmk rates it Critical, CVSS 4.0 9.4. Werk 17994: a role could be read by the agent as one of its own options, such as
# --profile. Flagging roles that start with a dash is OUR PATTERN. It only reads the files you point it at.
# No network, no file writes, nothing is run.
#
# Usage:
#   check-checkmk-kube-rules.sh --path PATH     PATH: a rules.mk file, or a folder searched for rules.mk files, such as
#                                               /omd/sites/mysite/etc/check_mk/conf.d/wato (documented by Checkmk)
# Option names come from Checkmk's public source: cluster_aggregation_exclude_node_roles (2.4.0 and later) and the older
# cluster-aggregation-exclude-node-roles. How rules.mk stores the value (a Python literal) is our reading, UNVERIFIED on a live site.
# A role list that itself contains a ] character is cut short; review such rules by hand.
# Exit: 0 no suspicious role, 1 suspicious role found, 4 usage or read error (including no rules.mk found).
set -u

usage() {
  cat >&2 <<'U'
Usage: check-checkmk-kube-rules.sh --path PATH
  --path  a rules.mk file, or a folder searched for rules.mk files (for example ~/etc/check_mk/conf.d/wato)
Exit: 0 no suspicious role, 1 suspicious role found, 4 usage or read error
U
  exit 4
}
die() { echo "ERROR: $1" >&2; exit 4; }

scan_file() { # file: prints result lines
  local f=$1 flat lists l roles r nl=0 bad=0
  [ -r "$f" ] || die "cannot read: $f"
  flat=$(tr '\n\r\t' '   ' < "$f") || die "cannot read: $f"
  lists=$(printf '%s' "$flat" | grep -oE "cluster[-_]aggregation[-_]exclude[-_]node[-_]roles['\"][[:space:]]*,[[:space:]]*\[[^]]*\]")
  while IFS= read -r l; do
    [ -n "$l" ] || continue
    nl=$((nl+1))
    roles=$(printf '%s' "${l#*[}" | grep -oE "'[^']*'|\"[^\"]*\"")
    while IFS= read -r r; do
      [ -n "$r" ] || continue
      r=${r:1:${#r}-2}
      case $r in
        -*) bad=$((bad+1)); printf 'SUSPICIOUS: %s: Kubernetes rule node role "%s" starts with a dash. Werk 17994 describes roles read as agent options, such as --profile. Review who created this rule.\n' "$f" "$r";;
      esac
    done <<< "$roles"
  done <<< "$lists"
  if [ "$bad" -eq 0 ]; then
    if [ "$nl" -gt 0 ]; then printf 'OK: %s: %d Kubernetes node role list(s), no role starts with a dash.\n' "$f" "$nl"
    else printf 'OK: %s: no Kubernetes node role list.\n' "$f"; fi
  fi
}

path=""
while [ $# -gt 0 ]; do
  [ $# -ge 2 ] || usage
  case $1 in
    --path) path=$2;;
    *) usage;;
  esac
  shift 2
done
[ -n "$path" ] || usage

if [ -f "$path" ]; then files=$path
elif [ -d "$path" ]; then
  files=$(find "$path" -type f -name rules.mk -print | LC_ALL=C sort) || die "cannot search: $path"
else die "no such file or folder: $path"; fi
[ -n "$files" ] || die "no rules.mk found under: $path"

out=""
while IFS= read -r f; do
  o=$(scan_file "$f") || exit 4
  out+="$o"$'\n'
done <<< "$files"
out=${out%$'\n'}
NS=$(printf '%s\n' "$out" | grep -c '^SUSPICIOUS: ')
NF=$(printf '%s\n' "$files" | grep -c .)
echo "check-checkmk-kube-rules: offline rules.mk check for CVE-2026-121374 (werk 17994). Roles starting with a dash are OUR PATTERN, not a Checkmk indicator."
printf '%s\n' "$out"
if [ "$NS" -gt 0 ]; then echo "RESULT: SUSPICIOUS ($NF file(s) read, $NS suspicious role(s))"; exit 1; fi
echo "RESULT: CLEAN ($NF file(s) read, 0 suspicious roles)"
exit 0
