mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-16 15:45:11 +00:00
64d42663cd
The Linux/macOS agent installer could abort during "pre-installation cleanup"
(terminal showed `Killing processes matching: haproxy-agent` then `Killed`,
returning to the prompt) when the install script's own command line contained
"haproxy-agent". The cleanup killed processes via `pgrep -f "$pattern"` starting
with the bare string "haproxy-agent", which also matched the running installer's
own command line and a sudo/PAM ancestor that the $$/$PPID self-exclusion did not
cover, so the installer terminated itself before installing.
- linux_install.sh / macos_install.sh: the cleanup kill loop now targets ONLY
the installed agent - "$INSTALL_DIR/haproxy-agent" (the daemon binary path) and
the agent service/label ("haproxy-agent.service" / "com.haproxy.agent") - never
the bare "haproxy-agent" substring. Neither pattern can match the installer's
own command line. The redundant bare pattern is dropped (the service is stopped
separately, and the binary-path pattern still catches a running daemon).
- frontend (AgentManagement.js): name the downloaded scripts
install-agent-<platform>.sh / uninstall-agent-<platform>.sh (matching the
backend's suggested filename) - defense in depth so this cannot resurface.
Installer-only change. The running agent and its privilege model are unchanged
(it runs as root for HAProxy reload, config writes, keepalived, and self-upgrade).
The cleanup runs only on a full interactive install (gated by SKIP_TO_DAEMON), so
daemon mode, self-upgrade, and config/version apply are unaffected. Both agent
scripts kept in sync. Scripts parse on bash 4.2-5.2; full backend suite green.
Addresses #31.
62 lines
3.3 KiB
Python
62 lines
3.3 KiB
Python
"""Issue #31 — agent-script hardening guard (static).
|
|
|
|
The agent install scripts hand-build the heartbeat JSON, so if `collect_system_info` ever yields
|
|
nothing the `$system_info,` line collapses to a bare comma and the whole heartbeat is invalid JSON
|
|
(HTTP 400). The fix adds a guard at every fragment-form call site that substitutes a single valid
|
|
key when system_info is empty. This static check enforces that the guard is present AND kept in
|
|
sync across BOTH platform scripts — the project requires the two agent-script copies to stay in
|
|
lockstep. (Empty numeric subfields like "memory_total": , are a separate, milder case already
|
|
repaired by the backend sanitizer, so they are intentionally NOT guarded in the script — guarding
|
|
them with a strict integer test would wrongly reject the scientific-notation that mawk emits for
|
|
multi-GB sizes on Debian/Ubuntu.)
|
|
"""
|
|
import os
|
|
|
|
_SCRIPT_DIR = os.path.join(
|
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), # backend/
|
|
"utils", "agent_scripts",
|
|
)
|
|
|
|
|
|
def _read(name: str) -> str:
|
|
with open(os.path.join(_SCRIPT_DIR, name), "r") as f:
|
|
return f.read()
|
|
|
|
|
|
LINUX = _read("linux_install.sh")
|
|
MACOS = _read("macos_install.sh")
|
|
|
|
# The empty-system_info guard — present at BOTH fragment call sites (register_agent + send_heartbeat).
|
|
_B2_GUARD = '[[ "$system_info" != *\'"\'* ]] && system_info=\'"operating_system": "unknown"\''
|
|
|
|
|
|
def test_b2_guard_present_and_in_sync():
|
|
# Two fragment-form call sites per script (register_agent + send_heartbeat), identical wording.
|
|
assert LINUX.count(_B2_GUARD) == 2, "linux_install.sh missing/duplicated empty-system_info guard"
|
|
assert MACOS.count(_B2_GUARD) == 2, "macos_install.sh missing/duplicated empty-system_info guard"
|
|
|
|
|
|
def test_b2_guard_precedes_every_fragment_system_info_use():
|
|
# Every ' $system_info,' fragment line (the one that breaks on an empty value) must be in a
|
|
# function whose system_info was guarded. We assert the count of guards matches the count of
|
|
# fragment-form interpolations' call sites: each script has exactly one register + one
|
|
# send_heartbeat fragment builder feeding those lines, both guarded above.
|
|
for name, script in (("linux", LINUX), ("macos", MACOS)):
|
|
assert script.count(" $system_info,") >= 1, f"{name}: fragment heartbeat form unexpectedly gone"
|
|
assert script.count(_B2_GUARD) == 2, f"{name}: each fragment call site must carry the guard"
|
|
|
|
|
|
def test_cleanup_does_not_self_kill_via_bare_haproxy_agent_pattern():
|
|
# Issue #31 (v1.8.4): the pre-installation cleanup kills processes by pgrep -f "$pattern". A bare
|
|
# "haproxy-agent" pattern also matches the installer's OWN path (install-haproxy-agent-*.sh) and a
|
|
# sudo/PAM ancestor, so the installer killed itself. The kill loop must target ONLY the installed
|
|
# agent (binary path + service/label), never the bare string.
|
|
for name, script in (("linux", LINUX), ("macos", MACOS)):
|
|
assert 'for pattern in "haproxy-agent"' not in script, (
|
|
f"{name}: pre-install cleanup uses the bare 'haproxy-agent' kill pattern -> self-kill (issue #31)"
|
|
)
|
|
# The narrowed, installer-safe pattern must be present (binary path via $INSTALL_DIR).
|
|
assert 'for pattern in "$INSTALL_DIR/haproxy-agent"' in script, (
|
|
f"{name}: cleanup must match the installed binary path, not a bare substring"
|
|
)
|