Rotate node token after initial enrollment
The bootstrap body (Windows installer, cloud user-data, Ubuntu/macOS scripts) carries NODE_TOKEN in cleartext, and that token stays valid forever: `node_credentials.issue()` overwrites a single `node_token_hash`, and nothing rotates it after enrolment. Rotate it once the install's self-check passes, so the token that travelled in the fetched body or the user-data (IMDS-readable) is dead.
Suggested design (Task 9004 recon):
- Additive migration: `nodes.pending_node_token_hash` (nullable, partial unique index like `uq_nodes_node_token_hash`).
- `POST /api/agent/token/rotate` (authenticated by the current token): mints a token, stores its digest as pending, returns the plaintext once. Both tokens resolve while pending is set.
- `POST /api/agent/token/rotate/confirm` (authenticated by the NEW token): promotes pending to `node_token_hash`, clears pending. A lost response leaves the old token working.
- `jaah auth rotate` in apps/jaahd/jaah_cli.py: call rotate, write node.env via `_write_node_env`, verify, confirm. The daemon already re-reads node.env on a 401 (`control_plane_api._refresh_node_token_from_env`).
- Each installer runs it as its last step, after `Register-AndCheck`.
Out of scope: an operator's console re-issue (unchanged); periodic rotation.
Done means: a test proves a node's bootstrap token is refused after the install's final step, and the rotated token authenticates; a lost rotate response leaves the node authenticated; security review lane pass.
Outcome
Each installer now rotates the bootstrap node token after its self-check: pending token, confirm, `jaah auth rotate`, additive migration. Quick gate green, review lanes pass. PR #6662 queued; the lane runs the full gate. Deferred: draft #9098.
Artifacts
Activity
- Created
- Run
- Runner started
- Runner ended (done)