Files
bot-bottle/docs/prds/prd-new-control-plane-auth-provisioning.md
T
didericis-claude 784e49239d
test / integration-docker (pull_request) Successful in 14s
test / unit (pull_request) Successful in 40s
lint / lint (push) Successful in 57s
test / integration-firecracker (pull_request) Successful in 3m51s
test / coverage (pull_request) Successful in 21s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Successful in 12s
docs(orchestrator): tighten auth-provisioning wording to concrete services
Address review on #482: drop the generic "credential boundary" framing in the
PRD and docstrings and talk about the specific services — the orchestrator holds
the control-plane key; the host controller (#468) gets a separate key the
orchestrator never holds, so the orchestrator can't mint the credentials it uses
to talk to the host controller that owns its lifecycle. Lead with that concrete
win. No code behaviour change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 01:14:06 +00:00

4.2 KiB

PRD prd-new: Per-service signing keys for control-plane auth

  • Status: Draft
  • Author: claude
  • Created: 2026-07-26
  • Issue: #476

Summary

Provision control-plane signing keys per service, through one shared seam, so no service can mint another's credentials. Concretely: the orchestrator holds the control-plane key and mints the gateway's and CLI's tokens; the host controller (#468, next) gets a separate key the orchestrator never holds — so the orchestrator cannot forge the credentials it uses to talk to the host controller that starts and stops it. Landing this seam also retires the per-backend provisioning duplication that made PR #471 take three review rounds.

Problem

1. The orchestrator could forge host-controller credentials. The orchestrator's key signs roles {gateway, cli}. The tempting way to add the host controller (#468) is a third role, host, on that same key. But then the orchestrator — which holds the key — can mint host tokens, and the host controller, which owns the orchestrator's lifecycle, must not trust anything the orchestrator can mint. The two services need separate keys.

2. Every backend provisioned auth by hand. Each launcher (docker gateway/infra, macOS infra, firecracker infra) re-derived how to generate the signing key, scope it to the orchestrator, mint the gateway JWT, and keep the host key file canonical. All three PR #471 High-severity findings were this one integration bug in different launchers: the data plane got the full cli token; the firecracker control plane ran open; the firecracker guest clobbered the host key.

Goals / Success Criteria

  • The orchestrator and the host controller sign with different keys; neither can mint the other's tokens. (This PR provisions the orchestrator's key and leaves a drop-in seam for the host controller's.)
  • One shared provisioning seam every backend uses — a new backend or daemon implements it instead of rediscovering these four invariants:
    1. the signing key is host-canonical: a guest is handed it, never generates or overwrites it;
    2. only the orchestrator process gets the raw key; the gateway gets a pre-minted gateway token it can't rewrite into cli;
    3. the host CLI's cli token is minted from the same key, so it stays valid across co-running backends;
    4. the control plane never runs open where its data plane shares the host/VM.

Non-goals

  • The host controller itself (#468) — this only provisions the orchestrator's key and the seam #468 plugs into.
  • Rewriting the HMAC primitive: orchestrator_auth.mint/verify gain an optional roles= arg (default unchanged) so a key can carry a different role set; nothing else changes.
  • Network topology, the plane split (#469), or the server's open-mode fallback for tests.

Design

A TrustDomain is one service's signing material: its host-canonical key file, the roles that key may sign, and the env vars its key and a pre-minted token ride in. mint/verify are scoped to that domain's roles, so a token signed by one service's key neither carries nor verifies another service's role.

  • CONTROL_PLANE — the orchestrator's domain: key orchestrator-token, roles {gateway, cli}. The orchestrator process holds the key; the gateway holds only a minted gateway token; the host CLI mints its own cli token.
  • The host controller (#468) will add a second TrustDomain — its own key file and role(s) — that the orchestrator never holds.

ControlPlaneProvisioning is the seam the backends call. orchestrator_key() returns the raw key for the control-plane process (fail-closed: it raises rather than hand back an empty key that would run the server open where the data plane is co-located). gateway_token() mints the gateway's token. Each backend applies these through its own transport — docker/macOS inject env vars, firecracker pushes over SSH — but none re-derives which key or role.

paths.host_signing_key(filename) generalizes host_orchestrator_token() so each domain names its own key file.

Open questions

None blocking. #468 adds its TrustDomain and a second ControlPlaneProvisioning-shaped consumer; renaming that class to something service-neutral is a cosmetic call to make then.