Extend browser admin lifecycle actions
Support browser reboot and HTTPS stop through deferred control, plus exact `web certificate rotate --force` handoff to the dispatcher. Add typed request validation and focused boundary and lifecycle coverage.
This commit is contained in:
@@ -171,6 +171,10 @@ For SSH, standard output/error is redirected to the invoking session's bounded o
|
||||
|
||||
Admin SSH `exit`, remote reboot, SSH stop/disconnect, and host-key rotate/reset use deferred control. The control task waits up to ten seconds for command state plus administration and transport application buffers to clear, then adds a short delay; this is a bounded best-effort heuristic, not peer-delivery confirmation. UART0 invokes these actions synchronously. User mutations and their revocations are not part of this mechanism. UART0 linenoise and the SSH editor consume the same manually maintained completion matcher and candidate formatter, so the two administration routes cannot drift in offered or displayed ambiguous completions; the hints can still drift from command registration and are not an authorization list.
|
||||
|
||||
The first 8D.7 slice enables browser-admin reboot and HTTPS stop through the same control task. `web stop` is deferred only for browser origin; UART0/admin SSH keep their synchronous HTTPS-stop path. WEB performs authoritative cookie/principal/token validation after drain and delay, then calls lifecycle APIs outside console locks, never socket IO. Console snapshots expose pending deferral; HTTPD discards buffered/new input observed during it and latches each frame's discard decision across payload reception/cancellation. HTTPS stop intentionally closes both browser routes.
|
||||
|
||||
The second 8D.7 slice additionally permits exact parsed browser `web certificate rotate --force`. The request queue has a typed command-line/deferred-action union with unchanged capacity. An immutable owner `dispatcher_actions` mask sends certificate work, after the bounded drain and 200 ms delay, nonblockingly to the existing 12 KiB dispatcher rather than the 4 KiB control stack; zero mask retains SSH control-task behavior. Pending input remains gated through queueing/execution. Dispatcher token/principal/session/owner revalidation and an executing-slot reservation prevent stale execution or reuse during self-detach; WEB validates currentness again before lifecycle APIs. Transactional certificate generation/persistence commits before stop → start; generation/commit error skips lifecycle calls, stop error skips start and retains HTTPD ownership, and later lifecycle failure does not roll back committed material. HTTPD alone owns socket IO. Successful restart invalidates browser sessions and both routes; certificate trust and login must be renewed, while USB/UART0/SSH remain available. Account/legacy-credential/network/restricted SSH mutations remain blocked. No new tasks, depths, routes, assets or stack sizes. Drain/acknowledgement bounds are not execution deadlines or delivery guarantees; owner-mask/local-scratch target layout and control/dispatcher stack margins remain unmeasured (host sizeof is not target proof).
|
||||
|
||||
## Wi-Fi and persistence
|
||||
|
||||
`wifi_config` owns a fixed-width versioned NVS schema with four prioritized station profiles and AP policy `off`, `fallback`, or `always`. Missing configuration generates per-device defaults including a random AP password. Invalid stored data is generally left untouched while RAM defaults are used.
|
||||
|
||||
@@ -77,6 +77,9 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
|
||||
### Browser admin backend (8D.5)
|
||||
|
||||
- 8D.7 second slice: exact parsed browser `web certificate rotate --force`; `admin_ssh_console.{c,h}` supplies the typed request union/owner `dispatcher_actions` mask, bounded drain/200 ms handoff to the existing 12 KiB dispatcher, persistent pending gate and revalidated executing-slot reservation. `web_console.c` schedules; `web_admin_transport.c` revalidates then calls transactional `web_security_rotate_certificate()` → `web_server_stop()` → `web_server_start()`, short-circuiting errors and retaining ownership on failed stop. SSH/UART0 unchanged. No tasks/depth/routes/assets/stacks added; target stack margins unknown. Boundary `run.py` includes `certificate.c`; lifecycle/policy and transport 25/tickets 12 host groups pass as reported. Credential/account then other owner slices remain; user authorized stacking, not target/M2 sign-off. See `docs/phase8d7_implementation.md`.
|
||||
- 8D.7 first-slice history: browser `reboot`/`web stop` defer via `admin_ssh_console` control task; WEB owner revalidates cookie/principal/token before lifecycle calls. Pending console input is discarded (incoming-frame disposition latched before receive). `web_console.c` defers stop only for browser origin; other restrictions remain. Tests additionally include `python3 tests/admin_console_boundary/lifecycle.py`; handoff: `docs/phase8d7_implementation.md`. No 8D.7/M2 acceptance yet.
|
||||
|
||||
- Files: `src/web_admin_transport.{c,h}`, `src/web_admin_tickets.{c,h}`, protected registration/lifecycle in `web_server.c`, revocation through `web_serial_transport_revoke_*`, diagnostics in `web_console.c`.
|
||||
- Routes: CSRF-protected admin-only `POST /api/admin/ws-ticket`; ordinary `GET /ws/admin` with cookie/Origin/ticket/shared-console admission before explicit 101. No UI entry or broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled, 16 URI handlers.
|
||||
- Ownership: 20 ms ESP timer queues at most one HTTPD poll, no new task; HTTPD owns 1,552 B PSRAM-only payload and IO. Closure uses HTTPD-owned `shutdown`, not IDF's reusable-pointer queued close. Detach fences submitters; only successful HTTPD stop retires queued state before restart. Session/principal currentness and generation checks protect all sensitive boundaries.
|
||||
|
||||
@@ -4,6 +4,10 @@ This file is working memory. Update it during active work and before handoff; do
|
||||
|
||||
## Development state
|
||||
|
||||
- **8D.7 second certificate slice implemented / host-tested / build-verified, target pending:** Exact parsed browser `web certificate rotate --force` is enabled. Typed deferred-request union preserves queue capacity; immutable owner `dispatcher_actions` transfers certificate work after bounded drain/200 ms delay to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending-input gate survives handoff; token/principal/session revalidation and executing-slot reservation fence stale work/self-detach reuse. WEB uses transactional certificate commit → stop → start, returning on early errors and retaining ownership on failed stop. SSH/UART0 unchanged; other account/network/credential/SSH mutations remain blocked. No new tasks/depth/routes/assets/stacks. Parent final `pio run` PASS **26.32 s, 95,580 B RAM / 1,648,061 B flash**: **0 / +1,036 B** vs first slice, **0 / +1,572 B** vs 8D.6, **+1,048 / +48,088 B** vs 8D.0. Parent final rerun passes boundary (`run.py` including `certificate.c`), lifecycle, policy, transport **25**/tickets **12**, server **11**, cookie/admin, store/serial, UI **17** plus renderer/CSP and diff checks; independent reviewer has no actionable findings. Sanitizers unavailable (missing libasan/libubsan); no hardware validation. Owner mask/local scratch and target stack margins remain unmeasured; host sizeof is not target proof. Drain/acknowledgement is bounded, not execution latency or peer receipt. **User explicitly authorized continuing to stack the next bounded slice, not target sign-off. Next: credential/account, then other owner slices; 8D.7/M2 acceptance and numeric reserves pending.** See `docs/phase8d7_implementation.md` for separate slice histories and certificate force/trust/relogin/USB/SSH/failure checklist. Supersedes the first-slice next-step/continuation-pending instruction below. Production/tests/docs updated and parent build/regressions executed; no device operation, asset regeneration or commit.
|
||||
|
||||
- **8D.7 first increment implemented / host-tested / build-verified (2026-09-06), target pending:** User requests starting 8D.7. Scope split enables only browser `reboot` and `web stop` through existing bounded deferred control; WEB revalidates session/principal/token before lifecycle APIs. Adds browser-origin query and pending snapshot flag; HTTPD discards deferred input, latching disposition before receive. Review fixed second-frame-before-poll and cancellation-during-receive races; final reviewer confirms correction. Canonical handler/console/policy, transport **23**/tickets **12**, lifecycle **11**, cookie/admin, store/serial and UI **17** suites pass. Final `pio run` **12.44 s, 95,580 B RAM / 1,647,025 B flash**, **0 / +536 B** vs 8D.6. No tasks/routes/capacity/assets/UI/credential changes or device operations. `docs/phase8d7_implementation.md` holds behavior, restrictions, validation limits/resources and disruptive target checklist. **8D.7/M2 incomplete; next slice remains safe HTTPS identity/certificate handling**, then remaining owner/account restrictions. Obtain target regression or explicit user decision before stacking runtime changes. `web stop` intentionally closes both browser routes; restart via UART0/admin SSH. Prior 8D.6 sign-off stands; internal/DMA minima **6,516 / 1,580 B** and numeric reserves remain follow-ups.
|
||||
|
||||
- **8D.6 validated by explicit user sign-off (2026-09-06):** After 60-second boot/full-mix/partial-cleanup telemetry, user confirms all discussed remaining checks and requests wrap-up. Closes 8D.6 including Open admin and toolbar-order fixes, superseding older target-pending notes below. Evidence in `docs/phase8d6_implementation.md`: full mix at **230400 baud**, browser admin + admin SSH, two web serial/SSH/USB broker clients; web24 sole writer. Loaded internal/DMA/PSRAM free **33,900 / 26,144 / 8,087,468 B**, minima **6,516 / 1,580 / 8,063,428 B**, largest **24,576 / 24,576 / 7,995,392 B**; SSH stack minimum-free **16,280 B**. Partial cleanup retains serial, internal/DMA largest returns to **31,744 B**. One SSH handshake failure and rejected input byte remain unattributed. Exact revision/browser/counts/soak duration not separately supplied; no invented detailed execution or reserve approval. Numeric reserves and low lifetime minima remain follow-ups, not blockers reopening signed-off 8D.6. Last toolbar build/tests: **7.60 s, 95,580 B RAM / 1,646,489 B flash**, 17 UI groups/CSP/diff pass. This handoff changes documentation only. **Wait for separately requested 8D.7 — bounded web-shell lifecycle parity and M2 acceptance.** Preserve current restrictions until their safe owner paths are implemented; no settings/M2 completion or new implementation is implied.
|
||||
|
||||
- **8D.6 Open admin bug fix (2026-09-06), target retest pending:** Confirmed frontend/backend ticket mismatch: `openAdmin()` required 32 URL-safe characters (serial format), while admin issuance returns 64 lowercase hex characters. Valid POST responses therefore threw before WebSocket construction with the user's exact generic message. Fixed only the admin validator; split the previously shared serial/admin test fixture, assert exact admin query URL and reject malformed/serial-format tickets without closing serial. Correct-format fixture reproduced pre-fix failure; final production-renderer/CSP + 17 UI groups, cookie/admin endpoint integration, transport 19/tickets 12 and lifecycle 11 pass. These are separate frontend and backend harnesses, not real-browser/on-wire integration. CSP loader hash matches the reported `o6St...` policy; rejected UUID/index.js scripts' origin is unverified, not grounds to relax CSP. Final bounded `pio run` passed in **7.96 s**, **95,580 B RAM / 1,646,489 B flash**, unchanged sizes. Diff check passes. No assets, upload, commit, backend/admission changes or target pass. Short bug record in `docs/phase8d6_implementation.md`; parent independent review and user target retest pending.
|
||||
@@ -77,7 +81,7 @@ Based on checked-in source plus `README.md` and `docs/roadmap.md`:
|
||||
|
||||
- Phase 8C hardware validation passed, including route separation, shared command serialization, history/completion, prompts, output backpressure, revocation during queued work, deferred SSH lifecycle/reboot actions, and full concurrent transport operation. At 460800 baud with SSH and WebSocket clients in parallel, substantial packet drops and slow display controls were observed under load, without memory exhaustion; no baud-rate reduction is planned.
|
||||
- Current HTTPS UI retains status/serial for both roles and exposes an admin-only selector in 8D.6. The 8D.5 backend's temporary policy still denies all user mutations and unsupported self-affecting lifecycle commands.
|
||||
- Browser authentication uses cookie login/logout without Basic fallback. M1 and 8D.5 are signed off; 8D.6 selector target checks remain pending. Lifecycle parity remains separately requested 8D.7 work, not completed M2.
|
||||
- Browser authentication uses cookie login/logout without Basic fallback. M1 and 8D.4–8D.6 are signed off. 8D.7 stop/reboot and certificate slices are implemented with target checks pending; the user authorized the next bounded credential/account slice, not M2 acceptance.
|
||||
- NVS encryption, secure boot/flash encryption review, production certificate/provisioning policy, and OTA are not implemented. HTTPS login has a bounded global five-verifications/60-second throttle, not comprehensive cross-transport DoS protection.
|
||||
|
||||
## Known inconsistencies
|
||||
|
||||
@@ -84,7 +84,11 @@ Phase 8D.2 adds a third identity: non-reused 64-bit originating web-session IDs
|
||||
|
||||
Phase 8D.4 routes drain/lifecycle operations through a firmware-lifetime immutable owner adapter on the existing control task, outside console locks. Tokens include a transport namespace; owners revalidate full identity and marshal to their transport APIs. `SELF_CLOSE` targets the invoking frontend while existing SSH action meanings remain unchanged. Unsupported actions must fail before side effects. The two console slots remain a shared bounded pool, with no hypothetical browser capacity allocated. The 8D.5 prerequisite additionally requires owner currentness on the dispatcher, outside console locks, before commands and during prompts; account currentness alone cannot establish originating browser-session liveness. Recheck token identity after external validation, reject revoked submitted replies, and wipe consumed output. Polling is not a hard cancellation deadline and cannot roll back arbitrary handlers; owners retain admission/input/output/lifecycle responsibilities.
|
||||
|
||||
**Relevant files:** `src/admin_ssh_console.c`, `src/system_console.c`, `src/ssh_console.c`, `src/ssh_transport.c`
|
||||
**8D.7 first slice:** WEB also supports reboot and explicit HTTPS stop on the existing control task, with originating-session/principal validation after drain/delay. Stop is service-wide, not admin-socket-only; serial isolation applies to selector/SELF_CLOSE, not explicit HTTPS shutdown. Buffered input observed during deferral is wiped, including an incoming frame whose payload read races cancellation. Keep unsupported identity/credential/network paths blocked until separately implemented; no new executor or delivery guarantee.
|
||||
|
||||
**8D.7 second certificate slice:** Exact parsed browser `web certificate rotate --force` schedules a typed action, not command replay. Use the existing request-queue union and immutable owner `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher: crypto/NVS must not run on the 4 KiB control stack. Preserve queue capacity, pending-input gating through execution, token/principal/session revalidation and executing-slot reservation across self-detach. Zero mask preserves legacy SSH execution. WEB revalidates before transactional certificate commit → stop → start; early errors short-circuit and failed stop retains HTTPD ownership without start. Lifecycle failure after commit does not restore the old identity. Browser trust/relogin and UART0/SSH recovery are explicit operational consequences; USB/SSH are not stopped. Bounded acknowledgement/drain is neither an execution deadline nor receipt proof. No stack-size/task/route expansion; owner mask and local scratch still need target accounting/high-water evidence, not host sizeof assumptions. Other credential/account/network/SSH mutations remain blocked pending bounded owner slices.
|
||||
|
||||
**Relevant files:** `src/admin_ssh_console.c`, `src/system_console.c`, `src/ssh_console.c`, `src/ssh_transport.c`, `src/web_admin_transport.c`, `src/web_console.c`
|
||||
|
||||
## Authentication uses copied principals and fail-safe currentness checks
|
||||
|
||||
|
||||
Reference in New Issue
Block a user