"""Contract tests for the scripts an operator runs: `deploy/`, `scripts/dev-*.sh`/`.py`, and `scripts/capture-gateway-config.sh`. The capture script gets the same treatment as the installer because it can do the same kind of damage quietly: pointed at a gateway it cannot read, it is six empty files where six verbatim captures were, and every parser test that reads them then passes against nothing. So it is run here, against a stub `ssh` and a fake remote tree, and the assertions are about what lands on disk — including the one edit it makes (a globally routable socket address becomes documentation space) and the thing it never does (rewrite a config). There is no shell test framework here — and adding one would buy nothing a subprocess does not — but phase 10 rests on a sentence that lives in **two** files: the sudoers grant in `deploy/agent-sudoers`, and the hint `deploy/install-agent.sh` prints when it is run without root by the unprivileged `ops` account. Two copies of one instruction is exactly what drifts silently, and the failure is an install that cannot happen on a host nobody is sitting at. So these tests read the file, run the script the way the operator does (unprivileged, which exits before the script can touch anything), and require the two lines to be the same line apart from the path the script was invoked through. The same reasoning covers `scripts/dev-certs-export.py` and the `scripts/dev-server.sh` call that runs it (phase 24): the exporter is what turns the durable CA into a file an operator can `scp`, and the composition — *after* the readiness probe, *as* a warning line — is as much of the contract as the bytes it writes. So both files are run here and read here. """ from __future__ import annotations import difflib import importlib.util import json import os import re import shutil import stat import subprocess import sys import threading import time from collections.abc import Iterator from contextlib import contextmanager from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from typing import Any import httpx import pytest import uvicorn from auth_contract import ADMIN_PASSWORD, FAST_PBKDF2 from dashboards import auth, certs from dashboards.app import create_app from dashboards.config import Settings, Target from dashboards.store import SampleStore ROOT = Path(__file__).resolve().parent.parent DEPLOY = ROOT / "deploy" SCRIPTS = ROOT / "scripts" INSTALLER = DEPLOY / "install-agent.sh" SUDOERS = DEPLOY / "agent-sudoers" BASH = shutil.which("bash") or "/bin/bash" #: The grant as the file documents it. `/home/ops/agent-install` is the canonical staging #: path: a monitored host has no repo checkout, so `deploy/` and `agent/` are copied there #: (phase 10 task 02) and every host ends up with the same absolute path to grant. CANONICAL_GRANT = "ops ALL=(root) NOPASSWD: /home/ops/agent-install/deploy/install-agent.sh" #: The prefix a correct hint line starts with — user, run-as, and NOPASSWD, everything but #: the path, which is the one thing the installer is allowed to substitute. GRANT_PREFIX = "ops ALL=(root) NOPASSWD: " #: Everything a non-root operator needs to hear, besides the grant line itself. HINT_MUST_SAY = ( "visudo -cf", # check the syntax before installing it "install -m 0440 -o root -g root", # and install it the way sudoers files are installed "/etc/sudoers.d/dashboards-agent", # the destination both files name "deploy/agent-sudoers", # the pointer back to the documented file ) def _run(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess[str]: return subprocess.run(args, capture_output=True, text=True, cwd=cwd, check=False) def _grant_lines(text: str) -> list[str]: """The sudoers *rules* in a file — comments start with `#`, so this never matches prose.""" return [line for line in text.splitlines() if line.startswith(GRANT_PREFIX)] def _installer_without_root(*flags: str) -> subprocess.CompletedProcess[str]: """Run the installer the way `ops` does. Skipped as root on purpose: root would go past `need_root` and really install a systemd unit on the machine running pytest. """ if hasattr(os, "geteuid") and os.geteuid() == 0: pytest.skip("running as root — the installer would install a real unit here") return _run([BASH, str(INSTALLER), *flags]) # --- deploy/agent-sudoers --------------------------------------------------------------- def test_agent_sudoers_exists_and_is_a_config_file_not_a_script() -> None: assert SUDOERS.is_file(), "deploy/agent-sudoers is missing (phase 10 task 01)" assert not os.access(SUDOERS, os.X_OK), "a sudoers file is data; only install-agent.sh runs" def test_agent_sudoers_is_exactly_one_grant_line_at_the_canonical_path() -> None: """One rule, the installer, the staging path — nothing else may be granted.""" lines = _grant_lines(SUDOERS.read_text()) assert lines == [CANONICAL_GRANT], ( "deploy/agent-sudoers must grant exactly one command, the staged installer:\n" + "\n".join(lines) ) assert "/home/ops/agent-install" in SUDOERS.read_text() def test_agent_sudoers_grants_no_shell_editor_or_second_path() -> None: """The 'deliberately NOT granted' block has to stay a comment, not become a rule.""" text = SUDOERS.read_text() not_granted = text.split("deliberately NOT granted", 1)[-1] for never in ("visudo", "sh", "tee", "systemctl", "usermod", "upscmd", "upsrw"): assert never in not_granted, f"the file must name {never!r} as deliberately not granted" assert not_granted.lstrip().startswith("-"), "the block heading is followed by the list" # Every line of that block is a comment: a stray rule there would be a second grant. assert _grant_lines(not_granted) == [] # And it names the token file, because "we did not grant reading it" is the point. assert "/etc/dashboards-agent.token" in not_granted def test_agent_sudoers_documents_the_why_and_the_two_stage_install() -> None: """House style (deploy/nut-sudoers): why it is needed, then check-first install steps.""" text = SUDOERS.read_text() assert "WHY THIS IS NEEDED" in text for step in HINT_MUST_SAY: assert step in text, f"deploy/agent-sudoers must document {step!r}" # The installed copy is checked too — the file being valid is not the same as the # installed file being valid (nut-sudoers' third step, and the reason stays). assert text.count("visudo -cf") >= 2 @pytest.mark.skipif(shutil.which("visudo") is None, reason="visudo is not installed on this box") def test_agent_sudoers_passes_the_syntax_check_it_documents() -> None: """The file tells the operator to run `visudo -cf` on it, so it must survive that.""" result = _run([shutil.which("visudo") or "/usr/sbin/visudo", "-cf", str(SUDOERS)]) assert result.returncode == 0, f"visudo -cf failed:\n{result.stdout}{result.stderr}" # --- deploy/install-agent.sh without root ----------------------------------------------- def test_installer_without_root_exits_1_and_prints_the_grant_line() -> None: result = _installer_without_root("--bind", "127.0.0.1") assert result.returncode == 1, "the non-root refusal keeps its exit code (1)" assert result.stdout == "", "the refusal is a failure: it goes to stderr, not stdout" lines = _grant_lines(result.stderr) assert lines == [f"{GRANT_PREFIX}{Path(os.path.realpath(INSTALLER))}"], ( "the hint must print one grant line naming this script's resolved path:\n" + result.stderr ) def test_hint_and_the_sudoers_file_are_the_same_line_apart_from_the_path() -> None: """The pinned version of the diff in data/logs/NOTES-sudoers.md.""" printed = _installer_without_root("--bind", "127.0.0.1").stderr hint = _grant_lines(printed)[0] canonical = _grant_lines(SUDOERS.read_text())[0] assert hint.startswith(GRANT_PREFIX) and canonical.startswith(GRANT_PREFIX) assert hint.removeprefix(GRANT_PREFIX).endswith("/deploy/install-agent.sh") assert hint.removeprefix(GRANT_PREFIX).removeprefix(str(DEPLOY.parent)) == ( canonical.removeprefix(GRANT_PREFIX).removeprefix("/home/ops/agent-install") ), "same user, same (root), same NOPASSWD, same script — only the checkout path differs" def test_hint_resolves_a_relative_invocation_to_an_absolute_path() -> None: """`bash deploy/install-agent.sh` from the repo root still prints a path sudo can match.""" result = _run([BASH, "deploy/install-agent.sh", "--bind", "10.0.0.5"], cwd=ROOT) assert result.returncode == 1 assert _grant_lines(result.stderr) == [f"{GRANT_PREFIX}{Path(os.path.realpath(INSTALLER))}"] def test_installer_names_the_two_stage_install_and_points_at_the_file() -> None: stderr = _installer_without_root("--bind", "127.0.0.1").stderr for phrase in HINT_MUST_SAY: assert phrase in stderr, f"the non-root hint must say {phrase!r}, not just 'run as root'" # The pointer is the file next to *this* script, wherever the checkout lives. assert str(Path(os.path.realpath(SUDOERS))) in stderr def test_installer_hint_repeats_the_command_that_was_asked_for() -> None: """Copy-pasteable: the hint ends with the same invocation, with the flags it was given.""" stderr = _installer_without_root("--uninstall").stderr assert f"sudo {Path(os.path.realpath(INSTALLER))} --uninstall" in stderr def test_installer_help_points_at_the_sudoers_grant() -> None: """The header comment *is* the help text, so the grant is discoverable from --help.""" result = _run([BASH, str(INSTALLER), "--help"]) assert result.returncode == 0 assert "deploy/agent-sudoers" in result.stdout # --- the tunnel machinery stays retired (phase 10) --------------------------------------- #: Names and ports that only ever existed to serve the ssh-tunnel dev transport, which phase #: 10 deleted. Every pattern is written as a character class (`[d]ev-…`) so that **this file #: cannot match its own grep**: the phase criterion demands the repo stay free of those #: literals, and a guard that reintroduces one to forbid it is the oldest trick there is. #: Scope excludes `.agents/` (phase files and run reports are history — rewriting a finished #: phase's record to satisfy a grep would falsify it) and `tests/fixtures/` (verbatim #: upstream captures: a sector counter or a wheel hash may contain those digits by accident, #: and rule 7 forbids editing a capture to please a grep). TUNNEL_PATTERNS = ("[d]ev-tunnels", "DEV_[T]UNNELS", "DEV_[F]ORWARDS", r"912[123]\b", r"[3]494\b") #: Where those names must not appear: every file that *tells someone what to run*. #: `tests/fixtures/` is excluded (verbatim captures, see above) and so is `.agents/` — #: phase files and run reports are history, and rewriting a finished phase's record to #: satisfy a grep would falsify it. TUNNEL_FREE_ROOTS = ("scripts", "deploy", "src", "agent", "web") def _tunnel_scope_files() -> list[Path]: files: list[Path] = [] for top in TUNNEL_FREE_ROOTS: for path in (ROOT / top).rglob("*"): if path.is_file() and path.suffix in (".py", ".sh", ".md", ".css", ".js", ".html"): files.append(path) files.append(ROOT / "README.md") files.append(ROOT / "AGENTS.md") files.append(ROOT / ".env.example") files.append(ROOT / ".env.dev.example") for path in (ROOT / "tests").glob("*.py"): files.append(path) return files def test_dev_tunnel_script_is_deleted() -> None: """The tunnel script is gone, not shelved: git history keeps it if it is ever needed.""" # Name built at runtime so this file never carries the literal the grep forbids. script = ROOT / "scripts" / ("dev-" + "tunnels.sh") assert not script.exists(), ( "the retired ssh-tunnel dev script is back — phase 10 deleted it deliberately" ) def test_no_operational_file_mentions_the_tunnel_machinery() -> None: """A stale tunnel-env instruction is a setup step nobody can complete any more.""" offenders: list[str] = [] for path in _tunnel_scope_files(): text = path.read_text(errors="replace") for pattern in TUNNEL_PATTERNS: if re.search(pattern, text): offenders.append(f"{path.relative_to(ROOT)}: /{pattern}/") assert not offenders, "tunnel-era names/ports are back:\n" + "\n".join(offenders) # --- the mTLS registration is the documented default (phase 24) --------------------------- #: The three flags that make a host's **install** its registration moment: the run that installs #: the agent also pulls that host's bundle from the dashboard that owns the authority. The #: token-mode install is still supported (a host nobody has bundled keeps its bearer token), but #: it is the compatibility path now, and a setup file that leads with it is a recipe for a fleet #: of `http://` agents — which is the exact state `TODO.md:3` asked to be rid of. REGISTRATION_FLAGS = ("--fetch-bundle", "--cert-name", "--session") #: The files that tell an operator what to type, per host. REGISTRATION_DOCS = ("README.md", "AGENTS.md", ".env.example", ".env.dev.example") def _host_target_lines(path: Path) -> list[str]: """Every `HOST_TARGETS` assignment line in a file, comments included. The commented example in `.env.example` counts, because it is the line a reader copies. """ return [line for line in path.read_text().splitlines() if re.search(r"^\s*#?\s*(export\s+)?HOST_TARGETS\s*=", line)] @pytest.mark.parametrize("name", REGISTRATION_DOCS) def test_the_setup_docs_teach_registration_with_a_bundle(name: str) -> None: """The per-host recipe a reader copies is the bundle fetch, not the bearer token.""" text = (ROOT / name).read_text() for flag in REGISTRATION_FLAGS: assert flag in text, f"{name} stopped naming {flag} — the fetch is no longer documented" @pytest.mark.parametrize("name", (".env.example", ".env.dev.example")) def test_the_template_target_urls_are_https_and_credential_free(name: str) -> None: """A `HOST_TARGETS` line an operator copies is `https://` with no userinfo. Pinning the *example* is what keeps this true: prose can and should still document the token-mode shape (it is a supported dial, `collectors.fetch_snapshot` keeps its path byte-for- byte), but the line that gets pasted into a real config must not seed a non-mTLS fleet. The empty `HOST_TARGETS=` is allowed — unconfigured is a different fact from misconfigured. """ lines = _host_target_lines(ROOT / name) assert lines, f"{name} lost its HOST_TARGETS documentation entirely" for line in lines: value = line.split("=", 1)[1] urls = re.findall(r"\w+://[^\s\"']+", value) if not urls: continue # the empty assignment bad = [u for u in urls if not u.startswith("https://") or "@" in u] assert not bad, f"{name} seeds a non-mTLS or userinfo-bearing target: {bad}" # --- shell syntax (the only gate these scripts get) ------------------------------------- #: Names a shell function must never take. Bash resolves a **function** before an external #: command of the same name, so defining `install()` in a script that then calls #: `install -d /somewhere` is not a directory creation — it is the function calling itself, #: forever. That is not hypothetical: `deploy/install-agent.sh` did exactly this, and the #: first real install of phase 10 spun on the SMART block for two minutes and installed #: nothing (no unit, no agent file, no token) while looking like a working script to `bash -n`. SHADOW_FORBIDDEN = frozenset( { "install", "test", "time", "date", "sort", "grep", "sed", "tee", "type", "ulimit", "wait", "kill", "cd", "pwd", } ) _FUNCTION_DEF_RE = r"^([A-Za-z_][A-Za-z0-9_]*)\s*\(\)" def _function_names(script: Path) -> list[str]: return re.findall(_FUNCTION_DEF_RE, script.read_text(), flags=re.MULTILINE) @pytest.mark.parametrize( "script", sorted([*DEPLOY.glob("*.sh"), *(ROOT / "scripts").glob("*.sh")]), ids=lambda p: str(p.relative_to(ROOT)), ) def test_shell_script_has_valid_syntax(script: Path) -> None: result = _run([BASH, "-n", str(script)]) assert result.returncode == 0, f"bash -n failed for {script}:\n{result.stderr}" @pytest.mark.parametrize( "script", sorted([*DEPLOY.glob("*.sh"), *(ROOT / "scripts").glob("*.sh")]), ids=lambda p: str(p.relative_to(ROOT)), ) def test_no_shell_function_is_named_after_a_coreutil_it_could_be_asked_to_run(script: Path) -> None: """`install()` + `install -d` is infinite recursion, and `bash -n` cannot see it.""" shadowed = SHADOW_FORBIDDEN.intersection(_function_names(script)) assert not shadowed, ( f"{script.relative_to(ROOT)} defines function(s) {sorted(shadowed)} that shadow an " "external command: bash runs the function, so any call meant for the program recurses" ) # --- the install path, run for real against a sandboxed root boundary -------------------- #: The commands of `install()` that would touch the machine running pytest. Each one is a #: PATH stub that records its arguments and succeeds — the same trick `ScriptedNutClient` is #: for the UPS wire: fake the privileged boundary, run the real logic. ROOT_BOUNDARY_STUBS = ("id", "getent", "useradd", "usermod", "chown", "systemctl") #: The three absolute destinations the installer writes to, rewritten into a temp directory #: so the test exercises the body instead of the operator's `/usr` and `/etc`. SANDBOX_REWRITES = { 'AGENT_DIR="/usr/local/lib/dashboards-agent"': 'AGENT_DIR="{root}/lib"', 'TOKEN_FILE="/etc/dashboards-agent.token"': 'TOKEN_FILE="{root}/token"', 'CERT_FILE="/etc/dashboards-agent.certs"': 'CERT_FILE="{root}/certs"', 'UNIT="/etc/systemd/system/$SERVICE.service"': 'UNIT="{root}/dashboards-agent.service"', } def _sandbox_installer(tmp_path: Path) -> tuple[Path, Path, dict[str, str]]: """A copy of the installer whose writes land in `tmp_path`, plus a stubbed PATH. Returns (script, stub call log, env). `agent/host_agent.py` is stubbed next to the copy because the script resolves the agent as `../agent/host_agent.py`. """ text = INSTALLER.read_text() target = tmp_path / "target" for old, new in SANDBOX_REWRITES.items(): assert old in text, f"the installer moved {old!r} — update SANDBOX_REWRITES with it" text = text.replace(old, new.format(root=target)) script = tmp_path / "deploy" / "install-agent.sh" script.parent.mkdir(parents=True) script.write_text(text) (tmp_path / "agent").mkdir() (tmp_path / "agent" / "host_agent.py").write_text("# stub agent source\n", encoding="utf-8") stubs = tmp_path / "stubs" stubs.mkdir() call_log = tmp_path / "stub-calls.log" for name in ROOT_BOUNDARY_STUBS: stub = stubs / name # `id -u` must answer 0 or need_root() refuses before the body runs; every other call # just records itself, which is what the assertions below read. body = ['#!/bin/sh', f'printf "%s %s\\n" "{name}" "$*" >> "$STUB_LOG"'] if name == "id": body.append('[ "$1" = "-u" ] && echo 0') body.append("exit 0") stub.write_text("\n".join(body) + "\n", encoding="utf-8") stub.chmod(0o755) env = {**os.environ, "PATH": f"{stubs}{os.pathsep}{os.environ['PATH']}", "STUB_LOG": str(call_log)} return script, call_log, env @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_install_path_installs_into_its_own_paths_and_prints_the_token_once(tmp_path: Path) -> None: """The install body end to end: files, unit, token, and no recursion. The exit code is the regression test for the `install()` shadow: while the function was named after `/usr/bin/install`, this never returned at all. """ script, call_log, env = _sandbox_installer(tmp_path) try: result = subprocess.run( [BASH, str(script), "--bind", "10.0.0.5", "--port", "9120"], capture_output=True, text=True, env=env, timeout=60, check=False, ) except subprocess.TimeoutExpired as exc: pytest.fail(f"the installer did not return (recursion? partial output: {exc.stdout!r})") assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" target = tmp_path / "target" assert (target / "lib" / "host_agent.py").is_file(), "install -m never landed the agent" unit = (target / "dashboards-agent.service").read_text() token_path = target / "token" token = token_path.read_text().strip() assert "User=dashboards-agent" in unit assert f"--token-file {token_path}" in unit, "the unit must use the file, never --token" assert "--host 10.0.0.5 --port 9120" in unit # A token-only install runs no TLS, so it gets neither the bundle flag nor the directory # the bundle unpacks into (the agent falls back to a private mkdtemp without one). assert "--cert-bundle" not in unit, "no bundle installed means no TLS flag on a token install" assert "RuntimeDirectory=dashboards-agent" not in unit # No device filter, on purpose (phase 17): a strict device policy denies every node the # allow-list fails to match, and systemd matches those globs by *name* against # /proc/devices — where kernel 7.2 lists NVMe as `blkext`, so the old `/dev/nvme*` entry # locked the whole device namespace on driveripper. Device access is DAC (the `disk` group # the installer grants) plus SELinux, the standard SMART-monitoring arrangement. assert "DevicePolicy" not in unit, ( "a strict device policy with a name-matched allow-list is the deny-all device filter " "that locked driveripper's SMART reads — the unit must carry none" ) assert "DeviceAllow" not in unit, "no device filter means no allow-list either" # PrivateDevices=false survives as the explicit statement of the systemd default: a # private /dev with no device list dies at spawn (status 208/STDIN), because systemd # opens /dev/null for the service stdin before it execs python. assert "PrivateDevices=false" in unit, ( "the explicit default is what keeps a private-/dev sandbox from dying at STDIN" ) assert token and len(token) >= 32, "a generated token is expected" assert token in result.stdout, "first install prints the token, and never again" assert f'HOST_TARGETS="hostname=http://{token}@10.0.0.5:9120"' in result.stdout calls = call_log.read_text() assert "systemctl daemon-reload" in calls assert "systemctl enable --now dashboards-agent" in calls assert "chown root:dashboards-agent" in calls, "the token file must become group-readable" #: A line the unit heredoc may legitimately write: a comment, a `[Section]` header, or a #: systemd directive assignment. Anything else is the unquoted heredoc having *expanded* #: something — see `test_the_written_unit_is_only_comments_and_directives`. _UNIT_LINE_RE = re.compile(r"^(?:#|\[[A-Za-z]+\]|[A-Za-z]+=)") @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_written_unit_is_only_comments_and_directives(tmp_path: Path) -> None: """The unit heredoc is unquoted so `$TLS_UNIT_LINES` and `$TLS_EXEC_ARGS` can expand. That is also what executes its **comments**: writing the phase-17 rationale with backticks around `disk`, `blkext` and `/dev/nvme*` made the install run them (`disk: command not found`, smartctl's version banner) and paste the output into the unit file, with `bash -n` and every text assertion still happy. Quote the heredoc and phase 15's interpolated lines stop working, so the guard is on the *result*: a unit line is a comment, a section header, or a directive assignment — never the output of something the prose mentioned. """ script, _call_log, env = _sandbox_installer(tmp_path) result = subprocess.run( [BASH, str(script), "--bind", "127.0.0.1", "--port", "9199"], capture_output=True, text=True, env=env, timeout=60, check=False, ) assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" unit = (tmp_path / "target" / "dashboards-agent.service").read_text() offenders = [line for line in unit.splitlines() if line.strip() and not _UNIT_LINE_RE.match(line)] assert not offenders, "the unit heredoc expanded its own text into the unit file:\n" + "\n".join( offenders ) assert result.stderr == "", "installing must not execute anything the unit's comments mention" # --- the template's unit: no device filter, and the whole unit pinned (phase 17 task 04) ------ #: The ten lines the pre-phase template wrote between ``PrivateDevices=false`` and #: ``ProtectSystem=strict`` — the device filter phase 17 removes. Pinned as the *old* text, in #: order, because the fix is the absence of exactly these lines: re-adding #: ``DeviceAllow=/dev/nvme* rw`` with ``DevicePolicy=strict`` is the deny-all returning, and a #: test that only asserted "`DevicePolicy` is absent today" would pass while somebody added #: the allow-list back under a default `auto` policy that ignores it. DEVICE_FILTER_LINES = ( "DevicePolicy=strict", "DeviceAllow=/dev/null rw", "DeviceAllow=/dev/zero rw", "DeviceAllow=/dev/full rw", "DeviceAllow=/dev/random rw", "DeviceAllow=/dev/urandom rw", "DeviceAllow=/dev/nvme* rw", "DeviceAllow=/dev/sd* rw", "DeviceAllow=/dev/vd* rw", "DeviceAllow=/dev/sg* rw", ) #: The unit the template wrote **before** phase 17, rendered for a default token install #: (bind `10.0.0.5`, port `9120`, no bundle), with the sandbox root spelled ``. #: #: This is the whole unit and not a highlight reel: it is the reference the default install is #: compared against, so a template line added, dropped, reordered or reworded *outside* this #: phase's remit fails a test instead of landing in the next host's service file. The comment #: block is included verbatim (it is what the operator reads in `systemctl cat`) even though #: phase 17 task 01 rewrote that prose on purpose — which is why the comparison below reads #: directives, and the new rationale is pinned separately for the two facts it must carry. PRE_PHASE_DEFAULT_UNIT = """[Unit] Description=Homelab host agent (read-only system metrics) After=network-online.target Wants=network-online.target [Service] Type=simple User=dashboards-agent ExecStart=/usr/bin/env python3 /target/lib/host_agent.py --host 10.0.0.5 --port 9120 --token-file /target/token Restart=on-failure RestartSec=5 Environment=PYTHONUNBUFFERED=1 SyslogIdentifier=dashboards-agent # The agent reads /proc, /sys and statvfs() and writes nothing, so everything writable can # be taken away. smartctl is the one thing that needs a real /dev node, so instead of # PrivateDevices the unit closes all devices and reopens only the disk nodes, read/write # at the cgroup layer while the code (and smartctl -i -A -H) stays read-only by design. # # The pseudo nodes are not decoration: DevicePolicy=strict denies every node that is not # listed, and systemd needs /dev/null for the service's stdin before it ever execs python — # without it the unit dies at spawn with "Failed at step STDIN" (status 208), which is how # the first phase-10 install failed on a live host running systemd 257. /dev/zero, full, # random and urandom are the rest of the set a PrivateDevices= sandbox would have provided, # and none of them is a disk. NoNewPrivileges=true PrivateTmp=true PrivateDevices=false DevicePolicy=strict DeviceAllow=/dev/null rw DeviceAllow=/dev/zero rw DeviceAllow=/dev/full rw DeviceAllow=/dev/random rw DeviceAllow=/dev/urandom rw DeviceAllow=/dev/nvme* rw DeviceAllow=/dev/sd* rw DeviceAllow=/dev/vd* rw DeviceAllow=/dev/sg* rw ProtectSystem=strict ProtectHome=read-only ProtectKernelTunables=true ProtectKernelModules=true ProtectControlGroups=true RestrictSUIDSGID=true RestrictNamespaces=true RestrictRealtime=true LockPersonality=true MemoryDenyWriteExecute=true CapabilityBoundingSet= SystemCallFilter=@system-service [Install] WantedBy=multi-user.target """ def _unit_heredoc() -> str: """The template's unit heredoc, exactly as the script holds it. Scoped on purpose: the rest of the script is prose about flags, and a pin that read the whole file for `DeviceAllow` would be satisfied by deleting a sentence rather than by the absence of a *unit line*. """ text = INSTALLER.read_text() assert "< list[str]: """The lines systemd acts on — every line that is neither a comment nor blank. Comments carry the phase's reasoning and *are* part of the file, but they are also the one thing phase 17 task 01 rewrote deliberately (the old block justified the filter; the new one explains its absence). Directives are what a host runs, so those are compared exactly. """ return [line for line in unit.splitlines() if line.strip() and not line.startswith("#")] def test_the_template_unit_has_no_device_filter_lines() -> None: """The fix is the absence, so the absence is what is pinned. Three layers, because the regression has three shapes: a `DevicePolicy=strict` or `DeviceAllow=` line back in the heredoc (the deny-all returning on kernel 7.2, where systemd matches those globs by *name* against `/proc/devices` and lists NVMe as `blkext`, so `/dev/nvme*` matched nothing and strict denied the whole namespace); `PrivateDevices=false` removed as "redundant" because it is the systemd default (a private `/dev` with no device list dies at spawn with `status=208/STDIN`); and a rationale comment that stops naming the mechanism, which is how the next operator re-adds the filter while believing they are hardening the unit. """ text = INSTALLER.read_text() # The phase's completion criterion is this exact grep, so it is pinned as one: neither # directive may appear anywhere in the script, prose included. assert "DevicePolicy" not in text, "no device policy line anywhere in the installer" assert "DeviceAllow" not in text, "no device allow-list anywhere in the installer" heredoc = _unit_heredoc() offenders = [line for line in heredoc.splitlines() if re.match(r"^\s*(DevicePolicy|DeviceAllow)\s*=", line)] assert offenders == [], "the unit heredoc grew a device filter line again:\n" + "\n".join(offenders) assert "PrivateDevices=false" in heredoc, ( "the explicit default is the line that keeps the unit from dying at STDIN; the filter " "going away does not make it redundant" ) # The rewritten rationale has to be an explanation, not a shrug (task 01's own criterion). comment = "\n".join(line for line in heredoc.splitlines() if line.startswith("#")) assert "/proc/devices" in comment and "blkext" in comment, ( "the comment must name the mechanism, or the next reader cannot tell an omitted filter " "from a forgotten one" ) assert "208" in comment, "and the STDIN failure that PrivateDevices=false still prevents" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_default_install_unit_is_the_pre_phase_unit_minus_the_filter_lines(tmp_path: Path) -> None: """No flags writes the unit this script has always written, minus exactly ten lines. `AGENTS.md` invariant 3 in deployment clothes: a host that needs nothing new must not get a new unit from an unrelated phase. The reference is the whole pre-phase unit recorded above, so every directive is compared (not a keyword spot-check) and the only allowed difference is the device filter's absence — asserted as the ordered list comparison *and* as each of the ten lines being absent by name, so a filter line silently renamed rather than removed fails too. """ script, _call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, "--bind", "10.0.0.5", "--port", "9120") assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" installed = (tmp_path / "target" / "dashboards-agent.service").read_text() reference = PRE_PHASE_DEFAULT_UNIT.replace("", str(tmp_path)) expected = [line for line in _unit_directives(reference) if line not in DEVICE_FILTER_LINES] assert len(expected) == len(_unit_directives(reference)) - len(DEVICE_FILTER_LINES), ( "DEVICE_FILTER_LINES must match ten real lines of the reference unit — a filter line " "this list no longer names is a filter that would pass this test" ) got = _unit_directives(installed) drift = "\n".join( difflib.unified_diff( expected, got, fromfile="pre-phase unit minus the filter", tofile="installed", lineterm="" ) ) assert got == expected, f"the default unit drifted from the pre-phase unit by more than the filter:\n{drift}" # And the ten lines are gone rather than reworded into something equivalent. assert not [line for line in installed.splitlines() if line.startswith(DEVICE_FILTER_LINES)] # --- the kernel-gate unit: --as-root / --caps (phase 17 task 02) --------------------------- #: The exact sentence `--as-root` alone prints. It is not a refusal — a capability-less root is #: a legitimate if unusual choice — but it is the fact gap 10 paid for: the first root-user #: attempt on the kernel-7.2 host still read `n/a` on all 16 drives, because the bounding set #: bounds even root (`CapEff 0`). Pinned verbatim so a paraphrase that drops "not uid" is a #: test failure rather than an operator rediscovering it. AS_ROOT_NOTE = ( "note: --as-root with the default empty CapabilityBoundingSet leaves the process " "capability-less (the kernel gates check capabilities, not uid); pair it with --caps " "for a kernel-gated host" ) #: `--caps` on the dedicated service user is not the kernel-gate fix (a non-root process cannot #: hold these capabilities across exec), so the combination is refused rather than written. CAPS_REFUSAL = "--caps needs --as-root: a capability grant on the service user is not the kernel-gate fix" #: The two capabilities the kernel names for the two gates (NVMe SMART log / SG_IO), and the #: `driveripper` recipe as `--help` must teach it (phase 17 completion criterion). KERNEL_CAPS = "CAP_SYS_RAWIO,CAP_SYS_ADMIN" KERNEL_GATE_RECIPE = f"--as-root --caps {KERNEL_CAPS}" def _install(script: Path, env: dict[str, str], *flags: str) -> subprocess.CompletedProcess[str]: """One sandboxed installer run: fake root boundary, real script body.""" try: return subprocess.run( [BASH, str(script), *flags], capture_output=True, text=True, env=env, timeout=60, check=False, ) except subprocess.TimeoutExpired as exc: # pragma: no cover - the recursion regression pytest.fail(f"the installer did not return (flags {flags}, partial output: {exc.stdout!r})") def _directive(unit: str, key: str) -> list[str]: """Every line of the unit assigning `key` — a duplicate assignment is a heredoc bug.""" return [line for line in unit.splitlines() if line.split("=", 1)[0] == key] @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_default_unit_keeps_the_service_user_and_an_empty_bounding_set(tmp_path: Path) -> None: """No flags = the unit this script wrote before the flags existed, byte-for-byte. `CapabilityBoundingSet=` with nothing after the `=` is the capability-less default and what every host whose drives answer through the `disk` group runs; it is also the line `--caps` replaces, so its exact spelling is pinned here rather than as `in unit`. """ script, _call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, "--bind", "127.0.0.1", "--port", "9199") assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" unit = (tmp_path / "target" / "dashboards-agent.service").read_text() assert _directive(unit, "User") == ["User=dashboards-agent"] assert _directive(unit, "CapabilityBoundingSet") == ["CapabilityBoundingSet="] assert AS_ROOT_NOTE not in result.stdout, "the note belongs to --as-root alone" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_as_root_with_caps_writes_the_kernel_gate_unit(tmp_path: Path) -> None: """The two lines that reproduce the working `driveripper` unit, exactly. Comma on the command line, space in the unit: that is systemd's own spelling for `CapabilityBoundingSet`, and it is the whole reason the flag takes a list rather than the line. Everything else in the unit — the token file, its `0640 root:dashboards-agent`, the hardening block — is unchanged by these flags. """ script, call_log, env = _sandbox_installer(tmp_path) kernel_gate = ["--bind", "10.0.0.5", "--port", "9120", "--as-root", "--caps", KERNEL_CAPS] result = _install(script, env, *kernel_gate) assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" unit = (tmp_path / "target" / "dashboards-agent.service").read_text() assert _directive(unit, "User") == ["User=root"] assert _directive(unit, "CapabilityBoundingSet") == ["CapabilityBoundingSet=CAP_SYS_RAWIO CAP_SYS_ADMIN"] assert AS_ROOT_NOTE not in result.stdout, "--caps answers the note: the grant is there" # The token file's logic is deliberately untouched by --as-root: root reads it either way, # and re-deriving the mode per service user would be a second rule for no new safety. token = tmp_path / "target" / "token" assert stat.S_IMODE(token.stat().st_mode) == 0o640 assert "chown root:dashboards-agent" in call_log.read_text() assert "--host 10.0.0.5 --port 9120" in unit @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_as_root_and_caps_write_the_kernel_gate_unit_lines(tmp_path: Path) -> None: """The whole flag contract in one place, plus the part only a comparison can pin. The three flag shapes (`--as-root --caps LIST`, `--caps` alone, `--as-root` alone) each have their own detailed pin nearby; what none of them shows is **how much** the recipe moves. So: two installs in two sandboxes at the same bind and port, one default and one kernel-gated, and their directive lists must differ by exactly the two lines this phase promises and nothing else — the hardening block, the token file's path, the `ExecStart` and the `[Install]` section are not these flags' business, and an `--as-root` that also slipped a `ProtectSystem` change in would be a privilege change nobody asked for. The two half-recipes are then run in their own sandboxes (one host re-run with different flags is the guard's decision, not the parser's). """ plain_script, _plain_log, plain_env = _sandbox_installer(tmp_path / "plain") plain = _install(plain_script, plain_env, "--bind", "10.1.0.126", "--port", "9120") assert plain.returncode == 0, f"default install failed:\n{plain.stdout}\n{plain.stderr}" gate_script, gate_log, gate_env = _sandbox_installer(tmp_path / "gate") recipe = _install( gate_script, gate_env, "--bind", "10.1.0.126", "--port", "9120", "--as-root", "--caps", KERNEL_CAPS ) assert recipe.returncode == 0, f"the driveripper recipe failed:\n{recipe.stdout}\n{recipe.stderr}" def directives(where: str) -> list[str]: root = tmp_path / where unit = (root / "target" / "dashboards-agent.service").read_text() return _unit_directives(unit.replace(str(root), "")) changed = [ f"{a} -> {b}" for a, b in zip(directives("plain"), directives("gate"), strict=True) if a != b ] assert changed == [ "User=dashboards-agent -> User=root", "CapabilityBoundingSet= -> CapabilityBoundingSet=CAP_SYS_RAWIO CAP_SYS_ADMIN", ], "--as-root --caps may write these two lines and nothing else" assert "chown root:dashboards-agent" in gate_log.read_text(), ( "--as-root leaves the token file's ownership alone: root reads it either way, and the " "grant must not widen because of a flag that is about capabilities" ) # The two half-recipes, in their own sandboxes (a differing re-run of one host is the # guard's business, not the flag parser's). caps_only_script, caps_only_log, caps_only_env = _sandbox_installer(tmp_path / "caps-only") caps_only = _install( caps_only_script, caps_only_env, "--bind", "127.0.0.1", "--port", "9199", "--caps", KERNEL_CAPS ) assert caps_only.returncode == 1, "--caps without --as-root is refused" assert CAPS_REFUSAL in caps_only.stderr assert not caps_only_log.exists(), "and refused before a single privileged command" root_only_script, _root_only_log, root_only_env = _sandbox_installer(tmp_path / "root-only") root_only = _install(root_only_script, root_only_env, "--bind", "127.0.0.1", "--port", "9199", "--as-root") assert root_only.returncode == 0, f"--as-root alone must proceed:\n{root_only.stderr}" assert AS_ROOT_NOTE in root_only.stdout, "with the capability-less note" root_unit = (tmp_path / "root-only" / "target" / "dashboards-agent.service").read_text() assert _directive(root_unit, "User") == ["User=root"] assert _directive(root_unit, "CapabilityBoundingSet") == ["CapabilityBoundingSet="] @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_as_root_alone_installs_and_prints_the_capability_note(tmp_path: Path) -> None: """A capability-less root proceeds — with the reminder, on stdout, and no refusal.""" script, _call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, "--bind", "127.0.0.1", "--port", "9199", "--as-root") assert result.returncode == 0, f"--as-root alone must proceed:\n{result.stdout}\n{result.stderr}" assert AS_ROOT_NOTE in result.stdout, "the note is the operator's only warning here" unit = (tmp_path / "target" / "dashboards-agent.service").read_text() assert _directive(unit, "User") == ["User=root"] assert _directive(unit, "CapabilityBoundingSet") == ["CapabilityBoundingSet="], ( "--as-root alone writes no capability grant: the note is the whole effect" ) @pytest.mark.parametrize( ("caps", "refusal"), [ ("CAP_SYS_RAWIO", CAPS_REFUSAL), ("CAP_SYS_RAWIO,CAP_SYS_ADMIN", CAPS_REFUSAL), ("", "--caps needs at least one capability name"), (" , ", "--caps needs at least one capability name"), ], ids=["one-cap", "two-caps", "empty", "punctuation-only"], ) @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_caps_without_as_root_is_refused_before_anything_is_written( tmp_path: Path, caps: str, refusal: str ) -> None: """Exit 1, the reason on stderr, and no side effect at all. The check sits *before* `need_root` (the `--cert-bundle` validator's reasoning): a wrong flag combination gets its own sentence instead of a sudoers hint, and nothing after it — user, group membership, token, unit — can have run. """ script, call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, "--bind", "127.0.0.1", "--port", "9199", "--caps", caps) assert result.returncode == 1, "the refusal keeps exit 1" assert result.stdout == "", "a refusal is a failure: nothing on stdout" assert refusal in result.stderr assert GRANT_PREFIX not in result.stderr, "a flag mistake is not a sudoers problem" target = tmp_path / "target" assert not (target / "dashboards-agent.service").exists(), "refused means nothing written" assert not (target / "token").exists() assert not call_log.exists(), "no privileged command ran either" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_caps_without_an_argument_is_refused_rather_than_crashing(tmp_path: Path) -> None: """`--caps` last on the command line: exit 1 naming the flag, not an `unbound variable`.""" script, _call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, "--bind", "127.0.0.1", "--port", "9199", "--as-root", "--caps") assert result.returncode == 1 assert result.stdout == "" assert "--caps needs a capability list" in result.stderr @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_caps_list_is_normalised_to_systemd_spelling(tmp_path: Path) -> None: """Empty entries and stray whitespace never reach the unit as a double space… …and a `*` stays a literal: the list is split with pathname expansion switched off, so an operator typo cannot substitute the working directory's file names into `User=`'s partner. """ script, _call_log, env = _sandbox_installer(tmp_path) messy = ["--bind", "127.0.0.1", "--port", "9199", "--as-root", "--caps", "CAP_SYS_RAWIO,, CAP_SYS_ADMIN "] result = _install(script, env, *messy) assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" unit = (tmp_path / "target" / "dashboards-agent.service").read_text() assert _directive(unit, "CapabilityBoundingSet") == ["CapabilityBoundingSet=CAP_SYS_RAWIO CAP_SYS_ADMIN"] script_glob, _log, env_glob = _sandbox_installer(tmp_path / "glob") star = ["--bind", "127.0.0.1", "--port", "9199", "--as-root", "--caps", "*"] result_glob = _install(script_glob, env_glob, *star) assert result_glob.returncode == 0, f"install failed:\n{result_glob.stdout}\n{result_glob.stderr}" unit_glob = (tmp_path / "glob" / "target" / "dashboards-agent.service").read_text() assert _directive(unit_glob, "CapabilityBoundingSet") == ["CapabilityBoundingSet=*"], ( "the capability list is words, not a glob" ) def test_help_teaches_the_kernel_gate_recipe() -> None: """Completion criterion: the `driveripper` flags are learnable from `--help` alone. `--help` *is* the header comment, so the sentence that explains why a root service needs its capabilities named is pinned here too — it is the one thing an executor on a fresh clone cannot infer from a unit file. """ result = _run([BASH, str(INSTALLER), "--help"]) assert result.returncode == 0 assert KERNEL_GATE_RECIPE in result.stdout, "the recipe line must be copy-pasteable" assert "--as-root run the service as root" in result.stdout assert "--caps LIST CapabilityBoundingSet line for the unit" in result.stdout assert "capabilities, not uid" in result.stdout, "the kernel gate is the why of both flags" assert "capability-less" in result.stdout, "--as-root without --caps has to say what it is" def test_the_status_probe_is_blind_to_who_the_service_runs_as() -> None: """`--status` probes the agent over the wire; the unit's user is invisible to it, on purpose. A status path that read `AS_ROOT`/`CAPS` would make a health probe depend on flags the operator may not have passed on this invocation — the probe answers about the port. """ status_body = INSTALLER.read_text().split("status() {", 1)[1].split("\n}", 1)[0] assert "AS_ROOT" not in status_body assert "CAPS" not in status_body def test_token_file_is_readable_by_the_service_user_and_nobody_else(tmp_path: Path) -> None: """`--token-file` is a path the *agent* opens, as its own user. systemd does not read the secret for the service, so a 0600 root-owned file makes the agent exit before it binds. 0640 root:dashboards-agent is the smallest mode that works, and it is still unreadable by `ops` (which is why deploy/agent-sudoers does not grant it). """ script, _call_log, env = _sandbox_installer(tmp_path) result = subprocess.run( [BASH, str(script), "--bind", "10.0.0.5"], capture_output=True, text=True, env=env, timeout=60, check=False, ) assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" mode = stat.S_IMODE((tmp_path / "target" / "token").stat().st_mode) assert mode == 0o640, f"token file mode is {oct(mode)}, expected 0o640 (group = the agent user)" assert not mode & 0o007, "the token file must never be world-readable" # --- the re-run guard: no silent clobber (phase 17 task 03) ---------------------------------- def _refusal(unit: Path) -> str: """The sentence a refused re-run prints, with the unit path substituted. Pinned verbatim because it is the operator's only explanation of why a routine re-run stopped, and because the diff follows it on **stdout** rather than stderr: the diff is the payload of the answer (an operator pipes it into a pager or a file), and splitting a sentence from its diff across two streams only interleaves them. """ return f"refusing to overwrite {unit} (generated unit differs); run with --force to apply:" #: One line the operator can plausibly have edited on the host — `RestartSec` is a real knob, #: it is not a line any flag writes, and it sits far from every other assertion in these tests. OPERATOR_EDIT = ("RestartSec=5", "RestartSec=7 # hand-tuned on the host after a flapping start") def _hand_edit(unit_path: Path) -> str: """Edit the installed unit the way an operator does, and return what it now holds.""" text = unit_path.read_text() assert OPERATOR_EDIT[0] in text, "the template unit lost the line the operator edits" edited = text.replace(*OPERATOR_EDIT) unit_path.write_text(edited, encoding="utf-8") return edited def _systemctl_calls(call_log: Path) -> list[str]: """Only the `systemctl` lines of the stub log — the ones that touch the running service. The privileged calls before the guard (`id`, `getent`, `usermod`, `chown`) do run again on a refused re-run: they are the idempotent ones the guard's reasoning accepts. `systemctl` is what a refused run must not do — no `daemon-reload`, no `enable --now` — so that is the half compared here. """ return [line for line in call_log.read_text().splitlines() if line.startswith("systemctl ")] @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_rerun_that_would_change_the_installed_unit_refuses(tmp_path: Path) -> None: """install → hand-edit → re-run: exit 1, the diff on stdout, nothing applied. This is the incident the guard exists for: `driveripper`'s unit was fixed by hand, and the installer rewrote it unconditionally. A refused run must leave the unit on disk as the operator left it and must not reload or re-enable anything, or the refusal arrives after the thing it is refusing. """ script, call_log, env = _sandbox_installer(tmp_path) unit_path = tmp_path / "target" / "dashboards-agent.service" token_path = tmp_path / "target" / "token" first = _install(script, env, "--bind", "10.0.0.5", "--port", "9120") assert first.returncode == 0, f"first install failed:\n{first.stdout}\n{first.stderr}" edited = _hand_edit(unit_path) token_before = token_path.read_bytes() reloads_before = _systemctl_calls(call_log) assert reloads_before, "the first install did reload and enable the service" result = _install(script, env, "--bind", "10.0.0.5", "--port", "9120") assert result.returncode == 1, "a differing re-run refuses with exit 1" assert result.stderr == "", "the refusal and its diff are one answer: stdout, not split" assert _refusal(unit_path) in result.stdout assert "--- " in result.stdout and "+++ " in result.stdout, "the diff is unified" assert f"-{OPERATOR_EDIT[1]}" in result.stdout, "the diff shows the operator's line going away" assert "+RestartSec=5" in result.stdout, "and the template's line coming back" assert unit_path.read_text() == edited, "a refused run leaves the installed unit byte-identical" # The reuse path re-reads the existing secret and rewrites the same bytes with the same # mode; its *value* — what invariant 6 and the HOST_TARGETS entry depend on — never moves, # which is the guarantee here (mtime is not: the write happens before the guard). assert token_path.read_bytes() == token_before, "the reused token must not change" assert stat.S_IMODE(token_path.stat().st_mode) == 0o640 assert _systemctl_calls(call_log) == reloads_before, ( "a refused run must not daemon-reload or enable: that would restart the service the " "refusal exists to leave alone" ) @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_force_prints_the_diff_then_writes_the_generated_unit(tmp_path: Path) -> None: """`--force` is the opt-in, and the diff still arrives: an operator approves what they saw.""" script, call_log, env = _sandbox_installer(tmp_path) unit_path = tmp_path / "target" / "dashboards-agent.service" _install(script, env, "--bind", "10.0.0.5", "--port", "9120") _hand_edit(unit_path) result = _install(script, env, "--bind", "10.0.0.5", "--port", "9120", "--force") assert result.returncode == 0, f"--force must proceed:\n{result.stdout}\n{result.stderr}" assert _refusal(unit_path) not in result.stdout, "--force is not a refusal" assert f"-{OPERATOR_EDIT[1]}" in result.stdout and "+RestartSec=5" in result.stdout, ( "the diff is printed even when the answer is yes" ) unit = unit_path.read_text() assert "RestartSec=5" in unit and OPERATOR_EDIT[1] not in unit, "the generated unit is written" assert _systemctl_calls(call_log).count("systemctl enable --now dashboards-agent") == 2, ( "the overwrite is followed by the usual reload/enable" ) @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_an_unchanged_rerun_is_silent_and_proceeds(tmp_path: Path) -> None: """Idempotence is the normal path: same flags, same unit, no diff, no complaint, exit 0.""" script, call_log, env = _sandbox_installer(tmp_path) unit_path = tmp_path / "target" / "dashboards-agent.service" first = _install(script, env, "--bind", "10.0.0.5", "--port", "9120") assert first.returncode == 0, f"first install failed:\n{first.stdout}\n{first.stderr}" installed = unit_path.read_text() result = _install(script, env, "--bind", "10.0.0.5", "--port", "9120") assert result.returncode == 0, f"an identical re-run must proceed:\n{result.stdout}\n{result.stderr}" assert _refusal(unit_path) not in result.stdout assert "+++ " not in result.stdout, "nothing differs, so nothing is printed about differences" assert "reusing the existing token" in result.stdout, "the token idiom is unchanged" assert unit_path.read_text() == installed assert _systemctl_calls(call_log).count("systemctl enable --now dashboards-agent") == 2 @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_re_run_that_differs_refuses_without_force_and_applies_with_it(tmp_path: Path) -> None: """The refuse → force → silent sequence on **one** host, in that order. Each leg has its own detailed pin beside it; what those cannot show is the transition — a guard that refuses correctly the first time and then never lets an identical re-run through silently is the same bug seen from the other side (an operator who has to type `--force` every deploy stops reading the diff). So: install, hand-edit, re-run (refused, unit left exactly as edited, no new `systemctl` call), re-run with `--force` (diff first, generated unit on disk, service re-enabled), then re-run plain once more (silent, unit unmoved). """ script, call_log, env = _sandbox_installer(tmp_path) unit_path = tmp_path / "target" / "dashboards-agent.service" flags = ["--bind", "10.1.0.126", "--port", "9120", "--as-root", "--caps", KERNEL_CAPS] first = _install(script, env, *flags) assert first.returncode == 0, f"first install failed:\n{first.stdout}\n{first.stderr}" edited = _hand_edit(unit_path) reloads = len(_systemctl_calls(call_log)) refused = _install(script, env, *flags) assert refused.returncode == 1, "leg 1: a differing re-run refuses" assert _refusal(unit_path) in refused.stdout and "+++ " in refused.stdout assert unit_path.read_text() == edited, "leg 1: the hand edit survives" assert len(_systemctl_calls(call_log)) == reloads, "leg 1: nothing was reloaded or re-enabled" applied = _install(script, env, *flags, "--force") assert applied.returncode == 0, f"leg 2: --force failed:\n{applied.stdout}\n{applied.stderr}" assert "+++ " in applied.stdout, "leg 2: the diff is printed even on a yes" assert unit_path.read_text() != edited, "leg 2: the generated unit replaced the hand edit" assert "RestartSec=5" in unit_path.read_text() generated = unit_path.read_text() # three calls per applied install: daemon-reload, enable --now, and the is-active check assert len(_systemctl_calls(call_log)) == reloads + 3, "leg 2: reload + enable, as a normal install" settled = _install(script, env, *flags) assert settled.returncode == 0, f"leg 3: a silent re-run failed:\n{settled.stdout}\n{settled.stderr}" assert "refusing to overwrite" not in settled.stdout and "+++ " not in settled.stdout assert "reusing the existing token" in settled.stdout assert unit_path.read_text() == generated, "leg 3: the silent path leaves the applied unit in place" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_fresh_install_never_meets_the_guard(tmp_path: Path) -> None: """No installed unit → no comparison, no output about one, and `--force` changes nothing. The guard is about a *re*-run. A first install must stay exactly the run it was before this task, with or without `--force` — hence the two sandboxes writing byte-identical units. """ plain_script, _plain_log, plain_env = _sandbox_installer(tmp_path / "plain") forced_script, _forced_log, forced_env = _sandbox_installer(tmp_path / "forced") plain = _install(plain_script, plain_env, "--bind", "10.0.0.5", "--port", "9120") forced = _install(forced_script, forced_env, "--bind", "10.0.0.5", "--port", "9120", "--force") assert plain.returncode == 0, f"a first install failed:\n{plain.stdout}\n{plain.stderr}" assert forced.returncode == 0, f"--force broke a first install:\n{forced.stdout}\n{forced.stderr}" for stdout in (plain.stdout, forced.stdout): assert "refusing to overwrite" not in stdout assert "+++ " not in stdout def rendered(where: str) -> str: """The unit that sandbox wrote, with its own sandbox root name blanked out. The two runs live in different directories, so the absolute paths in `ExecStart` and `--token-file` differ while every *decision* in the unit is the same — and the decision is what `--force` must not touch. """ root = tmp_path / where return ( (root / "target" / "dashboards-agent.service") .read_text() .replace(str(root), "") ) assert rendered("plain") == rendered("forced"), "--force writes the unit the default install writes" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_kernel_gate_reproduction_refuses_once_and_then_holds(tmp_path: Path) -> None: """The `driveripper` case end to end: a token-mode unit is *not* the kernel-gate unit. Switching a host to `--as-root --caps` is a real change to a real unit, so it asks first, applies with `--force`, and is silent afterwards — the flag line in `--help` is a reproduction command only if running it twice lands the same unit and says nothing twice. """ script, _call_log, env = _sandbox_installer(tmp_path) unit_path = tmp_path / "target" / "dashboards-agent.service" kernel_gate = ["--bind", "10.1.0.126", "--port", "9120", "--as-root", "--caps", KERNEL_CAPS] _install(script, env, "--bind", "10.1.0.126", "--port", "9120") token_mode = unit_path.read_text() asked = _install(script, env, *kernel_gate) assert asked.returncode == 1, "a flag change to an installed unit is a change, so it asks" assert _refusal(unit_path) in asked.stdout assert "-User=dashboards-agent" in asked.stdout and "+User=root" in asked.stdout assert "+CapabilityBoundingSet=CAP_SYS_RAWIO CAP_SYS_ADMIN" in asked.stdout assert unit_path.read_text() == token_mode, "the question must not also be the answer" applied = _install(script, env, *kernel_gate, "--force") assert applied.returncode == 0, f"--force failed:\n{applied.stdout}\n{applied.stderr}" applied_unit = unit_path.read_text() assert _directive(applied_unit, "User") == ["User=root"] assert _directive(applied_unit, "CapabilityBoundingSet") == [ "CapabilityBoundingSet=CAP_SYS_RAWIO CAP_SYS_ADMIN" ] again = _install(script, env, *kernel_gate, "--force") assert again.returncode == 0, f"the same command twice failed:\n{again.stdout}\n{again.stderr}" assert "+++ " not in again.stdout, "once applied, the same flags are the zero-noise path" assert unit_path.read_text() == applied_unit @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_uninstall_is_still_deliberate_and_never_asks(tmp_path: Path) -> None: """The guard belongs to `do_install`: `--uninstall` removes a hand-edited unit without one. A guard on the teardown path would be a guard that keeps a unit nobody wants, and `uninstall()` does not render a unit at all. """ script, call_log, env = _sandbox_installer(tmp_path) unit_path = tmp_path / "target" / "dashboards-agent.service" _install(script, env, "--bind", "10.0.0.5", "--port", "9120") _hand_edit(unit_path) result = _install(script, env, "--uninstall") assert result.returncode == 0, f"uninstall refused:\n{result.stdout}\n{result.stderr}" assert "refusing to overwrite" not in result.stdout assert not unit_path.exists(), "uninstall still removes the unit, edited or not" assert "systemctl disable --now dashboards-agent" in _systemctl_calls(call_log)[-3:] def test_the_guard_sits_between_the_render_and_the_unit_write() -> None: """Text pin for the ordering the behaviour depends on. The comparison must read the *rendered* unit (every flag, bundle and capability decision applied) and must come before the write, the `daemon-reload` and the `enable`; a `cat > "$UNIT" < "$TMP_UNIT" < "$UNIT"') reload = body.index("systemctl daemon-reload") enable = body.index('systemctl enable --now "$SERVICE"') assert render < guard < write < reload < enable assert 'cat > "$UNIT" <<' not in text, "the template may only write $UNIT after comparing it" assert 'if [ "$FORCE" -eq 1 ]; then' in text, "--force is the guard's own opt-in branch" assert "rm -f \"$TMP_UNIT\"" in text, "the rendered temp file is cleaned up on every path" assert 'mktemp "${TMPDIR:-/tmp}/dashboards-agent-unit.XXXXXX"' in text, ( "the diff's +++ side must name the generated unit, not an anonymous temp file" ) #: The `--force` help line's sentence (phase 17 completion criterion: the flags are learnable #: from `--help`, and the diff-first promise is the reason an operator trusts `--force`). FORCE_HELP_SENTENCE = "overwrite an existing unit even if the generated unit differs" def test_help_documents_force_and_keeps_the_flag_block_aligned() -> None: stdout = _run([BASH, str(INSTALLER), "--help"]).stdout lines = [line for line in stdout.splitlines() if FORCE_HELP_SENTENCE in line] assert len(lines) == 1, f"exactly one --force flag line is expected, got {lines}" assert lines[0].lstrip().startswith("--force") assert "(prints the" in lines[0] and "diff first)" in stdout # One column for every flag's description: the block is a table, and `--force` landing at # its own column is how `--help` stays readable as a reference rather than a paragraph. as_root = next(line for line in stdout.splitlines() if line.lstrip().startswith("--as-root")) assert lines[0].index(FORCE_HELP_SENTENCE) == as_root.index("run the service as root") # --- the certificate bundle (phase 15 task 06) -------------------------------------------- #: Where the installed bundle lives — one constant in the script, pinned here so a rename #: fails a test instead of stranding every already-written unit file. CERT_FILE_PATH = "/etc/dashboards-agent.certs" def _fake_bundle_text(revision: str = "3") -> str: """A syntactically real bundle: agent cert, agent key, root CA, and the rev line. Shape only. The installer's validator greps PEM blocks by text exactly like `resolve_bundle` does (a monitored host has no `cryptography`), so a well-formed *fake* is all an unprivileged validation run needs; the functional probe tests below use a real bundle minted by `dashboards.certs`. """ def block(kind: str, body: str) -> str: return f"-----BEGIN {kind}-----\n{body}\n-----END {kind}-----\n" return ( f"# BUNDLE-REV {revision}\n" + block("CERTIFICATE", "AGENTCERT") + block("PRIVATE KEY", "AGENTKEY") + block("CERTIFICATE", "ROOTCA") ) def _installer_status(stdout: str) -> str: """The line `status()` prints as `health: …`, parsed as JSON when it is JSON.""" for line in stdout.splitlines(): if line.startswith("health: "): body = line.removeprefix("health: ").strip() try: return str(json.loads(body)["ok"]) except (json.JSONDecodeError, KeyError): return body pytest.fail(f"the probe printed no health line:\n{stdout}") @pytest.mark.parametrize( ("shape", "reason"), [ ("missing", "cannot be read"), ("empty", "is empty"), ("no-revision", "BUNDLE-REV"), ("two-keys", "the agent certificate, the agent key and the root CA"), ], ) def test_a_bad_cert_bundle_is_refused_with_its_reason_before_anything_else( tmp_path: Path, shape: str, reason: str ) -> None: """Completion criterion: `--cert-bundle /nonexistent` refuses with the reason, touches nothing. The validation runs *before* `need_root`, so the failure names the file rather than sending the operator to add a sudoers line for a typo in the flag's argument — and nothing after the validation (user, files, unit) can have run. """ if shape == "missing": path = tmp_path / "absent.bundle" elif shape == "empty": path = tmp_path / "empty.bundle" path.write_text("\n", encoding="ascii") elif shape == "no-revision": path = tmp_path / "revless.bundle" path.write_text(_fake_bundle_text().replace("# BUNDLE-REV 3\n", ""), encoding="ascii") else: # a bundle with the root *key* in it is exactly what the shape check refuses path = tmp_path / "leaky.bundle" extra = "-----BEGIN PRIVATE KEY-----\nROOTKEY\n-----END PRIVATE KEY-----\n" path.write_text(_fake_bundle_text() + extra, encoding="ascii") result = _installer_without_root("--cert-bundle", str(path)) assert result.returncode == 2, "the bundle refusal keeps its own exit code (2)" assert result.stdout == "", "a refusal goes to stderr, not stdout" assert reason in result.stderr assert str(path) in result.stderr, "the message names the file the operator passed" assert GRANT_PREFIX not in result.stderr, "a bad bundle is not a sudoers problem" def test_a_well_formed_bundle_passes_validation_and_reaches_the_root_check( tmp_path: Path, ) -> None: """Validation is shape-only and never dials: the next refusal is the usual sudoers one.""" path = tmp_path / "agent.bundle" path.write_text(_fake_bundle_text(), encoding="ascii") result = _installer_without_root("--bind", "10.0.0.5", "--cert-bundle", str(path)) assert result.returncode == 1 assert _grant_lines(result.stderr), "the bundle validated; what remains is the root boundary" def test_installer_help_documents_the_cert_bundle_and_its_path() -> None: result = _run([BASH, str(INSTALLER), "--help"]) assert result.returncode == 0 assert "--cert-bundle" in result.stdout assert CERT_FILE_PATH in result.stdout def test_the_bundle_lands_at_the_token_file_idiom_in_the_script() -> None: """`0640 root:$AGENT_USER` + chown: the agent opens the bundle itself and re-reads it.""" text = INSTALLER.read_text() assert f'CERT_FILE="{CERT_FILE_PATH}"' in text assert 'install -m 0640 "$BUNDLE_SRC" "$CERT_FILE"' in text assert 'chown root:"$AGENT_USER" "$CERT_FILE"' in text # 0600 would fail the unit the way it would fail the token file: the service user opens # the bundle at startup and re-reads the denylist live. Pin the reasoning, not just the mode. assert "0600 root-owned file would fail the unit" in text assert "re-reads it when its stamp changes" in text def test_the_tls_unit_lines_come_from_one_bundle_branch() -> None: """Flag + RuntimeDirectory are emitted together, or the unit is a token-mode unit.""" text = INSTALLER.read_text() match = re.search(r'\n if \[ -f "\$CERT_FILE" \]; then(.*?)\n fi\n', text, re.DOTALL) assert match, "the TLS unit lines must come from one `if [ -f \"$CERT_FILE\" ]` branch" block = match.group(1) assert 'TLS_EXEC_ARGS=" --cert-bundle $CERT_FILE"' in block assert "RuntimeDirectory=dashboards-agent" in block assert "${TLS_EXEC_ARGS}" in text, "ExecStart must carry the flag from that branch" def test_status_probes_tls_with_the_bundle_ca_and_no_client_cert() -> None: """The status branch: unit says --cert-bundle → python3 ssl probe, never openssl, no cert.""" status_body = INSTALLER.read_text().split("status() {", 1)[1].split("\n}", 1)[0] assert "grep -q -- '--cert-bundle'" in status_body assert "PROTOCOL_TLS_CLIENT" in status_body assert "load_verify_locations" in status_body assert "https://" in status_body assert "load_cert_chain" not in status_body, "the installer holds no client certificate" assert 'http://$BIND:$PORT/health' in status_body, "token mode keeps the plain probe" assert "openssl" not in INSTALLER.read_text(), "no new binary dependency on a monitored host" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_bundle_install_lands_the_file_the_unit_flag_and_the_runtime_directory( tmp_path: Path, ) -> None: """The install body with a bundle: byte-identical file at 0640, TLS unit, https hint.""" bundle = tmp_path / "agent.bundle" bundle.write_text(_fake_bundle_text(), encoding="ascii") script, call_log, env = _sandbox_installer(tmp_path) result = subprocess.run( [BASH, str(script), "--bind", "10.0.0.5", "--port", "9120", "--cert-bundle", str(bundle)], capture_output=True, text=True, env=env, timeout=60, check=False, ) assert result.returncode == 0, f"install failed:\n{result.stdout}\n{result.stderr}" cert_path = tmp_path / "target" / "certs" assert cert_path.is_file(), "install -m never landed the bundle" assert cert_path.read_text(encoding="ascii") == _fake_bundle_text(), ( "the installed bundle must be byte-identical to the downloaded one" ) mode = stat.S_IMODE(cert_path.stat().st_mode) assert mode == 0o640, f"bundle mode is {oct(mode)}, expected 0o640 (group = the agent user)" assert not mode & 0o007, "the bundle holds the agent's key: never world-readable" unit = (tmp_path / "target" / "dashboards-agent.service").read_text() assert f"--cert-bundle {cert_path}" in unit assert "RuntimeDirectory=dashboards-agent" in unit, ( "ProtectSystem=strict leaves the agent no writable file but this, and the bundle " "unpacks its PEM blocks there" ) assert "--token-file" in unit, "the dual-mode line stays — rollout is host by host" assert 'HOST_TARGETS="hostname=https://10.0.0.5:9120"' in result.stdout, ( "a TLS install points the dashboard at https, with no token in the URL" ) calls = call_log.read_text() assert calls.count("chown root:dashboards-agent") == 2, "token and bundle both chowned" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_an_unrelated_rerun_reuses_the_installed_bundle(tmp_path: Path) -> None: """The token-reuse idiom: a re-run without the flag neither rewrites nor drops the bundle.""" script, _call_log, env = _sandbox_installer(tmp_path) target = tmp_path / "target" target.mkdir(parents=True) cert_path = target / "certs" cert_path.write_text(_fake_bundle_text(revision="7"), encoding="ascii") result = subprocess.run( [BASH, str(script), "--bind", "10.0.0.5"], capture_output=True, text=True, env=env, timeout=60, check=False, ) assert result.returncode == 0, f"re-run failed:\n{result.stdout}\n{result.stderr}" assert cert_path.read_text(encoding="ascii") == _fake_bundle_text(revision="7") assert "reusing the installed certificate bundle" in result.stdout unit = (target / "dashboards-agent.service").read_text() assert f"--cert-bundle {cert_path}" in unit, "an installed bundle keeps the unit in TLS mode" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_uninstall_removes_the_bundle_with_the_token(tmp_path: Path) -> None: """An uninstalled agent leaves no private key behind.""" script, call_log, env = _sandbox_installer(tmp_path) target = tmp_path / "target" (target / "lib").mkdir(parents=True) leftovers = { target / "token": "tok\n", target / "certs": _fake_bundle_text(), target / "lib" / "host_agent.py": "# stub\n", target / "dashboards-agent.service": "[Unit]\n", } for path, body in leftovers.items(): path.write_text(body, encoding="ascii") result = subprocess.run( [BASH, str(script), "--uninstall"], capture_output=True, text=True, env=env, timeout=60, check=False, ) assert result.returncode == 0, f"uninstall failed:\n{result.stdout}\n{result.stderr}" for path in leftovers: assert not path.exists(), f"uninstall left {path} on disk" assert "systemctl disable --now dashboards-agent" in call_log.read_text() # --- the --status probe, end to end against the real TLS agent ----------------------------- class DictMeta: """The three-method `certs.MetaStore` slice, backed by a dict (the `test_auth.py` idiom).""" def __init__(self) -> None: self.rows: dict[str, str] = {} def get_meta(self, key: str) -> str | None: return self.rows.get(key) def set_meta(self, key: str, value: str) -> None: self.rows[key] = value def delete_meta(self, key: str) -> int: return 1 if self.rows.pop(key, None) is not None else 0 def _mint_bundle() -> str: """A real bundle from the dashboard's own CA — the artifact the operator downloads.""" store = DictMeta() state = certs.ensure_certs(store) state = certs.issue_agent_cert(store, state, "localhost", ["127.0.0.1", "localhost"]) entry = state.agent("localhost") assert entry is not None return entry.bundle def _serve_tls_agent(host_agent: Any, bundle_text: str, where: Path) -> tuple[Any, int]: """The real agent handler on a TLS-wrapped loopback port, exactly as the unit runs it.""" path = where / "installed.certs" path.write_text(bundle_text, encoding="ascii") bundle, error = host_agent.TlsBundle.load(str(path)) assert error is None and bundle is not None host_agent.Handler.collector = host_agent.Collector("/", smart=False) host_agent.Handler.token = None host_agent.Handler.bundle = bundle httpd = ThreadingHTTPServer(("127.0.0.1", 0), host_agent.Handler) httpd.socket = bundle.context.wrap_socket(httpd.socket, server_side=True) threading.Thread(target=httpd.serve_forever, daemon=True).start() return httpd, httpd.server_address[1] def _run_status(script: Path, port: int, env: dict[str, str]) -> str: result = subprocess.run( [BASH, str(script), "--status", "--bind", "127.0.0.1", "--port", str(port)], capture_output=True, text=True, env=env, timeout=60, check=False, ) assert result.returncode == 0, f"--status exited {result.returncode}:\n{result.stderr}" return result.stdout @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_tls_status_probe_reads_health_with_the_ca_and_no_client_cert( tmp_path: Path, host_agent_module: Any ) -> None: """The task-02 ASSUMPTION, proven through the installer: CERT_OPTIONAL + the /health exemption let a probe that holds only the bundle's CA read /health on a TLS agent.""" bundle = _mint_bundle() httpd, port = _serve_tls_agent(host_agent_module, bundle, tmp_path) try: script, _call_log, env = _sandbox_installer(tmp_path / "installer") target = tmp_path / "installer" / "target" target.mkdir(parents=True) (target / "dashboards-agent.service").write_text( f"ExecStart=python3 host_agent.py --cert-bundle {target / 'certs'}\n", encoding="ascii" ) (target / "certs").write_text(bundle, encoding="ascii") assert _installer_status(_run_status(script, port, env)) == "True" finally: httpd.shutdown() httpd.server_close() host_agent_module.Handler.bundle = None @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_tls_status_probe_verifies_the_chain_and_says_no_to_another_authority( tmp_path: Path, host_agent_module: Any ) -> None: """The probe loads the CA for a reason: a server signed by a different root is not health.""" httpd, port = _serve_tls_agent(host_agent_module, _mint_bundle(), tmp_path) try: script, _call_log, env = _sandbox_installer(tmp_path / "installer") target = tmp_path / "installer" / "target" target.mkdir(parents=True) (target / "dashboards-agent.service").write_text( f"ExecStart=python3 host_agent.py --cert-bundle {target / 'certs'}\n", encoding="ascii" ) (target / "certs").write_text(_mint_bundle(), encoding="ascii") # a *different* CA stdout = _run_status(script, port, env) finally: httpd.shutdown() httpd.server_close() host_agent_module.Handler.bundle = None line = _installer_status(stdout) assert line != "True" and line.startswith("(no answer"), f"the probe must refuse: {line}" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_tls_status_probe_names_the_bundle_when_it_cannot_read_it(tmp_path: Path) -> None: """The bundle is 0640 root:agent — an unprivileged --status cannot read it, and says so.""" script, _call_log, env = _sandbox_installer(tmp_path) target = tmp_path / "target" target.mkdir(parents=True) (target / "dashboards-agent.service").write_text( f"ExecStart=python3 host_agent.py --cert-bundle {target / 'certs'}\n", encoding="ascii" ) cert = target / "certs" cert.write_text(_fake_bundle_text(), encoding="ascii") cert.chmod(0o000) line = _installer_status(_run_status(script, 1, env)) assert "cannot read" in line and "run --status as root" in line, line @pytest.mark.skipif(shutil.which("curl") is None, reason="curl is not installed on this box") @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_token_mode_status_probe_stays_plain_http(tmp_path: Path) -> None: """The other branch: no bundle in the unit → the unchanged plain probe, not https.""" script, _call_log, env = _sandbox_installer(tmp_path) target = tmp_path / "target" target.mkdir(parents=True) (target / "dashboards-agent.service").write_text( "ExecStart=python3 host_agent.py --token-file /etc/dashboards-agent.token\n", encoding="ascii", ) result = subprocess.run( [BASH, str(script), "--status", "--bind", "127.0.0.1", "--port", "1"], capture_output=True, text=True, env=env, timeout=60, check=False, ) assert result.returncode == 0 assert _installer_status(result.stdout).startswith("(no answer"), ( "token mode must probe plain http — nothing listens here, and the refusal is the point" ) # --- scripts/dev-certs-export.py (phase 24 task 01) --------------------------------------- #: The dev exporter, and the dev server that calls it. Both are **run** below and **read** #: below: the bytes a host installs are the contract of the first, and the composition of the #: second (export *after* the readiness probe, failure *as* a warning) is the contract of the #: dev loop that has to stop producing `http://` agents. EXPORTER = ROOT / "scripts" / "dev-certs-export.py" DEV_SERVER = ROOT / "scripts" / "dev-server.sh" #: The invariant-3 pair, as two configured hosts: one with a bundle in `meta`, one without. ISSUED_HOST = "issuedhost" UNISSUED_HOST = "unissuedhost" def _load_exporter() -> Any: """The exporter as a module — it is a script, not a package member, so import it by path.""" spec = importlib.util.spec_from_file_location("dev_certs_export", EXPORTER) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module @contextmanager def served_dashboard(tmp_path: Path) -> Iterator[tuple[str, SampleStore]]: """A real `create_app(...)` on a real socket, with one host's bundle already issued. uvicorn in a thread rather than `TestClient`, because the unit under test is an **HTTP client**: the exporter signs in over the public route and reads the session token back out of a `Set-Cookie` header, and a transport-level test client can pass that without ever serialising a header. Port 0, so nothing here can collide with a port another test bound. The store is injected and closed here, and the two `HOST_TARGETS` entries point at a port nothing listens on: the app under test must never dial, and a dead target is what keeps this hermetic. Every other source is switched off explicitly, so an operator running the suite with `.env.dev` sourced still gets a test of two hosts and no fleet. """ db = tmp_path / "export.sqlite3" store = SampleStore(db) state = certs.ensure_certs(store) certs.issue_agent_cert(store, state, ISSUED_HOST, san=[ISSUED_HOST]) settings = Settings( db_path=str(db), admin_password=ADMIN_PASSWORD, pbkdf2_iterations=FAST_PBKDF2, host_targets=(Target(name=ISSUED_HOST, url="http://127.0.0.1:1"), Target(name=UNISSUED_HOST, url="http://127.0.0.1:1")), host_interval=3600, host_timeout=0.2, gufo_url="", vllm_url="", nut_host="", ) server = uvicorn.Server(uvicorn.Config(create_app(settings, store), host="127.0.0.1", port=0, log_level="error", lifespan="on")) thread = threading.Thread(target=server.run, daemon=True, name="dev-certs-export-test") thread.start() try: for _ in range(2_000): if server.started: break time.sleep(0.01) assert server.started, "the test dashboard never came up (see the captured stderr)" port = int(server.servers[0].sockets[0].getsockname()[1]) yield f"http://127.0.0.1:{port}", store finally: server.should_exit = True thread.join(timeout=60) store.close() def _export(base: str, out_dir: Path, names: list[str], *, password: str | None = ADMIN_PASSWORD) -> subprocess.CompletedProcess[str]: """Run the exporter exactly the way `dev-server.sh` does, as a subprocess.""" args = [sys.executable, str(EXPORTER), "--url", base, "--out-dir", str(out_dir)] if password is not None: args += ["--password", password] if names: args += ["--names", *names] return _run(args) def _served_bundle(base: str, name: str) -> bytes: """What `GET /api/certs//bundle` answers, fetched the way the exporter fetches it.""" with httpx.Client(base_url=base, timeout=10.0) as client: login = client.post("/api/auth/login", json={"password": ADMIN_PASSWORD}) login.raise_for_status() token = login.cookies[auth.SESSION_COOKIE] answer = client.get(f"/api/certs/{name}/bundle", headers={"authorization": f"Bearer {token}"}) answer.raise_for_status() return answer.content def test_the_export_writes_the_issued_bundle_byte_identical_at_0600(tmp_path: Path) -> None: """The task's headline: the file is the stored bundle, at a mode that keeps it private. Byte-identity is compared against the **route** rather than the `meta` row because the question this answers is "is the file I am about to `scp` the file the dashboard issued?", and a bundle that re-signed between the two would make that question unanswerable. """ with served_dashboard(tmp_path) as (base, _store): out = tmp_path / "certs" result = _export(base, out, [ISSUED_HOST]) assert result.returncode == 0, result.stderr path = out / f"{ISSUED_HOST}.bundle" assert path.is_file(), f"no bundle exported; stdout was:\n{result.stdout}" assert stat.S_IMODE(path.stat().st_mode) == 0o600, ( "a bundle carries a private key; 0600 is its birth mode and stays its mode" ) assert path.read_bytes() == _served_bundle(base, ISSUED_HOST) assert ISSUED_HOST in result.stdout and "0600" in result.stdout def test_an_unissued_host_writes_no_file_and_names_the_certs_route(tmp_path: Path) -> None: """`issued: false` is a fact, not a failure: one line, no file, exit 0 (invariant 3).""" with served_dashboard(tmp_path) as (base, _store): out = tmp_path / "certs" result = _export(base, out, [ISSUED_HOST, UNISSUED_HOST]) assert result.returncode == 0, result.stderr assert not (out / f"{UNISSUED_HOST}.bundle").exists() note = next(line for line in result.stdout.splitlines() if UNISSUED_HOST in line) assert "no bundle issued" in note and "/certs" in note, note assert "!" not in note, f"an unissued host must not read as a failure: {note}" def test_a_name_outside_host_targets_is_a_note_and_not_a_crash(tmp_path: Path) -> None: """The `{"configured": false}` arm: impossible from `HOST_TARGETS`, still never a crash.""" with served_dashboard(tmp_path) as (base, _store): out = tmp_path / "certs" result = _export(base, out, ["nosuchhost"]) assert result.returncode == 0, result.stderr assert not out.exists(), "nothing was issued, so nothing — not even a directory" assert "HOST_TARGETS" in result.stdout def test_a_wrong_password_exits_non_zero_and_writes_nothing(tmp_path: Path) -> None: """A dev server that cannot be signed in to says so loudly (and the CA stays untouched).""" with served_dashboard(tmp_path) as (base, _store): out = tmp_path / "certs" result = _export(base, out, [ISSUED_HOST], password="definitely-not-the-password") assert result.returncode == 2, f"a sign-in failure must not look like success: {result}" assert not out.exists() assert "sign-in" in result.stderr.lower() def test_no_password_at_all_is_a_refusal_and_not_a_hang(tmp_path: Path) -> None: """`--password` is optional only because `$DASH_ADMIN_PASSWORD` is the dev source of it.""" env = {key: value for key, value in os.environ.items() if key != "DASH_ADMIN_PASSWORD"} result = subprocess.run( [sys.executable, str(EXPORTER), "--url", "http://127.0.0.1:1", "--out-dir", str(tmp_path / "certs"), "--names", ISSUED_HOST], capture_output=True, text=True, check=False, env=env, timeout=60, ) assert result.returncode == 2 assert "DASH_ADMIN_PASSWORD" in result.stderr assert not (tmp_path / "certs").exists() def test_no_names_at_all_is_a_quiet_zero(tmp_path: Path) -> None: """A dashboard with no `HOST_TARGETS` has nothing to export and is not broken.""" result = _export("http://127.0.0.1:1", tmp_path / "certs", []) assert result.returncode == 0, result.stderr assert "nothing to export" in result.stdout assert not (tmp_path / "certs").exists() def test_a_second_export_is_idempotent_and_says_unchanged(tmp_path: Path) -> None: """`restart` runs this every time; a dev loop that rewrote keys on every boot would be noise.""" with served_dashboard(tmp_path) as (base, _store): out = tmp_path / "certs" first = _export(base, out, [ISSUED_HOST]) assert first.returncode == 0, first.stderr path = out / f"{ISSUED_HOST}.bundle" before = path.read_bytes() path.chmod(0o644) # somebody loosened it; the export is what puts the mode back second = _export(base, out, [ISSUED_HOST]) assert second.returncode == 0, second.stderr assert path.read_bytes() == before, "the stored bundle must not move between exports" assert stat.S_IMODE(path.stat().st_mode) == 0o600, "an existing file is re-chmodded" assert "unchanged" in second.stdout def test_the_export_reads_the_authority_and_never_mints_anything(tmp_path: Path) -> None: """The difference between `read_state` and `ensure_certs`, pinned as an observable. "The dev server should generate certs" is the TODO's wording; the safe reading is *export what the first boot minted*. If this script ever issued, a stray `restart` would rotate a fleet's identities — so the serials, the revisions and the root all have to be the same bytes after the run as before it. """ with served_dashboard(tmp_path) as (base, store): def fingerprint() -> tuple[str, ...]: listed = certs.read_state(store) rows = tuple(f"{e.name}:{e.serial}:{e.bundle_rev}:{e.status}" for e in certs.read_agents(store)) return (f"root:{listed.root_serial}", *rows) before = fingerprint() assert _export(base, tmp_path / "certs", [ISSUED_HOST, UNISSUED_HOST, "nosuchhost"]).returncode == 0 assert fingerprint() == before, "the exporter changed the certificate authority" def test_the_export_prints_no_key_material_and_no_password(tmp_path: Path) -> None: """Invariant 6, on the one path that holds both a session token and a private key.""" with served_dashboard(tmp_path) as (base, _store): result = _export(base, tmp_path / "certs", [ISSUED_HOST, UNISSUED_HOST]) printed = result.stdout + result.stderr assert "BEGIN" not in printed, "a PEM block reached the terminal (and the dev log)" assert ADMIN_PASSWORD not in printed assert auth.SESSION_COOKIE not in printed, "the token itself must never be printed" def test_the_exporter_splits_names_the_way_host_targets_looks() -> None: """One argument with spaces in it, three arguments, commas: all the same list.""" module = _load_exporter() assert module.split_names(["alpha beta", "gamma,delta"]) == ["alpha", "beta", "gamma", "delta"] assert module.split_names([" alpha,,beta "]) == ["alpha", "beta"] assert module.split_names([]) == [] assert module.split_names(None) == [] def test_the_exporter_reads_the_cookie_the_app_sets() -> None: """`auth.SESSION_COOKIE` is imported, not restated — a rename must not silently break dev.""" module = _load_exporter() assert module.SESSION_COOKIE == auth.SESSION_COOKIE def test_write_bundle_refuses_a_name_that_would_leave_the_directory(tmp_path: Path) -> None: """The route gates names long before this, but this is the function that picks a path.""" module = _load_exporter() for bad in ("../escape", "nested/x", "/absolute", "", "."): with pytest.raises(module.ExportError): module.write_bundle(tmp_path, bad, b"secret") assert list(tmp_path.iterdir()) == [] def test_the_bundles_only_ever_land_under_the_gitignored_data_tree() -> None: """No key, bundle or PEM outside `data/`: the exporter's default and the caller's flag.""" module = _load_exporter() assert module.DEFAULT_OUT_DIR == ROOT / "data" / "certs" gitignore = (ROOT / ".gitignore").read_text() assert re.search(r"^data/$", gitignore, re.MULTILINE), "data/ is the gitignored tree" dev_server = DEV_SERVER.read_text() assignments = [line for line in dev_server.splitlines() if line.startswith("CERTDIR=")] assert assignments == ['CERTDIR="$ROOT/data/certs"'], ( f"the dev server must name exactly one export dir, under data/: {assignments}" ) assert '--out-dir "$CERTDIR"' in dev_server, "the export must be told where it writes" def _shell_function(text: str, name: str) -> str: """One function's body, so a pin can talk about *that* block and not the whole file.""" match = re.search(rf"^{re.escape(name)}\(\) \{{$", text, re.MULTILINE) assert match is not None, f"{name}() is no longer a plain function in dev-server.sh" body = text[match.end():] end = body.index("\n}") return body[:end] def test_dev_server_exports_the_bundles_after_the_readiness_probe() -> None: """The order is the whole safety of the dev loop: no bundle export against a dead server.""" text = DEV_SERVER.read_text() probe = text.index("curl -sf -o /dev/null") call = text.index('export_bundles "$PORT"') assert probe < call, "the export must run only once /api/health has answered" assert "dev-certs-export.py" in _shell_function(text, "export_bundles") body = _shell_function(text, "export_bundles") assert "http://127.0.0.1:${port}" in body, "the exporter dials the dev server and nothing else" # The names are HOST_TARGETS' names, split by the caller: the exporter re-parses no config. assert "${entry%%=*}" in body def test_a_failed_bundle_export_never_fails_the_dev_server_start() -> None: """A dev-convenience failure is a warning line: the readiness probe already proved the app.""" body = _shell_function(DEV_SERVER.read_text(), "export_bundles") assert "|| echo" in body, "a failed export must print a warning rather than propagate" for escape in ("return 1", "exit 1", "set -e"): assert escape not in body, f"the export can now abort `start` ({escape})" def test_dev_server_status_prints_the_exported_bundles_block() -> None: """`status` answers "is this host's bundle there yet?" without a signed-in curl.""" text = DEV_SERVER.read_text() body = _shell_function(text, "bundles") assert "bundles exported" in body and ".bundle" in body assert "mode" in body, "the block shows the 0600, which is the thing worth checking" assert re.search(r"^ bundles ;;$", text, re.MULTILINE), "`status` must print the block" assert "bundles)" in text, "`bundles` is a subcommand of its own" def test_a_dashboard_that_never_answers_is_named_rather_than_guessed(tmp_path: Path) -> None: """Nothing listened: the line says which URL, the exit is non-zero, nothing is written.""" result = _export("http://127.0.0.1:1", tmp_path / "certs", [ISSUED_HOST]) assert result.returncode == 2 assert "no dashboard answered" in result.stderr and "127.0.0.1:1" in result.stderr assert not (tmp_path / "certs").exists() def test_an_unexpected_answer_on_the_bundle_route_is_not_read_as_unissued(tmp_path: Path) -> None: """Only the two 404 shapes are notes. A 401 means the caller is not authorised, which is news.""" with served_dashboard(tmp_path) as (base, _store): module = _load_exporter() with pytest.raises(module.ExportError) as raised: module.fetch_bundle(base, ISSUED_HOST, "not-a-session-token") assert "401" in str(raised.value) def test_one_host_the_route_refuses_never_stops_the_others(tmp_path: Any, monkeypatch: pytest.MonkeyPatch) -> None: """The aggregation `main` owns: every name is attempted, and a failure moves the exit code.""" module = _load_exporter() attempted: list[str] = [] def fake_fetch(base: str, name: str, token: str) -> bytes | str: attempted.append(name) if name == "bad": raise module.ExportError("bad: GET /api/certs/bad/bundle answered 500: boom") return b"bundle-bytes" monkeypatch.setattr(module, "sign_in", lambda base, password: "a-token") monkeypatch.setattr(module, "fetch_bundle", fake_fetch) code = module.main(["--url", "http://127.0.0.1:1", "--password", "p", "--names", "good bad later", "--out-dir", str(tmp_path / "certs")]) assert attempted == ["good", "bad", "later"], "one refused host must not end the run" assert code == 1, "a bundle nobody exported is a host that stays on http:// — say so" assert (tmp_path / "certs" / "good.bundle").is_file() assert not (tmp_path / "certs" / "bad.bundle").exists() # --- fetching the bundle during registration (phase 24 task 02) --------------------------- #: `install-agent.sh --fetch-bundle --cert-name --session `: the install #: run pulls its own bundle from the dashboard, so a host is never registered onto a token URL #: because an operator forgot a `curl`. The contract has three parts — the bytes that land are #: the bytes the route serves, a refusal writes *nothing*, and the session token is a fetch #: credential that appears in no artefact the run leaves behind. FETCH_UNISSUED = "no bundle issued for" FETCH_ALL_OR_NOTHING = "--fetch-bundle, --cert-name and --session go together" FETCH_TWO_SOURCES = "two bundle sources" FETCH_MODE_REFUSAL = "--fetch-bundle needs an install" def _sign_in(base: str) -> str: """A real session token for the served dashboard — the credential `--session` carries.""" with httpx.Client(base_url=base, timeout=10.0) as client: login = client.post("/api/auth/login", json={"password": ADMIN_PASSWORD}) login.raise_for_status() return login.cookies[auth.SESSION_COOKIE] def _fetch_run(tmp_path: Path, *flags: str, ) -> tuple[subprocess.CompletedProcess[str], Path, Path]: """One sandboxed install with a sandbox `TMPDIR`, so the fetched temp file is observable. The fetch writes to `${TMPDIR:-/tmp}`, so pointing TMPDIR into the sandbox is what turns "the fetched private key never stays behind" into an assertion instead of a hope — and it catches the temp file on the refusal paths too, where nothing else was written to look at. """ script, call_log, env = _sandbox_installer(tmp_path) tmpdir = tmp_path / "tmp" tmpdir.mkdir(parents=True, exist_ok=True) result = _install(script, {**env, "TMPDIR": str(tmpdir)}, *flags) return result, tmpdir, call_log def _fetch(base: str, name: str, token: str) -> list[str]: """The three fetch flags, spelled once.""" return ["--fetch-bundle", base, "--cert-name", name, "--session", token] @contextmanager def served_body(*, status: int = 200, body: bytes = b"wrong", media_type: str = "text/html") -> Iterator[str]: """A loopback port that answers every GET with one canned body. Only for the shapes the real route cannot produce: a proxy's 200 error page, a 200 with no bytes at all. The issued host, the unissued host and a refused session are all real answers from `served_dashboard`, and those are the legs that matter most. """ class Handler(BaseHTTPRequestHandler): def do_GET(self) -> None: self.send_response(status) self.send_header("content-type", media_type) self.send_header("content-length", str(len(body))) self.end_headers() if body: self.wfile.write(body) def log_message(self, *_args: Any, **_kwargs: Any) -> None: pass # the installer's answer is the assertion, not this server's access log httpd = ThreadingHTTPServer(("127.0.0.1", 0), Handler) threading.Thread(target=httpd.serve_forever, daemon=True).start() try: yield f"http://127.0.0.1:{httpd.server_address[1]}" finally: httpd.shutdown() httpd.server_close() @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_fetched_bundle_installs_byte_identical_at_0640(tmp_path: Path) -> None: """The registration leg: fetch, validate, install, TLS unit — one run, no hand-carried file. Byte-identity is against the same route's answer as task 01's export, because the claim the installer makes is the same one: the file on the host *is* the row in `meta`. Everything after the fetch is the existing `--cert-bundle` path, so the mode, the unit's TLS lines and the https hint are asserted here as unchanged rather than as new. """ with served_dashboard(tmp_path) as (base, _store): token = _sign_in(base) result, tmpdir, call_log = _fetch_run( tmp_path, "--bind", "10.0.0.5", "--port", "9120", *_fetch(base, ISSUED_HOST, token)) if result.returncode != 0: pytest.fail(f"the fetch install failed:\n{result.stdout}\n{result.stderr}") cert_path = tmp_path / "target" / "certs" assert cert_path.read_bytes() == _served_bundle(base, ISSUED_HOST) mode = stat.S_IMODE(cert_path.stat().st_mode) assert mode == 0o640, f"bundle mode is {oct(mode)}, expected 0o640 (group = agent user)" unit = (tmp_path / "target" / "dashboards-agent.service").read_text() assert f"--cert-bundle {cert_path}" in unit assert "RuntimeDirectory=dashboards-agent" in unit assert 'HOST_TARGETS="hostname=https://10.0.0.5:9120"' in result.stdout assert f"fetched the certificate bundle for {ISSUED_HOST} from {base}" in result.stdout assert "chown root:dashboards-agent" in call_log.read_text() assert list(tmpdir.iterdir()) == [], "the fetched private key outlived the install" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_an_unissued_bundle_is_refused_before_anything_is_written(tmp_path: Path) -> None: """`issued: false` is one sentence naming the fix, and the host stays exactly as it was. This is the invariant-3 answer on the install path: the route says "configured, nothing issued", and the installer must not read that as a bundle, as a sudoers problem, or as a reason to create a service user. The stub log is the proof nothing privileged ran: `id -u` is the first privileged call a real install makes, and it is absent. """ with served_dashboard(tmp_path) as (base, _store): token = _sign_in(base) result, tmpdir, call_log = _fetch_run( tmp_path, "--bind", "10.0.0.5", *_fetch(base, UNISSUED_HOST, token)) assert result.returncode == 2, f"a refused fetch keeps exit 2: {result.returncode}" assert result.stdout == "", "a refusal is a failure: nothing on stdout" assert FETCH_UNISSUED in result.stderr and UNISSUED_HOST in result.stderr assert "/certs" in result.stderr, "the sentence names where the certificate is issued" assert GRANT_PREFIX not in result.stderr, "a refusal is not a sudoers problem" assert not call_log.exists(), "refused before the root boundary: no privileged command ran" assert not (tmp_path / "target").exists(), "a refused fetch writes no files at all" assert list(tmpdir.iterdir()) == [], "the temp file is gone even though it was written" @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_name_the_dashboard_does_not_dial_is_refused_with_its_own_404(tmp_path: Path) -> None: """`{"configured": false}` is a third fact, and it is not read as "nothing issued". The route refuses a name outside `HOST_TARGETS` before any signing, so a typo in `--cert-name` must say *that* — an operator who is told "no bundle issued" would go looking for a certificate that was never supposed to exist for a host this dashboard does not dial. """ with served_dashboard(tmp_path) as (base, _store): token = _sign_in(base) result, tmpdir, call_log = _fetch_run( tmp_path, "--bind", "10.0.0.5", *_fetch(base, "nosuchhost", token)) assert result.returncode == 2 assert "404" in result.stderr, result.stderr assert FETCH_UNISSUED not in result.stderr, "not configured is not the same as unissued" assert "HOST_TARGETS" in result.stderr, "the dashboard's own sentence is echoed" assert not call_log.exists() assert list(tmpdir.iterdir()) == [] @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_refused_session_names_the_http_status(tmp_path: Path) -> None: """A stale or wrong session is a 401 on the wire, and the run says 401. Not "no bundle" and not a sudoers hint: the operator's move is a fresh sign-in, and the only way they learn that is the status the dashboard actually answered. """ with served_dashboard(tmp_path) as (base, _store): result, tmpdir, call_log = _fetch_run( tmp_path, "--bind", "10.0.0.5", *_fetch(base, ISSUED_HOST, "not-a-session-token")) assert result.returncode == 2 assert "401" in result.stderr, result.stderr assert "sign in required" in result.stderr, "the dashboard's own sentence is echoed" assert not call_log.exists() assert not (tmp_path / "target").exists() assert list(tmpdir.iterdir()) == [] @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") @pytest.mark.parametrize( ("status", "body", "reason"), [ (200, b"502 from the proxy", "# BUNDLE-REV"), (200, b"", "empty body"), (500, b"upstream exploded", "500"), ], ids=["proxy-error-page", "empty-200", "server-error"], ) def test_a_body_that_is_not_a_bundle_never_becomes_an_install( tmp_path: Path, status: int, body: bytes, reason: str ) -> None: """The shapes the dashboard cannot answer with — and the check that catches each one. A 200 with a proxy's HTML page is the dangerous one: it is a *success* to any client that only looks at the status, and installing it produces a unit that exits at startup. It falls through to the existing shape check, which is the reason one validator serves both bundle sources. The refusal names `--fetch-bundle` (the flag that was used), not the temp file. """ with served_body(status=status, body=body) as base: result, tmpdir, call_log = _fetch_run( tmp_path, "--bind", "10.0.0.5", *_fetch(base, ISSUED_HOST, "a-token")) assert result.returncode == 2, f"{status}/{len(body)} bytes must be refused" assert reason in result.stderr, result.stderr assert "--fetch-bundle" in result.stderr assert not call_log.exists() assert not (tmp_path / "target").exists() assert list(tmpdir.iterdir()) == [] @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_dashboard_that_never_answers_stops_the_fetch(tmp_path: Path) -> None: """Nothing listening on the dashboard URL: the line names it, and no install happens.""" result, tmpdir, call_log = _fetch_run( tmp_path, "--bind", "10.0.0.5", *_fetch("http://127.0.0.1:1", ISSUED_HOST, "a-token")) assert result.returncode == 2 assert "no dashboard answered" in result.stderr and "127.0.0.1:1" in result.stderr assert not call_log.exists() assert list(tmpdir.iterdir()) == [] @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_session_token_appears_in_no_written_artefact(tmp_path: Path) -> None: """Completion criterion: the token is a fetch credential, not a stored one. The test signs in for a real token and then searches **every** file the run could have touched plus both output streams. The agent's own bearer token is expected in the token file and on stdout — that is a different secret, deliberately printed once — so the pin is specifically that this session value is nowhere. """ with served_dashboard(tmp_path) as (base, _store): token = _sign_in(base) assert token and token != ADMIN_PASSWORD result, _tmpdir, call_log = _fetch_run( tmp_path, "--bind", "10.0.0.5", "--port", "9120", *_fetch(base, ISSUED_HOST, token)) assert result.returncode == 0, f"install failed:\n{result.stderr}" where = tmp_path / "target" assert token not in (where / "token").read_text(), "the token file is the agent's, not ours" assert token not in (where / "certs").read_text(), "a bundle is not a place for a session" assert token not in (where / "dashboards-agent.service").read_text() assert token not in call_log.read_text(), "a privileged call must not carry it either" assert token not in result.stdout + result.stderr hits = [str(path.relative_to(tmp_path)) for path in tmp_path.rglob("*") if path.is_file() and token in path.read_text(encoding="utf-8", errors="replace")] assert hits == [], f"the session token was written to {hits}" @pytest.mark.parametrize( "flags", [ ["--fetch-bundle", "http://127.0.0.1:8090"], ["--cert-name", "deskwork"], ["--session", "a-token"], ["--fetch-bundle", "http://127.0.0.1:8090", "--cert-name", "deskwork"], ["--cert-name", "deskwork", "--session", "a-token"], ], ids=["url", "name", "token", "url+name", "name+token"], ) @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_fetch_flags_are_all_or_nothing(tmp_path: Path, flags: list[str]) -> None: """Half a fetch is a refusal, not a guess: no URL, no name, or no token means nothing dials. A URL without a name has no route to fetch, and a name without a token is a 401 the operator would have to diagnose from a stack of unrelated output. Both are answered with one sentence before the root boundary and before any write — the `--caps` reasoning again. """ script, call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, "--bind", "127.0.0.1", "--port", "9199", *flags) assert result.returncode == 1, "a flag mistake keeps exit 1, not the fetch's 2" assert result.stdout == "" assert FETCH_ALL_OR_NOTHING in result.stderr assert GRANT_PREFIX not in result.stderr, "a flag mistake is not a sudoers problem" assert not call_log.exists(), "refused before a single privileged command" assert not (tmp_path / "target").exists() @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_two_bundle_sources_are_refused_rather_than_ordered(tmp_path: Path) -> None: """`--fetch-bundle` beside `--cert-bundle` is a contradiction: one install, one answer. "Which one wins" has no good answer here — a stale file and a fresh bundle are two different identities, and silently preferring either would install a certificate the operator did not mean to install. So the run stops. """ bundle = tmp_path / "agent.bundle" bundle.write_text(_fake_bundle_text(), encoding="ascii") script, call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, "--bind", "127.0.0.1", "--port", "9199", *_fetch("http://127.0.0.1:8090", "deskwork", "a-token"), "--cert-bundle", str(bundle)) assert result.returncode == 1 assert FETCH_TWO_SOURCES in result.stderr assert GRANT_PREFIX not in result.stderr assert not call_log.exists() assert not (tmp_path / "target").exists() @pytest.mark.parametrize("mode", ["--status", "--uninstall"], ids=["status", "uninstall"]) @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_fetching_a_bundle_is_refused_outside_an_install(tmp_path: Path, mode: str) -> None: """The fetch belongs to the run that installs, or a key is fetched to be thrown away. `--status` answers about a port and `--uninstall` removes files; neither has a use for the bytes, and a silent "flags ignored" on the uninstall path would leave an operator believing a bundle had been refreshed. """ script, call_log, env = _sandbox_installer(tmp_path) result = _install(script, env, mode, *_fetch("http://127.0.0.1:8090", "deskwork", "a-token")) assert result.returncode == 1 assert FETCH_MODE_REFUSAL in result.stderr assert not call_log.exists(), "and refused before the mode's own privileged work" def test_help_teaches_the_registration_fetch() -> None: """The three flags and the recipe are learnable from `--help`, like every other flag here.""" stdout = _run([BASH, str(INSTALLER), "--help"]).stdout assert "--fetch-bundle URL" in stdout assert "--cert-name NAME" in stdout assert "--session TOKEN" in stdout assert "GET /api/certs//bundle" in stdout assert "fetch credential only" in stdout, "the token's scope has to be learnable, not folklore" recipe = "--fetch-bundle https://dashboards.example.com --cert-name " assert recipe in stdout.replace("\\\n", " ").replace(" ", " "), ( "the recipe line must be copy-pasteable from --help" ) @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_the_sudoers_hint_never_repeats_the_session_token(tmp_path: Path) -> None: """The hint quotes a command, so it quotes only the flags that are safe to re-type. `need_root` ends with `sudo …` so the operator can copy it. Handing that string the real argv would print a bearer credential into the terminal, the ssh log and whoever's scrollback — and the fetch runs *before* the root boundary, so this is the one path where a good fetch and a refusal share a run. The fetch succeeding here is also the proof of the order: an unprivileged run can only reach the hint after fetching. """ with served_dashboard(tmp_path) as (base, _store): token = _sign_in(base) result = _installer_without_root("--bind", "10.0.0.5", "--port", "9120", *_fetch(base, ISSUED_HOST, token)) assert result.returncode == 1 assert _grant_lines(result.stderr), "the fetch succeeded, so what remains is the grant" assert f"fetched the certificate bundle for {ISSUED_HOST}" in result.stdout assert token not in result.stdout + result.stderr assert "--session" not in result.stderr, "the echoed command must not name the flag either" def test_the_session_token_is_confined_to_the_fetch() -> None: """Structural pin: the install body — everything that writes — cannot see the token. `unset SESSION_TOKEN` is the promise; this is the proof that the promise is enforceable rather than aspirational. Every file the installer writes is written inside `do_install` (the token file, the bundle, the unit), so if the name does not appear anywhere in that function or in the unit template, no artefact can carry it — the same style of pin `tests/test_auth.py` and `tests/test_certs.py` hold over the two modules that hold secrets. """ text = INSTALLER.read_text() assert 'unset SESSION_TOKEN' in text, "the fetch must forget the credential when it is done" assert 'FETCH_SESSION="$SESSION_TOKEN" python3' in text, ( "the token reaches python in the environment: argv is world-readable through `ps`, so a" " second copy on a command line would widen the exposure instead of limiting it" ) body = text.split("do_install() {", 1)[1] assert "SESSION_TOKEN" not in body, "nothing that writes may read the fetch credential" assert text.count("SESSION_TOKEN") <= 8, ( "the name belongs to the parser, the all-or-nothing check and the fetch; every further" " mention is another place a bearer token could be printed or written" ) def test_the_fetch_and_its_shape_check_both_run_before_the_root_boundary() -> None: """Text pin for the order the no-partial-install promise depends on. Behaviourally, a refusal as `ops` proves the refusal comes first; this proves the *shape* check is not quietly moved after `need_root` (where a bad fetched bundle would produce a sudoers hint instead of a reason, on the host where the operator can least afford it). """ body = INSTALLER.read_text().split("do_install() {", 1)[1] fetch = body.index("fetch_bundle\n") shape = body.index('validate_bundle "$BUNDLE_SRC"') root = body.index("need_root --bind") assert fetch < shape < root, "fetch, then shape, then root — in that order" assert 'trap cleanup_fetch EXIT' in INSTALLER.read_text(), ( "the fetched key is cleaned up on every exit path, not only the happy one" ) @pytest.mark.skipif(hasattr(os, "geteuid") and os.geteuid() == 0, reason="running as root") def test_a_local_bundle_still_validates_with_its_own_flag_name(tmp_path: Path) -> None: """The validator grew a label, and the pre-existing path must read exactly as before. `--fetch-bundle` needed the refusal to name the flag that was used; the `--cert-bundle` path must not start naming a flag nobody passed. That is the whole change, so this is the whole pin — the file-local behaviour around it is covered by the phase-15 tests. """ path = tmp_path / "revless.bundle" path.write_text(_fake_bundle_text().replace("# BUNDLE-REV 3\n", ""), encoding="ascii") result = _installer_without_root("--cert-bundle", str(path)) assert result.returncode == 2 assert result.stderr.startswith("--cert-bundle"), result.stderr assert "--fetch-bundle" not in result.stderr # --- the gateway capture (phase 28 task 04) ------------------------------------------------ #: The script that records `tests/fixtures/gateway_*` — the ground truth the agent's `haproxy`, #: `nginx` and `sockets` parsers are pinned to. It has no Python-side coverage to speak of (the #: project's coverage source is `src/dashboards`), so the pins here **run it**, against a fake #: remote reached through a stubbed `ssh`, the way the installer sandbox stubs `install`. CAPTURE = SCRIPTS / "capture-gateway-config.sh" #: The remote documents, and the fixture each one becomes. A new include directory on the gateway #: means a new entry here *and* a new `case` in the script — the script refuses silently-unknown #: globs for exactly this reason. CAPTURE_TARGETS = ( "/etc/haproxy/haproxy.cfg", "/etc/nginx/nginx.conf", "/etc/nginx/conf.d/*.conf", "/etc/nginx/stream.d/*.conf", "/proc/net/tcp", "/proc/net/tcp6", ) #: The stub `ssh`. It drops the options the script passes, records every call, and answers `cat` #: and `ls` from `$FAKE_REMOTE` as if that directory were the remote filesystem's `/`. Nothing #: here can reach a network: the point is to exercise the script's own branches (the include #: globs, the refusal rule, the socket-table edit) with no gateway in sight. CAPTURE_SSH_STUB = """#!/bin/sh printf '%s\\n' "$*" >> "$STUB_LOG" while [ $# -gt 0 ]; do case "$1" in -o) shift 2 ;; -n) shift ;; *) break ;; esac done host="$1"; shift command="$*" if [ "${FAIL_SSH:-0}" = "1" ]; then echo "ssh: connect to host $host port 22: Connection refused" >&2 exit 255 fi case "$command" in "cat "*) path="${command#cat }" file="$FAKE_REMOTE$path" [ -f "$file" ] || { echo "cat: $path: No such file or directory" >&2; exit 1; } cat "$file" ;; "ls "*) spec="${command#ls }" dir="${spec%/*}" glob="${spec##*/}" found=0 for file in "$FAKE_REMOTE$dir"/$glob; do [ -e "$file" ] || continue found=1 printf '%s\\n' "${file#"$FAKE_REMOTE"}" done [ "$found" = 1 ] || { echo "ls: cannot access '$spec': No such file or directory" >&2; exit 2; } ;; *) echo "stub ssh: unsupported remote command: $command" >&2 exit 2 ;; esac """ #: A gateway tree small enough to assert on whole, with the two shapes the parsers exist for: a #: `use_backend` whose target is a fetch expression, and a stream `proxy_pass` of a map variable. CAPTURE_FAKE_GATEWAY: dict[str, str] = { "/etc/haproxy/haproxy.cfg": ( "global\n maxconn 4096\n" "defaults\n mode tcp\n" "frontend haproxy-22\n bind :::22 v4v6\n default_backend gitea-2222\n" "frontend haproxy-8443\n bind :::8443 v4v6\n" " use_backend %[req.hdr(host),lower,map_dom(/etc/haproxy/maps,b0)\n" "backend gitea-2222\n server gitea gitea.reeselink.com:2222 check inter 5s\n" ), "/etc/nginx/nginx.conf": ( "user nginx;\n" "stream {\n include /etc/nginx/stream.d/*.conf;\n}\n" "http {\n" " include /etc/nginx/mime.types;\n" " server { listen 80; listen [::]:80; server_name _; return 301 https://$host; }\n" " include /etc/nginx/conf.d/*.conf;\n}\n" ), "/etc/nginx/conf.d/http.conf": ( "server {\n" " listen 4443 ssl proxy_protocol;\n" " server_name nextcloud.reeseapps.com;\n" " ssl_certificate_key /etc/letsencrypt/live/nextcloud.reeseapps.com/privkey.pem;\n" " location / { proxy_pass http://nextcloud.reeselink.com:11000; }\n}\n" ), "/etc/nginx/conf.d/tls.conf": ( "server {\n listen 8443 ssl;\n server_name brain.reeseapps.com;\n" " location / { proxy_pass http://reeseapps-brain-of-reese.reeselink.com:443; }\n}\n" ), "/etc/nginx/stream.d/stream.conf": ( "map $ssl_preread_server_name $targetBackend {\n" " default 127.0.0.1:4443;\n" " brain.example reeseapps-brain-of-reese.reeselink.com:443;\n}\n" "server {\n listen 443;\n proxy_pass $targetBackend;\n}\n" ), # 8.8.8.8 and 2603:6013::1 are globally routable, `::ffff:8.8.8.8` is a visitor behind a v6 # socket, and 203.0.113.7 is already documentation space. The tx/rx queue pair on the last row # is spelled like an address column and is not one. "/proc/net/tcp": ( " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid" " timeout inode\n" " 0: 00000000:1EFF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0" " 0 1 1 0000000000000000 100 0 0 10 0\n" " 1: 1B01010A:01BB 08080808:911A 01 00000000:00000000 00:00000000 00000000 101" " 0 2 1 00000000d22dedba 20 4 28 10 -1\n" " 2: 0100007F:23A0 017100CB:C0A8 06 08080808:08080808 00:00000000 00000000 0" " 0 3 3 0000000000000000 20 0 0 10 0\n" ), "/proc/net/tcp6": ( " sl local_address remote_address" " st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n" " 0: 00000000000000000000000000000000:01BB 00000000000000000000000000000000:0000 0A" " 00000000:00000000 00:00000000 00000000 0 0 4 1 0000000000000000 100 0 0 10 0\n" " 1: 1360032601010031FF005450E0B284FE:01BB 0000000000000000FFFF000008080808:C0AC 01" " 00000000:00000000 00:00000000 00000000 101 0 5 1 0000000000000000 63 4 26 10 -1\n" ), } def _capture_sandbox( tmp_path: Path, gateway: dict[str, str] | None = None, *, recorded: dict[str, str] | None = None, fail_ssh: bool = False, alias: str | None = None, ) -> tuple[Path, Path, Path, dict[str, str]]: """A copy of the capture script, a fake gateway, and a PATH whose `ssh` reaches only the fake. Returns (script, fixtures dir, stub call log, env). The script is copied into `/scripts/` because it derives `tests/fixtures/` from its own location, which is what keeps the repository's fixtures out of reach of a test. """ script = tmp_path / "scripts" / CAPTURE.name script.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(CAPTURE, script) fake = tmp_path / "gateway" for path, body in {**CAPTURE_FAKE_GATEWAY, **(gateway or {})}.items(): target = fake / path.lstrip("/") target.parent.mkdir(parents=True, exist_ok=True) target.write_text(body, encoding="utf-8") fixtures = tmp_path / "tests" / "fixtures" fixtures.mkdir(parents=True, exist_ok=True) for name, body in (recorded or {}).items(): (fixtures / name).write_text(body, encoding="utf-8") stubs = tmp_path / "stubs" stubs.mkdir() stub = stubs / "ssh" stub.write_text(CAPTURE_SSH_STUB, encoding="utf-8") stub.chmod(0o755) call_log = tmp_path / "ssh-calls.log" call_log.write_text("", encoding="utf-8") env = {**os.environ, "PATH": f"{stubs}{os.pathsep}{os.environ['PATH']}", "STUB_LOG": str(call_log), "FAKE_REMOTE": str(fake), "FAIL_SSH": "1" if fail_ssh else "0"} if alias is not None: env["GATEWAY_SSH"] = alias return script, fixtures, call_log, env def _capture(tmp_path: Path, **kwargs: object) -> subprocess.CompletedProcess[str]: script, _fixtures, _log, env = _capture_sandbox(tmp_path, **kwargs) # type: ignore[arg-type] return subprocess.run([BASH, str(script)], capture_output=True, text=True, env=env, check=False) def _fixture_body(path: Path) -> str: """A fixture without its `#` provenance block — the bytes the host answered.""" return "".join(line for line in path.read_text().splitlines(keepends=True) if not line.startswith("#")) def test_the_gateway_capture_exists_and_asks_for_no_privilege() -> None: """Six world-readable reads over an ssh key: nothing here needs root or a rewrite tool.""" assert CAPTURE.is_file(), "scripts/capture-gateway-config.sh is missing (phase 28 task 04)" assert os.access(CAPTURE, os.X_OK) text = CAPTURE.read_text() for path in CAPTURE_TARGETS: assert path in text, f"{path} is no longer captured" code = "\n".join(line for line in text.splitlines() if not line.lstrip().startswith("#")) assert "sudo" not in code, ( "the capture must read exactly what dashboards-agent can read; a pass that needed root " "would record more than the agent can see (the smartctl note in capture-fixtures.sh)" ) for tool in ("sed ", "awk ", " tr ", "cut "): assert tool not in text, f"{tool.strip()} would be a text filter between the read and a fixture" def test_the_gateway_capture_dials_the_alias_and_nothing_else(tmp_path: Path) -> None: """No hardcoded address: every remote command goes to `$GATEWAY_SSH`, default `gateway-ops`.""" text = CAPTURE.read_text() assert 'GATEWAY_SSH="${GATEWAY_SSH:-gateway-ops}"' in text # Comments may name an address (`0.0.0.0` is a shape the parser has to survive); code may not. code = "\n".join(line for line in text.splitlines() if not line.lstrip().startswith("#")) stray = [a for a in re.findall(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", code) if not a.startswith("203.0.113.")] assert not stray, f"the script dials or embeds an address: {stray}" _script, _fixtures, log, env = _capture_sandbox(tmp_path) subprocess.run([BASH, str(_script)], capture_output=True, text=True, env=env, check=False) assert _dialled_hosts(log) == {"gateway-ops"} other = _capture_sandbox(tmp_path / "alias", alias="lab-gw") subprocess.run([BASH, str(other[0])], capture_output=True, text=True, env=other[3], check=False) assert _dialled_hosts(other[2]) == {"lab-gw"} def _dialled_hosts(log: Path) -> set[str]: """The host argument of every ssh the stub was called for — options are not hosts.""" hosts = set() for line in log.read_text().splitlines(): tokens = [token for token in line.split() if not token.startswith("-") and "=" not in token] if tokens: hosts.add(tokens[0]) return hosts def test_the_gateway_capture_records_every_document_of_the_tree_it_read(tmp_path: Path) -> None: """The happy path, run: one fixture per remote document, include globs and all. Two files in `conf.d` is deliberate. The script reads the include globs out of the captured main config rather than a hardcoded pair of directories, so a gateway that adds a vhost file gets it captured, and `files` in the payload stays the tree the config actually merges. """ result = _capture(tmp_path) assert result.returncode == 0, result.stdout + result.stderr fixtures = tmp_path / "tests" / "fixtures" expected = {"gateway_haproxy.cfg", "gateway_nginx_main.conf", "gateway_nginx_conf_d_http.conf", "gateway_nginx_conf_d_tls.conf", "gateway_nginx_stream_d_stream.conf", "gateway_proc_net_tcp.txt", "gateway_proc_net_tcp6.txt"} assert {p.name for p in fixtures.iterdir()} == expected, result.stdout assert _fixture_body(fixtures / "gateway_haproxy.cfg") == \ CAPTURE_FAKE_GATEWAY["/etc/haproxy/haproxy.cfg"] assert _fixture_body(fixtures / "gateway_nginx_conf_d_tls.conf") == \ CAPTURE_FAKE_GATEWAY["/etc/nginx/conf.d/tls.conf"] # `mime.types` is included by the config and absent on the host: no fixture, no complaint — # the same absence-the-agent-reports rule, one level up. assert not (fixtures / "gateway_nginx_mime.types").exists() def test_the_gateway_capture_writes_the_domains_it_read_verbatim(tmp_path: Path) -> None: """Nothing is scrubbed from a config, and the domains are the proof. The `server_name` and the `proxy_pass` target beside it are the ingress map, and the `ssl_certificate_key` is a path rather than a key — so the file the fixture holds is the file the gateway runs, byte for byte. The one exception in this script is the socket tables' globally routable addresses, which `capture-fixtures.sh`'s identifier rule requires. """ _capture(tmp_path) vhosts = (tmp_path / "tests" / "fixtures" / "gateway_nginx_conf_d_http.conf").read_text() assert "server_name nextcloud.reeseapps.com;" in vhosts assert "proxy_pass http://nextcloud.reeselink.com:11000;" in vhosts assert "ssl_certificate_key /etc/letsencrypt/live/nextcloud.reeseapps.com/privkey.pem;" in vhosts assert "SCRUB" not in vhosts and "REDACTED" not in vhosts assert next((line for line in vhosts.splitlines() if line.startswith("# Verbatim /etc/nginx/conf.d/http.conf from gateway-ops")), None), "every fixture says which remote document it is" def test_a_dead_gateway_leaves_the_recorded_fixtures_alone(tmp_path: Path) -> None: """The `slots_gufo.json` rule: a capture script must never turn six good fixtures into six empty files, because then the parser tests pass against nothing (or fail for the wrong reason) and the ground truth is gone.""" keep = {"gateway_haproxy.cfg": "# old\nfrontend kept\n bind :::22 v4v6\n", "gateway_proc_net_tcp.txt": " 0: 00000000:1EFF 00000000:0000 0A x\n"} result = _capture(tmp_path, recorded=keep, fail_ssh=True) assert result.returncode == 1, result.stdout for name, body in keep.items(): assert (tmp_path / "tests" / "fixtures" / name).read_text() == body, \ f"{name} was overwritten by a read that failed" for name in keep: assert f"✗ {name}" in result.stdout, result.stdout assert "Connection refused" in result.stdout, "one line names the reason, per fixture" assert sorted(p.name for p in (tmp_path / "tests" / "fixtures").iterdir()) == sorted(keep), \ "a failed capture leaves no temp files behind" def test_an_empty_read_is_refused_the_way_a_failed_one_is(tmp_path: Path) -> None: """`cat` exiting 0 on an empty file is still not a capture.""" keep = {"gateway_proc_net_tcp.txt": " 0: 00000000:1EFF 00000000:0000 0A keep me\n"} result = _capture(tmp_path, gateway={"/proc/net/tcp": ""}, recorded=keep) assert result.returncode == 1, result.stdout assert "gateway_proc_net_tcp.txt" in result.stdout assert "empty" in result.stdout assert (tmp_path / "tests" / "fixtures" / "gateway_proc_net_tcp.txt").read_text() == \ next(iter(keep.values())) def test_an_include_dir_with_no_fixture_name_is_reported_and_fails(tmp_path: Path) -> None: """A config that moved its vhosts is a loud failure, not a fixture set that quietly stopped being the tree the agent merges.""" moved = {"/etc/nginx/nginx.conf": ( "http {\n include /etc/nginx/sites-enabled/*.conf;\n}\n" "stream {\n include /etc/nginx/stream.d/*.conf;\n}\n")} result = _capture(tmp_path, gateway=moved) assert result.returncode == 1, result.stdout assert "/etc/nginx/sites-enabled/*.conf" in result.stdout assert not (tmp_path / "tests" / "fixtures" / "gateway_nginx_sites_enabled_web.conf").exists() def test_a_config_with_no_glob_include_says_the_vhost_fixtures_were_not_refreshed( tmp_path: Path) -> None: keep = {"gateway_nginx_conf_d_http.conf": "# keep\nserver {\n listen 4443;\n}\n"} result = _capture(tmp_path, gateway={"/etc/nginx/nginx.conf": "user nginx;\nevents { }\n"}, recorded=keep) assert result.returncode == 1, result.stdout assert "no glob include" in result.stdout assert (tmp_path / "tests" / "fixtures" / "gateway_nginx_conf_d_http.conf").read_text() == \ next(iter(keep.values())) def test_the_socket_capture_publishes_no_globally_routable_endpoint(tmp_path: Path) -> None: """The one edit the capture makes, run rather than asserted in prose. Two rules at once, which is what makes the socket fixtures both usable and publishable: a globally routable address (including one behind `::ffff:`) becomes documentation space, and everything else — every port, every private/loopback/wildcard word, every other column, and the column widths themselves — is byte-for-byte what the kernel answered. A tx/rx queue pair is spelled exactly like an address column and must survive untouched, which is why the edit reads two columns and not the line. """ _capture(tmp_path) fixtures = tmp_path / "tests" / "fixtures" v4 = _fixture_body(fixtures / "gateway_proc_net_tcp.txt").splitlines() v6 = _fixture_body(fixtures / "gateway_proc_net_tcp6.txt").splitlines() raw_v4 = CAPTURE_FAKE_GATEWAY["/proc/net/tcp"].splitlines() raw_v6 = CAPTURE_FAKE_GATEWAY["/proc/net/tcp6"].splitlines() assert len(v4) == len(raw_v4) and len(v6) == len(raw_v6), "a row was added or lost" assert v4[1] == raw_v4[1], "a wildcard listener must survive untouched" assert "08080808:911A" not in "".join(v4), "8.8.8.8 is still in the fixture" assert "017100CB:911A" in v4[2], v4[2] # 203.0.113.1, the port kept assert len(v4[2]) == len(raw_v4[2]), "the replacement must be exactly as wide as the original" assert v4[3] == raw_v4[3], ( "an already-documentation address and the tx/rx queue pair on that row both moved: only " "the two address columns may be looked at") assert "08080808:08080808" in v4[3], v4[3] assert "1360032601010031FF005450E0B284FE" not in "".join(v6), "a global v6 is left in" assert "B80D0120000000000000000001000000:01BB" in v6[2], v6[2] # 2001:db8::1, port kept assert "FFFF0000017100CB:C0AC" in v6[2] or "FFFF0000" in v6[2], \ "a v4-mapped row must stay v4-mapped, or the parser's branch for it stops being exercised" assert v6[1] == raw_v6[1], "the wildcard v6 listener moved" assert len(v6[2]) == len(raw_v6[2]) def test_the_socket_capture_is_idempotent_so_a_re_run_does_not_churn(tmp_path: Path) -> None: """Re-recording the same moment must not diff, or every capture moves the fixture twice. Documentation space is not globally routable, so the second pass leaves it alone — the same property that makes the drive-serial scrub re-runnable. """ once = tmp_path / "once" _capture(once) again = _capture_sandbox(tmp_path / "twice", gateway={"/proc/net/tcp": _fixture_body( once / "tests" / "fixtures" / "gateway_proc_net_tcp.txt")}) result = subprocess.run([BASH, str(again[0])], capture_output=True, text=True, env=again[3], check=False) assert result.returncode == 0, result.stdout + result.stderr assert _fixture_body(again[1] / "gateway_proc_net_tcp.txt") == _fixture_body( once / "tests" / "fixtures" / "gateway_proc_net_tcp.txt")