Uniform backend contract for control-plane auth provisioning #476

Open
opened 2026-07-24 12:13:50 -04:00 by didericis-claude · 0 comments
Collaborator

Why now

PR #471 took three review rounds to land, and every blocking finding was the same class of bug: a backend that provisioned the control-plane auth invariants differently from its siblings.

  • Round 1 (High): the data plane got the full-power control-plane token, so a compromised egress/git-gate could approve its own supervise proposals. Fixed with role-scoped JWTs (gateway vs cli).
  • Round 2 (High): the Firecracker infra VM never provisioned the signing key or a gateway JWT, so ControlPlaneServer fell into open mode and handed every unauthenticated caller the cli role — reopening the exact bypass for the daemons co-located in that VM.
  • Round 3 (High): the Firecracker fix then clobbered the host-canonical control-plane-token with its guest key, so starting/adopting Firecracker silently 401'd every already-running Docker/macOS orchestrator.

None of these were logic bugs in the auth primitive. They were integration bugs: each launcher (docker gateway, docker infra, macos infra, firecracker infra) re-derives, by hand, how to generate the signing key, scope it to the orchestrator process, mint the gateway JWT, inject it into the data-plane daemons, and reconcile the host key file. Miss one step and it's either a security hole or a cross-backend coexistence regression — and the tests don't catch it because each backend's provisioning is bespoke.

The ask

Hoist control-plane auth provisioning out of the per-backend launchers into a single shared contract that every backend satisfies. A backend should declare what it is (combined infra VM vs standalone gateway container, co-located vs isolated data plane), and the shared layer should decide:

  1. Where the signing key is generated and persisted (host-canonical, never clobbered by a guest).
  2. Which process gets the key (orchestrator only) vs a pre-minted gateway JWT (data-plane daemons).
  3. How the host CLI's cli token stays valid across simultaneously-running backends.
  4. That open mode is never reachable for any backend whose data plane shares a host/VM with the control plane.

Goal: adding a new backend, or a new data-plane daemon, means implementing the contract — not rediscovering these four invariants and failing review three times.

Scope / notes

  • Tracked against the refactor/orchestrator-gateway work.
  • Residual hardening already noted in #469 (run data-plane daemons as distinct non-root UIDs so a co-located root compromise can't read /proc/<orch>/environ) is complementary and can fold in here or stay separate.
  • This is a refactor of provisioning, not the auth primitive itself — control_auth.py's mint/verify is fine; the mess is in how each launcher wires it.
## Why now [PR #471](https://gitea.dideric.is/didericis/bot-bottle/pulls/471) took three review rounds to land, and every blocking finding was the *same class of bug*: a backend that provisioned the control-plane auth invariants differently from its siblings. - **Round 1 (High):** the data plane got the full-power control-plane token, so a compromised egress/git-gate could approve its own supervise proposals. Fixed with role-scoped JWTs (`gateway` vs `cli`). - **Round 2 (High):** the Firecracker infra VM never provisioned the signing key or a `gateway` JWT, so `ControlPlaneServer` fell into **open mode** and handed every unauthenticated caller the `cli` role — reopening the exact bypass for the daemons co-located in that VM. - **Round 3 (High):** the Firecracker fix then **clobbered the host-canonical `control-plane-token`** with its guest key, so starting/adopting Firecracker silently 401'd every already-running Docker/macOS orchestrator. None of these were logic bugs in the auth primitive. They were *integration* bugs: each launcher (`docker` gateway, `docker` infra, `macos` infra, `firecracker` infra) re-derives, by hand, how to generate the signing key, scope it to the orchestrator process, mint the `gateway` JWT, inject it into the data-plane daemons, and reconcile the host key file. Miss one step and it's either a security hole or a cross-backend coexistence regression — and the tests don't catch it because each backend's provisioning is bespoke. ## The ask Hoist control-plane auth provisioning out of the per-backend launchers into a single shared contract that every backend satisfies. A backend should declare *what it is* (combined infra VM vs standalone gateway container, co-located vs isolated data plane), and the shared layer should decide: 1. Where the signing key is generated and persisted (host-canonical, **never** clobbered by a guest). 2. Which process gets the key (orchestrator only) vs a pre-minted `gateway` JWT (data-plane daemons). 3. How the host CLI's `cli` token stays valid across simultaneously-running backends. 4. That **open mode is never reachable** for any backend whose data plane shares a host/VM with the control plane. Goal: adding a new backend, or a new data-plane daemon, means implementing the contract — not rediscovering these four invariants and failing review three times. ## Scope / notes - Tracked against the `refactor/orchestrator-gateway` work. - Residual hardening already noted in #469 (run data-plane daemons as distinct non-root UIDs so a co-located root compromise can't read `/proc/<orch>/environ`) is complementary and can fold in here or stay separate. - This is a refactor of provisioning, not the auth primitive itself — `control_auth.py`'s mint/verify is fine; the mess is in how each launcher wires it.
gitea-actions bot added the Status/Needs Triage label 2026-07-24 12:14:04 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: didericis/bot-bottle#476