Push-based fleet Ansible: control = wherever Kai is, agent-SSH-deny stays at the ward gate #89

Closed
opened 2026-06-15 13:30:26 +00:00 by coilysiren · 1 comment
Owner

Decision

Move fleet Ansible off local-only execution. Today infrastructure/ansible runs ansible_connection: local against a bare localhost inventory, so converging the fleet means hand-running sync on every box — 6 physical hosts / 8 OS installs (Windows machines via WSL). That toll is the actual problem; the "agents shouldn't SSH, so Ansible stays local" rule was conflating two separable things.

Decouple agent-SSH-deny from Ansible transport:

  • The SSH-deny boundary belongs at the ward / cli-guard layer, scoped to the agent path (the ward hook pre-tool-use PreToolUse gate). Agents can't SSH.
  • Kai running a push is an operator action, hands-on-keyboard. Her direct CLI invocation doesn't pass through the agent hook, so the same boundary that denies the agent leaves her path open. This is the mechanism, not just a convention: ward gates the agent, ward-run-by-Kai proceeds.

Model

  • Control node = wherever Kai currently is. No designated always-on orchestrator. From whatever machine she's at, push to the targets.
  • Push targets = the always-on, headless boxes Kai is rarely physically at: kai-server, ser8, the desktop tower. These already run sshd, so push adds zero new inbound surface, and "on for long stretches" makes them reliably convergeable.
  • Daily-drivers stay local-run (or ansible-pull on login) — the macbook, the Windows laptop/WSL. Local-run is cheap there because Kai is already at the keyboard, and keeping them off the target list avoids enabling fresh inbound sshd surface.
  • Transport: tailnet-only, key-only, no WAN ingress. Uptime is not the security lever; transport scope is. On the tailnet, an unattended-while-away box is exposed to Kai's own tailnet, not the internet.
  • Local-run demotes to the bootstrap/fallback rung (new host with no keys yet; the control node converging itself), not the steady state.

Work

  • infrastructure/ansible — build a real fleet inventory + a push-capable sync play (replaces bare localhost / local.yml). Per the authoring-vs-rollout contract, the fleet rollout lives here.
  • ward — an operator verb that runs the push, gated so the agent path is denied SSH-bearing verbs while Kai's interactive invocation proceeds.
  • Handle Windows-native targets (push over SSH into WSL is fine; native Windows wants WinRM or SSH-into-WSL — likely keep those local-run / pull).
  • Bootstrap ordering vs the egress-deny in coilyco-flight-deck/infrastructure#332 (deny applied late enough not to brick provisioning; control node allowlisted).
  • coilyco-flight-deck/infrastructure#332 — deny outbound SSH on the autonomous-agent hosts. Compatible: push needs inbound SSH to targets; losing outbound on kai-server/ser8 doesn't block them being push targets.
## Decision Move fleet Ansible off local-only execution. Today `infrastructure/ansible` runs `ansible_connection: local` against a bare `localhost` inventory, so converging the fleet means hand-running sync on every box — 6 physical hosts / 8 OS installs (Windows machines via WSL). That toll is the actual problem; the "agents shouldn't SSH, so Ansible stays local" rule was conflating two separable things. **Decouple agent-SSH-deny from Ansible transport:** - The SSH-deny boundary belongs at the ward / cli-guard layer, scoped to the **agent path** (the `ward hook pre-tool-use` PreToolUse gate). Agents can't SSH. - Kai running a push is an **operator action, hands-on-keyboard**. Her direct CLI invocation doesn't pass through the agent hook, so the same boundary that denies the agent leaves her path open. This is the mechanism, not just a convention: ward gates the agent, ward-run-by-Kai proceeds. ## Model - **Control node = wherever Kai currently is.** No designated always-on orchestrator. From whatever machine she's at, push to the targets. - **Push targets = the always-on, headless boxes** Kai is rarely physically at: `kai-server`, `ser8`, the desktop tower. These already run `sshd`, so push adds zero new inbound surface, and "on for long stretches" makes them reliably convergeable. - **Daily-drivers stay local-run** (or `ansible-pull` on login) — the macbook, the Windows laptop/WSL. Local-run is cheap there because Kai is already at the keyboard, and keeping them off the target list avoids enabling fresh inbound `sshd` surface. - **Transport: tailnet-only, key-only, no WAN ingress.** Uptime is not the security lever; transport scope is. On the tailnet, an unattended-while-away box is exposed to Kai's own tailnet, not the internet. - **Local-run demotes to the bootstrap/fallback rung** (new host with no keys yet; the control node converging itself), not the steady state. ## Work - [ ] **infrastructure/ansible** — build a real fleet inventory + a push-capable sync play (replaces bare `localhost` / `local.yml`). Per the authoring-vs-rollout contract, the fleet rollout lives here. - [ ] **ward** — an operator verb that runs the push, gated so the agent path is denied SSH-bearing verbs while Kai's interactive invocation proceeds. - [ ] Handle Windows-native targets (push over SSH into WSL is fine; native Windows wants WinRM or SSH-into-WSL — likely keep those local-run / pull). - [ ] Bootstrap ordering vs the egress-deny in coilyco-flight-deck/infrastructure#332 (deny applied late enough not to brick provisioning; control node allowlisted). ## Related - coilyco-flight-deck/infrastructure#332 — deny *outbound* SSH on the autonomous-agent hosts. Compatible: push needs *inbound* SSH to targets; losing outbound on kai-server/ser8 doesn't block them being push targets.
Author
Owner

Misframed — there is no ward component here. ward's only involvement is that the existing ward exec runs the sync Makefile verb, same as build/test. The actual work (fleet inventory + push play) is entirely infrastructure/ansible. Refiled as coilyco-flight-deck/infrastructure#333.

Misframed — there is no ward component here. ward's only involvement is that the existing `ward exec` runs the sync Makefile verb, same as `build`/`test`. The actual work (fleet inventory + push play) is entirely infrastructure/ansible. Refiled as coilyco-flight-deck/infrastructure#333.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/ward#89
No description provided.