diff --git a/src/libs/002_core_globals.sh b/src/libs/002_core_globals.sh index 7aa5d8a..37662df 100644 --- a/src/libs/002_core_globals.sh +++ b/src/libs/002_core_globals.sh @@ -138,7 +138,12 @@ opt_intel_db=1 g_critical=0 g_unknown=0 -g_nrpe_vuln='' +g_nrpe_total=0 +g_nrpe_vuln_count=0 +g_nrpe_unk_count=0 +g_nrpe_vuln_ids='' +g_nrpe_vuln_details='' +g_nrpe_unk_details='' g_smc_vuln_output='' g_smc_ok_count=0 g_smc_vuln_count=0 diff --git a/src/libs/250_output_emitters.sh b/src/libs/250_output_emitters.sh index 02c3507..23e11a6 100644 --- a/src/libs/250_output_emitters.sh +++ b/src/libs/250_output_emitters.sh @@ -320,12 +320,23 @@ _emit_json_full() { g_json_vulns="${g_json_vulns}{\"cve\":\"$1\",\"name\":\"$esc_name\",\"aliases\":$(_json_str "$aliases"),\"cpu_affected\":$cpu_affected,\"status\":\"$3\",\"vulnerable\":$is_vuln,\"info\":\"$esc_infos\",\"sysfs_status\":$(_json_str "$sysfs_status"),\"sysfs_message\":$(_json_str "$sysfs_msg")}," } -# Append vulnerable CVE IDs to the NRPE output buffer +# Accumulate a CVE result into the NRPE output buffers # Args: $1=cve $2=aka $3=status $4=description -# Sets: g_nrpe_vuln +# Sets: g_nrpe_total, g_nrpe_vuln_count, g_nrpe_unk_count, g_nrpe_vuln_ids, g_nrpe_vuln_details, g_nrpe_unk_details # Callers: pvulnstatus _emit_nrpe() { - [ "$3" = VULN ] && g_nrpe_vuln="$g_nrpe_vuln $1" + g_nrpe_total=$((g_nrpe_total + 1)) + case "$3" in + VULN) + g_nrpe_vuln_count=$((g_nrpe_vuln_count + 1)) + g_nrpe_vuln_ids="${g_nrpe_vuln_ids:+$g_nrpe_vuln_ids }$1" + g_nrpe_vuln_details="${g_nrpe_vuln_details:+$g_nrpe_vuln_details\n}[CRITICAL] $1 ($2): $4" + ;; + UNK) + g_nrpe_unk_count=$((g_nrpe_unk_count + 1)) + g_nrpe_unk_details="${g_nrpe_unk_details:+$g_nrpe_unk_details\n}[UNKNOWN] $1 ($2): $4" + ;; + esac } # Append a CVE result as a legacy Prometheus metric to the batch output buffer diff --git a/src/main.sh b/src/main.sh index ca3dd09..211ffbf 100644 --- a/src/main.sh +++ b/src/main.sh @@ -92,11 +92,49 @@ if [ "$g_mocked" = 1 ]; then fi if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "nrpe" ]; then - if [ -n "$g_nrpe_vuln" ]; then - echo "Vulnerable:$g_nrpe_vuln" + _nrpe_is_root=0 + [ "$(id -u)" -eq 0 ] && _nrpe_is_root=1 + + # Non-root + VULN: demote to UNKNOWN, MSR reads were skipped so VULN findings + # may be false positives or genuine mitigations may have gone undetected + _nrpe_demoted=0 + [ "$g_nrpe_vuln_count" -gt 0 ] && [ "$_nrpe_is_root" = 0 ] && _nrpe_demoted=1 + + # Determine status word and build the one-line summary + if [ "$_nrpe_demoted" = 1 ]; then + _nrpe_status_word='UNKNOWN' + _nrpe_summary="${g_nrpe_vuln_count}/${g_nrpe_total} CVE(s) appear vulnerable (unconfirmed, not root): ${g_nrpe_vuln_ids}" + [ "$g_nrpe_unk_count" -gt 0 ] && _nrpe_summary="${_nrpe_summary}, ${g_nrpe_unk_count} inconclusive" + elif [ "$g_nrpe_vuln_count" -gt 0 ]; then + _nrpe_status_word='CRITICAL' + _nrpe_summary="${g_nrpe_vuln_count}/${g_nrpe_total} CVE(s) vulnerable: ${g_nrpe_vuln_ids}" + [ "$g_nrpe_unk_count" -gt 0 ] && _nrpe_summary="${_nrpe_summary}, ${g_nrpe_unk_count} inconclusive" + elif [ "$g_nrpe_unk_count" -gt 0 ]; then + _nrpe_status_word='UNKNOWN' + _nrpe_summary="${g_nrpe_unk_count}/${g_nrpe_total} CVE checks inconclusive" else - echo "OK" + _nrpe_status_word='OK' + _nrpe_summary="All ${g_nrpe_total} CVE checks passed" fi + + # Line 1: status word + summary + performance data (Nagios plugin spec) + echo "${_nrpe_status_word}: ${_nrpe_summary} | checked=${g_nrpe_total} vulnerable=${g_nrpe_vuln_count} unknown=${g_nrpe_unk_count}" + + # Long output (lines 2+): context notes, then per-CVE details + [ "$opt_paranoid" = 1 ] && echo "NOTE: paranoid mode active, stricter mitigation requirements applied" + case "${g_has_vmm:-}" in + 1) echo "NOTE: hypervisor host detected (${g_has_vmm_reason:-VMM}); L1TF/MDS severity is elevated" ;; + 0) echo "NOTE: not a hypervisor host" ;; + esac + [ "$_nrpe_is_root" = 0 ] && echo "NOTE: not running as root; MSR reads skipped, results may be incomplete" + + # VULN details first, then UNK details (each group in CVE-registry order) + [ -n "${g_nrpe_vuln_details:-}" ] && printf "%b\n" "$g_nrpe_vuln_details" + [ -n "${g_nrpe_unk_details:-}" ] && printf "%b\n" "$g_nrpe_unk_details" + + # Exit with the correct Nagios code when we demoted VULN→UNKNOWN due to non-root + # (g_critical=1 would otherwise cause exit 2 below) + [ "$_nrpe_demoted" = 1 ] && exit 3 fi if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "short" ]; then