Files
dashboards/.agents/phases/complete/24_dev_mtls_rollout/00_phase.md
T
ducoterra 89d9fb9f89 phase: 24_dev_mtls_rollout
**Phase 24 final verification — all green**

- **Verified (read-only re-run, 62/62 PASS)** via `data/logs/verify-24-mtls.py`: nine sources `ok`; `/api/certs` all three `active` with `deployed_bundle_rev == bundle_rev == 2`; `agent.tls: true` + `agent.version 1.1.0` on every host; driveripper 16/16 drives live; gateway absent from `/disks` but `disks:gateway ok`; all served target URLs `https://`, no userinfo; pre-flip backup present; no transport/cert alert.
- **Criterion 1 (export):** `data/certs/{driveripper,deskwork,gateway}.bundle` re-checked byte-identical (sha256) to `GET /api/certs/<name>/bundle`, mode `0600`, under gitignored `data/`; live arms re-probed (issued→file, unknown/`issued:false`→one-line note + no file, wrong password→exit 2, nothing written).
- **Criterion 2 (`--fetch-bundle`):** 54 sandbox pins in `tests/test_deploy_scripts.py` pass — byte-identical `0640` install + TLS unit lines, refusals (unissued / 401 / non-bundle body / dead dashboard) before `need_root` with no write, token absent from token file, unit, stdout and sudoers hint.
- **Criterion 3–4:** `./scripts/dev-server.sh status` nine `ok` + bundles block; screenshots `data/logs/24-task04-{certs,disks,system}.png` exist.
- **Criterion 7:** no behavior change — `agent/` untouched, `src/` diff is a `certs.ensure_certs` docstring only; token-mode, revocation-before-dial and bundle-route tests green.
- **Tests/lint:** `bash .agents/validate.sh` → **EXIT=0**; `uv run pytest --cov --cov-fail-under=94` → **1542 passed, 15 skipped, coverage 98.94%**; `ruff check src tests agent` → All checks passed; `pyright` → 0 errors; `node --check` clean; `uv run pytest -m live --live` → **11 passed**.
- **Docs:** README.md (all 1144 lines) + AGENTS.md reviewed against the live project — every config.py env var is in README and both templates, route set / severity thresholds / test counts / coverage figure match, all repo paths, cited test names and `Dash.*`/`DashCharts.*` helpers resolve, phase-24 doc pins green. **Verified consistent — no changes needed.**
- **Notable:** left `fastapi 0.153.0 → 0.158.0` (validate reports it as advisory; no dependency was touched, rule 8 conditions the check on deps moving). Killed two abandoned `/tmp/fbtest` loopback stub processes left by task 02's manual probe; dev server unaffected.
- **Next pending phase:** `.agents/phases/todo/25_masthead_control_placement/`.
2026-10-21 13:47:45 -04:00

6.4 KiB
Raw Blame History

Phase 24 — Dev mTLS: no non-mTLS agent, ever

Source: TODO.md L3 — "The client should never allow non-mtls communication. deskwork and gateway are still non-mtls according to the dashboard. During dev, the dev server should generate certs in a non-committed directory and then distribute them to clients during registration so we never need non-mtls servers." Story: n/a Context: phase 15 (complete) — the durable CA in meta (certs.py: five certs.* rows, the bundle stored verbatim), the agent's TLS mode (agent/host_agent.py 1.1.0: --cert-bundle, CERT_OPTIONAL, the live # REVOKED denylist, agent.tls/agent.bundle_rev in the snapshot), the collector's certificate dial (collectors.py AgentTls — an https:// HOST_TARGETS entry dials with this app's client certificate), the bundle download route (GET /api/certs/<name>/bundle, session-gated, the stored bytes verbatim), the installer's --cert-bundle shape check (deploy/install-agent.sh lines 270–331 — the python3 validator runs before need_root, the bundle installs 0640 root:dashboards-agent at line 385, the --status https probe), and the fleet record (AGENTS.md gap 11: no bundle is installed on any host, HOST_TARGETS is http:// for every host, the installed agents run 1.0.0 so 1.1.0 ships together with the bundle, never the bundle alone; gap 10: driveripper's unit is reproducible only with the repo script's --as-root --caps CAP_SYS_RAWIO CAP_SYS_ADMIN --force line — the Oct-18 sudo-granted snapshot must not be run; deskwork's ops has no installer grant, so its install goes through the operator's root). scripts/dev-server.sh (the dev server: reads .env.dev, nohup dashboards, /api/health readiness probe, status subcommand) and data/ (gitignored — the non-committed directory the TODO names)

Objective

The dev loop stops producing http:// agents. On start, the dev server exports every configured host's stored bundle to non-committed data/certs/ (the CA is already durable in meta — "generate" means materialise what the first boot minted, never a second authority), and the installer gains --fetch-bundle so a client's registration — its install — pulls its bundle from the dashboard and installs it in the same run (the operator no longer curls a file across the network by hand). Then the fleet itself moves: agent 1.1.0 + bundle on deskwork, driveripper and gateway, HOST_TARGETS flipped to https:// host by host (a URL flipped before its bundle is installed turns that host red by construction — the order is the safety), leaving the dashboard's nine sources ok with three agent.tls: true hosts and no http:// agent URL anywhere.

Dependencies

  • — (none) — the phase builds on complete phase 15 and phase 17; it is scheduled last on purpose, because it is the fleet-changing one.

Tasks

  1. 01_dev_bundle_export.md — scripts/dev-certs-export.py + dev-server.sh: the dev server writes every configured host's bundle to data/certs/<name>.bundle (0600) on start; an unissued host says so and writes nothing
  2. 02_installer_fetch_bundle.md — install-agent.sh --fetch-bundle <url> --cert-name <name> --session <token>: registration pulls the bundle from the dashboard, the shape check runs on the fetched bytes before need_root, the token never reaches the token file or the unit
  3. 03_fleet_rollout.md — agent 1.1.0 + bundle on the three hosts (deskwork via the operator's root, driveripper with the gap-10 --force line, gateway with its sudoers grant), HOST_TARGETS flipped per host, the URL never flipped before the bundle
  4. 04_verification.md — the fleet is mTLS: agent.tls/deployed_bundle_rev agree for all three hosts, nine sources ok, no http:// agent URL, the 16-driver SMART reads still live, screenshots

Testing & Quality

  • Unit/integration: tests/test_deploy_scripts.py — the export helper against a create_app(...) hosted on a socket (the sandbox pattern the installer tests already use: fake install/chown where the installer runs); the --fetch-bundle legs (a good bundle fetched, installed and byte-identical; a malformed one refused before any write; the token absent from every written artefact)
  • Coverage: >90% on new/modified code
  • The phase is "Complete" only when the entire project test suite passes 100% (every test in the project, not just this phase's)

Documentation & Consistency

  • The final pass reviews README.md and AGENTS.md in full against the project as it stands after this phase — fixing every inconsistency found, including pre-existing ones not caused by this phase. The README's Protecting an agent, host by host rollout becomes the --fetch-bundle recipe (the manual curl is demoted to the fallback), the dev section gains the data/certs export, AGENTS.md's fleet record and gap 11 resolve to "all three hosts mTLS since " with the exact installed units, and the "bearer-token transport is the compatibility path, not the designed one" sentence becomes history. No token, no PEM and no session value go into any tracked file — the exported bundles live in gitignored data/, the fleet's HOST_TARGETS URLs carry no userinfo once flipped (invariant 6), and the task transcript names hosts, not credentials.

Completion Criteria

  • ./scripts/dev-server.sh restart leaves data/certs/<name>.bundle for every configured host that has an issued bundle, 0600, byte-identical to the stored row, and a one-line note for any unissued host (pinned + verified)
  • sudo deploy/install-agent.sh --fetch-bundle <url> --cert-name <host> --session <token> ... installs a byte-identical bundle in the sandbox, refuses a malformed one before need_root, and leaves no token in the token file, the unit or the log (pinned)
  • all three hosts run agent 1.1.0 from an installed bundle, HOST_TARGETS is https:// for every host with no userinfo, and ./scripts/dev-server.sh status reads nine sources ok with agent.tls: true and deployed_bundle_rev == bundle_rev on all three (screenshots of /certs in data/logs/)
  • driveripper's 16 drives still read live on /disks after the --force unit rewrite (the gap-10 unit shape is reproduced exactly)
  • uv run pytest green, coverage not down, ruff + pyright clean
  • bash .agents/validate.sh green
  • no behavior change in completed work: the token-mode path, the revocation wiring, the bundle route and the agent's token-mode bytes are all unchanged