release 1.8.0: simplified mon pages + surface nmap + mail hosts

Public layout: only taler-monitoring-surface(+_err) for ecosystem
software/versions; nine landing stacks keep /monitoring(+_err).
Fold mail (firefly.gnunet.org, anastasis.taler-systems.com) and
mattermost into the surface job; deprecate separate mon pages/timers.
nmap OS fingerprinting; package-behind defaults to ERROR.
This commit is contained in:
Hernâni Marques 2026-07-19 00:29:49 +02:00
parent 2b846dab7a
commit e7a3cd558f
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
15 changed files with 263 additions and 154 deletions

View file

@ -32,6 +32,10 @@ PORT_TIMEOUT="${SURFACE_PORT_TIMEOUT:-2}"
HTTP_TIMEOUT="${SURFACE_HTTP_TIMEOUT:-12}"
# CVE via OSV (public, no key). Disable: SURFACE_CVE=0
: "${SURFACE_CVE:=1}"
# nmap OS / service fingerprint (v1.8.0+). Disable: SURFACE_NMAP=0
: "${SURFACE_NMAP:=1}"
# OS detection (-O) often needs root; falls back to -sV when denied
: "${SURFACE_NMAP_OS:=1}"
# Extra ports when -d domain mode
DOMAIN_EXTRA_PORTS="${SURFACE_DOMAIN_PORTS:-22,80,443,8443}"
@ -496,6 +500,109 @@ cert_expiry_check() {
return 0
}
# nmap OS / service fingerprint (v1.8.0+).
# ERROR when fingerprint indicates an OS that is EOL / should be upgraded.
# Soft/info when nmap missing or unprivileged OS scan fails.
nmap_fingerprint_host() {
local h="$1"
local xml out osline accuracy eol=0
[ "${SURFACE_NMAP:-1}" = "1" ] || return 0
if ! command -v nmap >/dev/null 2>&1; then
warn "nmap" "$h · nmap not installed (apt install nmap) — skip OS fingerprint"
return 0
fi
xml=$(mktemp)
out=$(mktemp)
# Prefer OS detection when allowed; always try service version light
if [ "${SURFACE_NMAP_OS:-1}" = "1" ] && {
nmap -Pn -n -T4 -O --osscan-guess --max-os-tries 1 --host-timeout 45s \
-p 22,80,443,25,465,587,993 "$h" -oX "$xml" >"$out" 2>&1
}; then
:
else
nmap -Pn -n -T4 -sV --version-light --host-timeout 35s \
-p 22,80,443,25,465,587,993 "$h" -oX "$xml" >"$out" 2>&1 || true
fi
# Parse best OS match from XML
osline=$(
python3 - "$xml" <<'PY' 2>/dev/null || true
import sys, xml.etree.ElementTree as ET
path = sys.argv[1]
try:
root = ET.parse(path).getroot()
except Exception:
sys.exit(0)
best = None
best_acc = -1
for osm in root.findall(".//osmatch"):
name = osm.get("name") or ""
try:
acc = int(osm.get("accuracy") or "0")
except ValueError:
acc = 0
if name and acc >= best_acc:
best_acc = acc
best = name
if best:
print(f"{best_acc}|{best}")
# also surface product/version from service table
svcs = []
for port in root.findall(".//port"):
state = (port.find("state").get("state") if port.find("state") is not None else "")
if state != "open":
continue
svc = port.find("service")
if svc is None:
continue
prod = svc.get("product") or svc.get("name") or ""
ver = svc.get("version") or ""
if prod:
svcs.append(f"{prod} {ver}".strip())
if svcs:
print("SVC|" + "; ".join(svcs[:8]))
PY
)
rm -f "$xml" "$out"
if [ -z "$osline" ]; then
info "nmap" "$h · no OS/service fingerprint (need root for -O, or host filtered)"
return 0
fi
local osname="" svcinfo=""
while IFS= read -r line; do
case "$line" in
SVC\|*) svcinfo=${line#SVC|} ;;
*\|*)
accuracy=${line%%|*}
osname=${line#*|}
ok "nmap-os" "$h · OS guess ${accuracy}% · $osname"
;;
esac
done <<<"$osline"
[ -n "$svcinfo" ] && info "nmap-svc" "$h · $svcinfo"
# EOL / upgrade-required OS fingerprints → ERROR
case "$(printf '%s' "$osname" | tr 'A-Z' 'a-z')" in
*debian*6*|*debian*7*|*debian*8*|*debian*9*|*debian*10*|*debian*\"buster\"*|*debian*buster*)
eol=1 ;;
*ubuntu*12.04*|*ubuntu*14.04*|*ubuntu*16.04*|*ubuntu*18.04*|*ubuntu*20.04*)
eol=1 ;;
*centos*[5-7]*|*red\ hat\ enterprise*5*|*red\ hat\ enterprise*6*|*red\ hat\ enterprise*7*)
eol=1 ;;
*windows\ server\ 2008*|*windows\ server\ 2012*|*windows\ xp*|*windows\ 7*)
eol=1 ;;
*freebsd\ 1[0-2]*|*freebsd\ [0-9].*)
eol=1 ;;
esac
if [ "$eol" = "1" ]; then
err "nmap-upgrade" "$h OS fingerprint indicates EOL / upgrade required" "$osname"
return 1
fi
if [ -n "$osname" ]; then
info "nmap-upgrade" "$h OS fingerprint not on EOL list · $osname"
fi
return 0
}
scan_host() {
local h="$1"
local ports proto label expect
@ -621,6 +728,9 @@ scan_host() {
fi
fi
# nmap OS / service fingerprint (v1.8.0+) — ERROR on EOL OS
nmap_fingerprint_host "$h" || true
# Expected service must answer protocol (not just ping/port)
# Note: ${array[*]:-x} is NOT valid default syntax (bash treats : as slice).
local ports_txt="${open_ports[*]}"
@ -667,7 +777,7 @@ else
fi
info "inventory" "${#ORDERED_HOSTS[@]} hosts to probe (remote only · no SSH · no local podman on targets)"
info "flags" "SURFACE_CVE=${SURFACE_CVE:-1} PORT_TIMEOUT=${PORT_TIMEOUT:-2} HTTP_TIMEOUT=${HTTP_TIMEOUT:-12}"
info "flags" "SURFACE_CVE=${SURFACE_CVE:-1} SURFACE_NMAP=${SURFACE_NMAP:-1} SURFACE_NMAP_OS=${SURFACE_NMAP_OS:-1} PORT_TIMEOUT=${PORT_TIMEOUT:-2} HTTP_TIMEOUT=${HTTP_TIMEOUT:-12}"
for h in "${ORDERED_HOSTS[@]}"; do
[ -n "$h" ] || continue