feat(control-plane): role-scoped signed tokens so the gateway can't drive operator routes
test / integration-docker (pull_request) Successful in 17s
tracker-policy-pr / check-pr (pull_request) Successful in 16s
test / unit (pull_request) Successful in 39s
lint / lint (push) Successful in 56s
test / integration-firecracker (pull_request) Successful in 3m21s
test / coverage (pull_request) Successful in 19s
test / publish-infra (pull_request) Has been skipped
test / integration-docker (pull_request) Successful in 17s
tracker-policy-pr / check-pr (pull_request) Successful in 16s
test / unit (pull_request) Successful in 39s
lint / lint (push) Successful in 56s
test / integration-firecracker (pull_request) Successful in 3m21s
test / coverage (pull_request) Successful in 19s
test / publish-infra (pull_request) Has been skipped
Review follow-up on #469: the data plane held the same control-plane secret that authorizes every route, so a compromised egress/git-gate could queue a supervise proposal AND approve it (or rewrite policy, read injected tokens) — the (source_ip, identity_token) checks attribute the *bottle*, not the caller. Replace the single shared bearer secret with role-scoped, HMAC-signed tokens (compact HS256 JWTs, stdlib-only — no new dependency): * new `control_auth` mints/verifies `{role}` tokens; roles are `gateway` (data plane) and `cli` (host operator/launcher). * the orchestrator holds only the signing *key* and verifies; `dispatch` gates each route by role — `gateway` reaches /resolve + /supervise/ {propose,poll}, everything else is `cli`-only (401 unauthenticated, 403 wrong role). * the gateway is handed a pre-minted `gateway` token it cannot rewrite into `cli`; the host CLI mints its own `cli` token from the host key. * `gateway_init` scopes the signing key to the orchestrator process and the gateway token to the data-plane daemons, so even in the combined infra container a compromised data-plane daemon never sees the key. Launchers (docker gateway + infra, macOS infra) inject the minted token(s); Firecracker stays open behind its nft boundary. Open mode (no key) still grants full `cli` access — the fail-visible fallback for tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,8 +22,10 @@ import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from .. import log
|
||||
from ..control_auth import ROLE_GATEWAY, mint
|
||||
from ..docker_cmd import run_docker
|
||||
from ..paths import (
|
||||
CONTROL_AUTH_JWT_ENV,
|
||||
CONTROL_PLANE_TOKEN_ENV,
|
||||
bot_bottle_root,
|
||||
host_control_plane_token,
|
||||
@@ -186,6 +188,7 @@ class OrchestratorService:
|
||||
so a later `ensure_running` can detect a real code change."""
|
||||
self._ensure_network()
|
||||
run_docker(["docker", "rm", "--force", self._infra_name])
|
||||
_signing_key = host_control_plane_token()
|
||||
proc = run_docker([
|
||||
"docker", "run", "--detach",
|
||||
"--name", self._infra_name,
|
||||
@@ -209,16 +212,23 @@ class OrchestratorService:
|
||||
# Orchestrator registry DB on the host (sole writer: control plane).
|
||||
"--volume", f"{self._host_root}:{_ROOT_IN_CONTAINER}",
|
||||
"--env", f"BOT_BOTTLE_ROOT={_ROOT_IN_CONTAINER}",
|
||||
# Control-plane secret: required by the orchestrator (to enforce)
|
||||
# and by the gateway daemons (to present on /resolve calls).
|
||||
# Control-plane signing key (orchestrator: verifies tokens) + the
|
||||
# pre-minted `gateway` JWT (data-plane daemons: present it). gateway_init
|
||||
# scopes each to its process, so a compromised data-plane daemon never
|
||||
# sees the key and can't mint a `cli` token (issue #469 review).
|
||||
"--env", CONTROL_PLANE_TOKEN_ENV,
|
||||
"--env", CONTROL_AUTH_JWT_ENV,
|
||||
# Gateway daemons reach the orchestrator over loopback at its
|
||||
# fixed internal port (DEFAULT_PORT), independent of self.port.
|
||||
"--env", f"BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{DEFAULT_PORT}",
|
||||
# Opt the orchestrator into gateway_init's supervise tree.
|
||||
"--env", f"BOT_BOTTLE_GATEWAY_DAEMONS={_INFRA_DAEMONS}",
|
||||
self.image,
|
||||
], env={**os.environ, CONTROL_PLANE_TOKEN_ENV: host_control_plane_token()})
|
||||
], env={
|
||||
**os.environ,
|
||||
CONTROL_PLANE_TOKEN_ENV: _signing_key,
|
||||
CONTROL_AUTH_JWT_ENV: mint(ROLE_GATEWAY, _signing_key),
|
||||
})
|
||||
if proc.returncode != 0:
|
||||
raise OrchestratorStartError(
|
||||
f"infra container failed to start: {proc.stderr.strip()}"
|
||||
|
||||
Reference in New Issue
Block a user