mirror of
https://github.com/speed47/spectre-meltdown-checker.git
synced 2026-04-09 10:13:18 +02:00
feat: rework the --batch prometheus output entirely
This commit is contained in:
@@ -46,7 +46,8 @@ show_usage() {
|
||||
--batch json produce comprehensive JSON output with system, CPU, and vulnerability details
|
||||
--batch json-terse produce a terse JSON array of per-CVE results (legacy format)
|
||||
--batch nrpe produce machine readable output formatted for NRPE
|
||||
--batch prometheus produce output for consumption by prometheus-node-exporter
|
||||
--batch prometheus produce Prometheus metrics (smc_* schema, recommended)
|
||||
--batch prometheus-legacy produce legacy Prometheus output (specex_vuln_status, deprecated)
|
||||
|
||||
--variant VARIANT specify which variant you'd like to check, by default all variants are checked.
|
||||
can be used multiple times (e.g. --variant 3a --variant l1tf)
|
||||
@@ -138,6 +139,12 @@ opt_intel_db=1
|
||||
g_critical=0
|
||||
g_unknown=0
|
||||
g_nrpe_vuln=''
|
||||
g_smc_vuln_output=''
|
||||
g_smc_ok_count=0
|
||||
g_smc_vuln_count=0
|
||||
g_smc_unk_count=0
|
||||
g_smc_system_info_line=''
|
||||
g_smc_cpu_info_line=''
|
||||
|
||||
# CVE Registry: single source of truth for all CVE metadata.
|
||||
# Fields: cve_id|json_key_name|affected_var_suffix|complete_name_and_aliases
|
||||
|
||||
@@ -116,7 +116,7 @@ while [ -n "${1:-}" ]; do
|
||||
opt_no_color=1
|
||||
shift
|
||||
case "$1" in
|
||||
text | short | nrpe | json | json-terse | prometheus)
|
||||
text | short | nrpe | json | json-terse | prometheus | prometheus-legacy)
|
||||
opt_batch_format="$1"
|
||||
shift
|
||||
;;
|
||||
@@ -124,7 +124,7 @@ while [ -n "${1:-}" ]; do
|
||||
'') ;; # allow nothing at all
|
||||
*)
|
||||
echo "$0: error: unknown batch format '$1'" >&2
|
||||
echo "$0: error: --batch expects a format from: text, nrpe, json, json-terse" >&2
|
||||
echo "$0: error: --batch expects a format from: text, short, nrpe, json, json-terse, prometheus, prometheus-legacy" >&2
|
||||
exit 255
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -8,6 +8,13 @@ _json_escape() {
|
||||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/ /\\t/g' | tr '\n' ' '
|
||||
}
|
||||
|
||||
# Escape a string for use as a Prometheus label value (handles backslashes, double quotes, newlines)
|
||||
# Args: $1=string
|
||||
# Prints: escaped string (without surrounding quotes)
|
||||
_prom_escape() {
|
||||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n' ' '
|
||||
}
|
||||
|
||||
# Convert a shell capability value to a JSON token
|
||||
# Args: $1=value (1=true, 0=false, -1/empty=null, other string=quoted string)
|
||||
# Prints: JSON token
|
||||
@@ -321,17 +328,124 @@ _emit_nrpe() {
|
||||
[ "$3" = VULN ] && g_nrpe_vuln="$g_nrpe_vuln $1"
|
||||
}
|
||||
|
||||
# Append a CVE result as a Prometheus metric to the batch output buffer
|
||||
# Append a CVE result as a legacy Prometheus metric to the batch output buffer
|
||||
# Args: $1=cve $2=aka $3=status $4=description
|
||||
# Sets: g_prometheus_output
|
||||
# Callers: pvulnstatus
|
||||
_emit_prometheus() {
|
||||
_emit_prometheus_legacy() {
|
||||
local esc_info
|
||||
# escape backslashes and double quotes for Prometheus label values
|
||||
esc_info=$(printf '%s' "$4" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
|
||||
g_prometheus_output="${g_prometheus_output:+$g_prometheus_output\n}specex_vuln_status{name=\"$2\",cve=\"$1\",status=\"$3\",info=\"$esc_info\"} 1"
|
||||
}
|
||||
|
||||
# Append a CVE result as a Prometheus gauge to the new-format batch output buffer
|
||||
# Status is encoded numerically: 0=not_vulnerable, 1=vulnerable, 2=unknown
|
||||
# Args: $1=cve $2=aka $3=status(UNK|VULN|OK) $4=description
|
||||
# Sets: g_smc_vuln_output, g_smc_ok_count, g_smc_vuln_count, g_smc_unk_count
|
||||
# Callers: pvulnstatus
|
||||
_emit_prometheus() {
|
||||
local numeric_status cpu_affected full_name esc_name
|
||||
case "$3" in
|
||||
OK) numeric_status=0 ; g_smc_ok_count=$((g_smc_ok_count + 1)) ;;
|
||||
VULN) numeric_status=1 ; g_smc_vuln_count=$((g_smc_vuln_count + 1)) ;;
|
||||
UNK) numeric_status=2 ; g_smc_unk_count=$((g_smc_unk_count + 1)) ;;
|
||||
*)
|
||||
echo "$0: error: unknown status '$3' passed to _emit_prometheus()" >&2
|
||||
exit 255
|
||||
;;
|
||||
esac
|
||||
if is_cpu_affected "$1" 2>/dev/null; then
|
||||
cpu_affected='true'
|
||||
else
|
||||
cpu_affected='false'
|
||||
fi
|
||||
# use the complete CVE name (field 4) rather than the short aka key (field 2)
|
||||
full_name=$(_cve_registry_field "$1" 4)
|
||||
esc_name=$(_prom_escape "$full_name")
|
||||
g_smc_vuln_output="${g_smc_vuln_output:+$g_smc_vuln_output\n}smc_vulnerability_status{cve=\"$1\",name=\"$esc_name\",cpu_affected=\"$cpu_affected\"} $numeric_status"
|
||||
}
|
||||
|
||||
# Build the smc_system_info Prometheus metric line
|
||||
# Sets: g_smc_system_info_line
|
||||
# Callers: src/main.sh (after check_cpu / check_cpu_vulnerabilities)
|
||||
# shellcheck disable=SC2034
|
||||
_build_prometheus_system_info() {
|
||||
local kernel_release kernel_arch hypervisor_host sys_labels
|
||||
if [ "$opt_live" = 1 ]; then
|
||||
kernel_release=$(uname -r 2>/dev/null || true)
|
||||
kernel_arch=$(uname -m 2>/dev/null || true)
|
||||
else
|
||||
kernel_release=''
|
||||
kernel_arch=''
|
||||
fi
|
||||
case "${g_has_vmm:-}" in
|
||||
1) hypervisor_host='true' ;;
|
||||
0) hypervisor_host='false' ;;
|
||||
*) hypervisor_host='' ;;
|
||||
esac
|
||||
sys_labels=''
|
||||
[ -n "$kernel_release" ] && sys_labels="${sys_labels:+$sys_labels,}kernel_release=\"$(_prom_escape "$kernel_release")\""
|
||||
[ -n "$kernel_arch" ] && sys_labels="${sys_labels:+$sys_labels,}kernel_arch=\"$(_prom_escape "$kernel_arch")\""
|
||||
[ -n "$hypervisor_host" ] && sys_labels="${sys_labels:+$sys_labels,}hypervisor_host=\"$hypervisor_host\""
|
||||
[ -n "$sys_labels" ] && g_smc_system_info_line="smc_system_info{$sys_labels} 1"
|
||||
}
|
||||
|
||||
# Build the smc_cpu_info Prometheus metric line
|
||||
# Sets: g_smc_cpu_info_line
|
||||
# Callers: src/main.sh (after check_cpu / check_cpu_vulnerabilities)
|
||||
# shellcheck disable=SC2034
|
||||
_build_prometheus_cpu_info() {
|
||||
local cpuid_hex ucode_hex ucode_latest_hex ucode_uptodate ucode_blacklisted codename smt_val cpu_labels
|
||||
if [ -n "${cpu_cpuid:-}" ]; then
|
||||
cpuid_hex=$(printf '0x%08x' "$cpu_cpuid")
|
||||
else
|
||||
cpuid_hex=''
|
||||
fi
|
||||
if [ -n "${cpu_ucode:-}" ]; then
|
||||
ucode_hex=$(printf '0x%x' "$cpu_ucode")
|
||||
else
|
||||
ucode_hex=''
|
||||
fi
|
||||
is_latest_known_ucode
|
||||
case $? in
|
||||
0) ucode_uptodate='true' ;;
|
||||
1) ucode_uptodate='false' ;;
|
||||
*) ucode_uptodate='' ;;
|
||||
esac
|
||||
ucode_latest_hex="${ret_is_latest_known_ucode_version:-}"
|
||||
if is_ucode_blacklisted; then
|
||||
ucode_blacklisted='true'
|
||||
else
|
||||
ucode_blacklisted='false'
|
||||
fi
|
||||
codename=''
|
||||
if is_intel; then
|
||||
codename=$(get_intel_codename 2>/dev/null || true)
|
||||
fi
|
||||
is_cpu_smt_enabled
|
||||
case $? in
|
||||
0) smt_val='true' ;;
|
||||
1) smt_val='false' ;;
|
||||
*) smt_val='' ;;
|
||||
esac
|
||||
cpu_labels=''
|
||||
[ -n "${cpu_vendor:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}vendor=\"$(_prom_escape "$cpu_vendor")\""
|
||||
[ -n "${cpu_friendly_name:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}model=\"$(_prom_escape "$cpu_friendly_name")\""
|
||||
[ -n "${cpu_family:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}family=\"$cpu_family\""
|
||||
[ -n "${cpu_model:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}model_id=\"$cpu_model\""
|
||||
[ -n "${cpu_stepping:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}stepping=\"$cpu_stepping\""
|
||||
[ -n "$cpuid_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}cpuid=\"$cpuid_hex\""
|
||||
[ -n "$codename" ] && cpu_labels="${cpu_labels:+$cpu_labels,}codename=\"$(_prom_escape "$codename")\""
|
||||
[ -n "$smt_val" ] && cpu_labels="${cpu_labels:+$cpu_labels,}smt=\"$smt_val\""
|
||||
[ -n "$ucode_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode=\"$ucode_hex\""
|
||||
[ -n "$ucode_latest_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_latest=\"$ucode_latest_hex\""
|
||||
[ -n "$ucode_uptodate" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_up_to_date=\"$ucode_uptodate\""
|
||||
# always emit microcode_blacklisted when we have microcode info (it's a boolean, never omit)
|
||||
[ -n "$ucode_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_blacklisted=\"$ucode_blacklisted\""
|
||||
[ -n "$cpu_labels" ] && g_smc_cpu_info_line="smc_cpu_info{$cpu_labels} 1"
|
||||
}
|
||||
|
||||
# Update global state used to determine the program exit code
|
||||
# Args: $1=cve $2=status(UNK|VULN|OK)
|
||||
# Sets: g_unknown, g_critical
|
||||
@@ -364,6 +478,7 @@ pvulnstatus() {
|
||||
json-terse) _emit_json_terse "$1" "$aka" "$2" "$3" ;;
|
||||
nrpe) _emit_nrpe "$1" "$aka" "$2" "$3" ;;
|
||||
prometheus) _emit_prometheus "$1" "$aka" "$2" "$3" ;;
|
||||
prometheus-legacy) _emit_prometheus_legacy "$1" "$aka" "$2" "$3" ;;
|
||||
*)
|
||||
echo "$0: error: invalid batch format '$opt_batch_format' specified" >&2
|
||||
exit 255
|
||||
|
||||
57
src/main.sh
57
src/main.sh
@@ -25,6 +25,14 @@ if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "json" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build Prometheus info metric lines (same timing requirement as JSON builders above)
|
||||
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus" ]; then
|
||||
_build_prometheus_system_info
|
||||
if [ "$opt_no_hw" = 0 ] && [ -z "$opt_arch_prefix" ]; then
|
||||
_build_prometheus_cpu_info
|
||||
fi
|
||||
fi
|
||||
|
||||
# now run the checks the user asked for
|
||||
for cve in $g_supported_cve_list; do
|
||||
if [ "$opt_cve_all" = 1 ] || echo "$opt_cve_list" | grep -qw "$cve"; then
|
||||
@@ -117,12 +125,59 @@ if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "json" ]; then
|
||||
_pr_echo 0 "$_json_final"
|
||||
fi
|
||||
|
||||
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus" ]; then
|
||||
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus-legacy" ]; then
|
||||
echo "# TYPE specex_vuln_status untyped"
|
||||
echo "# HELP specex_vuln_status Exposure of system to speculative execution vulnerabilities"
|
||||
printf "%b\n" "$g_prometheus_output"
|
||||
fi
|
||||
|
||||
if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus" ]; then
|
||||
prom_run_as_root='false'
|
||||
[ "$(id -u)" -eq 0 ] && prom_run_as_root='true'
|
||||
prom_mode='offline'
|
||||
[ "$opt_live" = 1 ] && prom_mode='live'
|
||||
prom_paranoid='false'
|
||||
[ "$opt_paranoid" = 1 ] && prom_paranoid='true'
|
||||
prom_sysfs_only='false'
|
||||
[ "$opt_sysfs_only" = 1 ] && prom_sysfs_only='true'
|
||||
prom_reduced_accuracy='false'
|
||||
[ "${g_bad_accuracy:-0}" = 1 ] && prom_reduced_accuracy='true'
|
||||
prom_mocked='false'
|
||||
[ "${g_mocked:-0}" = 1 ] && prom_mocked='true'
|
||||
echo "# HELP smc_build_info spectre-meltdown-checker script metadata (always 1)"
|
||||
echo "# TYPE smc_build_info gauge"
|
||||
printf 'smc_build_info{version="%s",mode="%s",run_as_root="%s",paranoid="%s",sysfs_only="%s",reduced_accuracy="%s",mocked="%s"} 1\n' \
|
||||
"$(_prom_escape "$VERSION")" \
|
||||
"$prom_mode" \
|
||||
"$prom_run_as_root" \
|
||||
"$prom_paranoid" \
|
||||
"$prom_sysfs_only" \
|
||||
"$prom_reduced_accuracy" \
|
||||
"$prom_mocked"
|
||||
if [ -n "${g_smc_system_info_line:-}" ]; then
|
||||
echo "# HELP smc_system_info Operating system and kernel metadata (always 1)"
|
||||
echo "# TYPE smc_system_info gauge"
|
||||
echo "$g_smc_system_info_line"
|
||||
fi
|
||||
if [ -n "${g_smc_cpu_info_line:-}" ]; then
|
||||
echo "# HELP smc_cpu_info CPU hardware and microcode metadata (always 1)"
|
||||
echo "# TYPE smc_cpu_info gauge"
|
||||
echo "$g_smc_cpu_info_line"
|
||||
fi
|
||||
echo "# HELP smc_vulnerability_status Vulnerability check result per CVE: 0=not_vulnerable, 1=vulnerable, 2=unknown"
|
||||
echo "# TYPE smc_vulnerability_status gauge"
|
||||
printf "%b\n" "$g_smc_vuln_output"
|
||||
echo "# HELP smc_vulnerable_count Number of CVEs with vulnerable status"
|
||||
echo "# TYPE smc_vulnerable_count gauge"
|
||||
echo "smc_vulnerable_count $g_smc_vuln_count"
|
||||
echo "# HELP smc_unknown_count Number of CVEs with unknown status"
|
||||
echo "# TYPE smc_unknown_count gauge"
|
||||
echo "smc_unknown_count $g_smc_unk_count"
|
||||
echo "# HELP smc_last_scan_timestamp_seconds Unix timestamp when this scan completed"
|
||||
echo "# TYPE smc_last_scan_timestamp_seconds gauge"
|
||||
echo "smc_last_scan_timestamp_seconds $(date +%s 2>/dev/null || echo 0)"
|
||||
fi
|
||||
|
||||
# exit with the proper exit code
|
||||
[ "$g_critical" = 1 ] && exit 2 # critical
|
||||
[ "$g_unknown" = 1 ] && exit 3 # unknown
|
||||
|
||||
Reference in New Issue
Block a user