Convert the 11 TODO items to phases 20-24 (masthead/nav polish, page cleanups, /disks hiding unconfigured hosts, LLM rate window, dev mTLS rollout). TODO.md cleared; items now live in .agents/phases/todo/. LLM-Generated: true
6.4 KiB
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
01_dev_bundle_export.md—scripts/dev-certs-export.py+dev-server.sh: the dev server writes every configured host's bundle todata/certs/<name>.bundle(0600) on start; an unissued host says so and writes nothing02_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 beforeneed_root, the token never reaches the token file or the unit03_fleet_rollout.md— agent 1.1.0 + bundle on the three hosts (deskwork via the operator's root, driveripper with the gap-10--forceline, gateway with its sudoers grant),HOST_TARGETSflipped per host, the URL never flipped before the bundle04_verification.md— the fleet is mTLS:agent.tls/deployed_bundle_revagree for all three hosts, nine sourcesok, nohttp://agent URL, the 16-driver SMART reads still live, screenshots
Testing & Quality
- Unit/integration:
tests/test_deploy_scripts.py— the export helper against acreate_app(...)hosted on a socket (the sandbox pattern the installer tests already use: fakeinstall/chownwhere the installer runs); the--fetch-bundlelegs (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.mdandAGENTS.mdin 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-bundlerecipe (the manual curl is demoted to the fallback), the dev section gains thedata/certsexport, 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 gitignoreddata/, the fleet'sHOST_TARGETSURLs carry no userinfo once flipped (invariant 6), and the task transcript names hosts, not credentials.
Completion Criteria
./scripts/dev-server.sh restartleavesdata/certs/<name>.bundlefor 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 beforeneed_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_TARGETSishttps://for every host with no userinfo, and./scripts/dev-server.sh statusreads nine sourcesokwithagent.tls: trueanddeployed_bundle_rev == bundle_revon all three (screenshots of/certsindata/logs/) - driveripper's 16 drives still read live on
/disksafter the--forceunit rewrite (the gap-10 unit shape is reproduced exactly) uv run pytestgreen, coverage not down,ruff+pyrightcleanbash .agents/validate.shgreen- 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