mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
25d299346d
## Problem Agents installed through the one-step cluster deploy lose their command channel for good after their first restart. `POST /api/agents/agent/enroll` bound the runtime token to an invented `agent-<hostname>` and returned that as the agent's ID. The agent used it until Pulse acknowledged its first report with the host's real, machine-derived ID, which the agent persists. On the next start the agent registered under that real ID. The token was by then a version-2 binding to `agent-<hostname>`, so admission refused it with "Agent token mismatch: token is not bound to the registering agent", every few minutes, indefinitely. Seen live on a v6.5.0-rc.1 server with a deployed Proxmox node: reports flowed, but the node could run no commands or actions. ## Fix - **Enrollment leaves identity to the agent.** The runtime token is bound to the node's hostname and the current binding version, marked `deploy_identity_binding=agent`, and the response no longer carries `agentId`. The first command registration backfills the agent's own ID through the existing hostname-bound backfill path, and the config gate takes the same decision. - **One-time repair for tokens already issued.** A token enrollment bound to `agent-<hostname>` (it has `deploy_job_id` and no identity marker) may move once to the registering agent's ID: - The hostname must match exactly (case-insensitive), not by short-name equivalence. - The move persists `deploy_identity_binding=repaired` in the same save, and a failed save restores the previous metadata. - It applies whatever the token's binding version. - Any other identity or hostname is refused rather than handed to the legacy hostname migration. - A marked token never moves again, including after a hostname rebind recreates the `agent-<hostname>` shape. ## Tests - **`TestHandleEnroll_RuntimeTokenBindsTheAgentsOwnIdentityOnFirstCommandRegistration`** replays the field sequence: the identity enrollment handed out, then the acknowledged identity after a restart. It fails on the old code at the "registration after restart" step. - **`TestDeployPlaceholderTokenMovesToTheRegisteringAgentOnce`** covers: - the repair on the exact hostname only; - the persisted marker after a reload; - the repair, rebind to host b, repair-again attack; - config-gate parity. - **`TestVersionlessDeployPlaceholderCannotBeMovedTwice`** covers the versionless variant of that attack. - **`TestCurrentDeployTokenNeverMovesOnceBound`** checks that a token issued by current enrollment never moves once bound. - **Policy unit tests:** `TestEvaluateRepairsDeployPlaceholderBindingOnce`, `TestEvaluateVersionlessDeployPlaceholderOnlyMovesThroughTheRepair` and `TestEvaluateCurrentDeployTokenBackfillsTheAgentIdentity`. - **`TestContractDeployEnrollResponseCarriesNoAgentIdentity`** pins the enroll response keys. - **Mutation checks:** removing the marker check, loosening the hostname match, not persisting the marker, or letting placeholders fall through to the legacy branch each fails at least one of these tests. ## Not in this change Both of these exist for every install path and are not made worse here: - The first command registration binds whatever ID the agent presents. If that differs from the identity Pulse later resolves for its reports (a configured agent ID, or a host continuity fork), the next restart still mismatches. - Tokens that never connect the command channel keep an empty `bound_agent_id`, so collector-authority reduction cannot match them. Before this change they had a non-empty but wrong `agent-<hostname>`, which failed the same way.