feat(orchestrator): slice 6 — source-IP-keyed multi-tenant policy (#352)
The orchestrator side of the multi-tenant consolidated sidecar: hold each
bottle's sidecar policy and serve it by verified source IP, with live
reload. One shared sidecar can now get per-bottle config keyed on who's
calling.
* registry.py — a `policy` column (migration v3, opaque JSON the sidecar
interprets) on BottleRecord; `register(..., policy=)` stores it,
`set_policy(bottle_id, policy)` updates it live, and `attribute` returns
it (the source-IP-keyed resolution).
* service.py — `launch_bottle(..., policy=)` and `set_policy`.
* control_plane.py — `POST /bottles` accepts `policy`; `PUT
/bottles/<id>/policy` live-reloads it; `POST /resolve` returns
{bottle_id, policy} for a verified (source_ip, token) — the per-request
call the multi-tenant sidecar makes; `/attribute` stays identity-only.
Scope note: this is the control-plane / state half. The data-plane half —
the egress mitmproxy addon (and git-gate) selecting allowlist / DLP /
token-injection per client IP by calling `/resolve` — is the next slice
(route agent bottles through the shared sidecar). The orchestrator stays
policy-agnostic: it stores and serves the blob verbatim.
Tests: registry policy store/update/persist; Orchestrator launch-with-policy
+ live set_policy; control-plane resolve returns policy (403 on bad token),
PUT policy updates / 404 / 400. Verified live over HTTP (launch -> resolve
-> PUT reload -> resolve reflects). Full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -46,10 +46,12 @@ class Orchestrator:
|
||||
image_ref: str = "",
|
||||
slot: int | None = None,
|
||||
metadata: str = "",
|
||||
policy: str = "",
|
||||
) -> BottleRecord:
|
||||
"""Register a bottle and broker its launch. Rolls the registry entry
|
||||
back if the launch doesn't take, so a failure leaves no orphan."""
|
||||
rec = self.registry.register(source_ip, metadata=metadata)
|
||||
"""Register a bottle (with its sidecar policy) and broker its launch.
|
||||
Rolls the registry entry back if the launch doesn't take, so a
|
||||
failure leaves no orphan."""
|
||||
rec = self.registry.register(source_ip, metadata=metadata, policy=policy)
|
||||
req = LaunchRequest(
|
||||
op="launch",
|
||||
bottle_id=rec.bottle_id,
|
||||
@@ -77,9 +79,16 @@ class Orchestrator:
|
||||
return True
|
||||
|
||||
def attribute(self, source_ip: str, identity_token: str) -> BottleRecord | None:
|
||||
"""Fail-closed attribution (delegates to the registry)."""
|
||||
"""Fail-closed attribution (delegates to the registry). The returned
|
||||
record carries the bottle's `policy` — this is the source-IP-keyed
|
||||
resolution the multi-tenant sidecar makes per request."""
|
||||
return self.registry.attribute(source_ip, identity_token)
|
||||
|
||||
def set_policy(self, bottle_id: str, policy: str) -> bool:
|
||||
"""Update a bottle's sidecar policy in place (live reload). False if
|
||||
the bottle is unknown."""
|
||||
return self.registry.set_policy(bottle_id, policy)
|
||||
|
||||
# --- consolidated sidecar ----------------------------------------------
|
||||
|
||||
def ensure_sidecar(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user