feat(orchestrator+egress): slice 8 — multi-tenant egress via the resolver #363

Open
didericis-claude wants to merge 1 commits from orchestrator-slice8 into orchestrator-slice7
Collaborator

Slice 8 of PRD 0070, stacked on #362. The egress addon now selects each request's Config by the calling bottle's source IP, so one shared sidecar serves every bottle. Opt-in and fail-closed; single-tenant behaviour is unchanged (all 182 existing egress tests still pass).

Orchestrator side (source-IP-primary attribution, per the PRD invariant):

  • registry.by_source_ip (single active bottle at a source IP — network-layer attribution); attribute now composes it + the token.
  • service.resolve(source_ip, token="") — with a token, strict; without, source IP alone.
  • POST /resolve's identity_token is now optional (absent → source-IP-only); split cleanly from the token-required /attribute.
  • policy_resolver.resolve token optional.

Egress side:

  • egress_addon_core.resolve_client_config(...) — fetches + parses the client's Config, fail-closed: unattributed / resolver error / unparseable policy all → deny-all (no routes). Host-testable; a PolicyResolverLike Protocol keeps it import-free.
  • egress_addon: consolidated mode when BOT_BOTTLE_ORCHESTRATOR_URL is set → _active_config(flow) resolves per client IP (reads + strips the x-bot-bottle-identity header); request() uses it. Unset → the static routes file, exactly as before.

Security note: source-IP-only resolution is safe where the IP is unspoofable (Firecracker /31 + nft) and the control plane is reachable only by the trusted sidecar; the identity token, when the agent injects it, strengthens it on weaker backends.

Scope note: the egress data plane is now multi-tenant. Remaining to be fully live: the network topology routing every bottle's proxy to the one shared sidecar, git-gate multitenancy, and agent-side identity-token injection.

Merge order: #352#356#357#358#360#361#362 → this.

🤖 Generated with Claude Code

Slice 8 of PRD 0070, **stacked on #362**. The egress addon now selects each request's `Config` by the calling bottle's **source IP**, so one shared sidecar serves every bottle. **Opt-in and fail-closed; single-tenant behaviour is unchanged** (all 182 existing egress tests still pass). **Orchestrator side** (source-IP-primary attribution, per the PRD invariant): - `registry.by_source_ip` (single active bottle at a source IP — network-layer attribution); `attribute` now composes it + the token. - `service.resolve(source_ip, token="")` — with a token, strict; without, source IP alone. - `POST /resolve`'s `identity_token` is now **optional** (absent → source-IP-only); split cleanly from the token-required `/attribute`. - `policy_resolver.resolve` token optional. **Egress side:** - `egress_addon_core.resolve_client_config(...)` — fetches + parses the client's `Config`, **fail-closed**: unattributed / resolver error / unparseable policy all → deny-all (no routes). Host-testable; a `PolicyResolverLike` Protocol keeps it import-free. - `egress_addon`: consolidated mode when `BOT_BOTTLE_ORCHESTRATOR_URL` is set → `_active_config(flow)` resolves per client IP (reads + **strips** the `x-bot-bottle-identity` header); `request()` uses it. Unset → the static routes file, exactly as before. **Security note:** source-IP-only resolution is safe where the IP is unspoofable (Firecracker `/31` + nft) **and** the control plane is reachable only by the trusted sidecar; the identity token, when the agent injects it, strengthens it on weaker backends. **Scope note:** the egress data plane is now multi-tenant. Remaining to be fully live: the **network topology** routing every bottle's proxy to the one shared sidecar, **git-gate** multitenancy, and **agent-side identity-token injection**. Merge order: #352 → #356 → #357 → #358 → #360 → #361 → #362 → this. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
didericis-claude added 1 commit 2026-07-13 17:49:03 -04:00
feat(orchestrator+egress): slice 8 — multi-tenant egress via the resolver (#352)
lint / lint (push) Successful in 2m8s
test / unit (pull_request) Successful in 1m1s
test / integration (pull_request) Successful in 25s
test / coverage (pull_request) Successful in 1m25s
d3e08cf039
The egress addon now selects each request's Config by the calling bottle's
source IP, so one shared sidecar serves every bottle. Opt-in and fail-closed;
single-tenant behaviour is unchanged.

Orchestrator side (source-IP-primary attribution, per the PRD invariant):
  * registry: `by_source_ip` (the single active bottle at a source IP —
    network-layer attribution); `attribute` now composes it + the token.
  * service: `resolve(source_ip, token="")` — with a token, strict
    attribution; without, source IP alone.
  * control_plane: `POST /resolve`'s identity_token is now OPTIONAL (absent
    → source-IP-only); split cleanly from the token-required `/attribute`.
  * policy_resolver: `resolve` token now optional.

Egress side:
  * egress_addon_core: `resolve_client_config(resolver, client_ip, token)` —
    fetches + parses the client's Config, **fail-closed**: unattributed, a
    resolver error, or an unparseable policy all yield deny-all (no routes).
    Host-testable; `PolicyResolverLike` Protocol keeps it import-free.
  * egress_addon: consolidated mode when `BOT_BOTTLE_ORCHESTRATOR_URL` is
    set → `_active_config(flow)` resolves per client IP (reads + strips the
    `x-bot-bottle-identity` header); `request()` uses it. Unset → the static
    routes file, exactly as before. `PolicyResolver` added to the bundle.

Security note: source-IP-only resolution is safe where the IP is unspoofable
(Firecracker /31 + nft) AND the control plane is reachable only by the
trusted sidecar; the identity token, when the agent injects it, strengthens
it on weaker backends.

Scope note: the egress data plane is now multi-tenant. Remaining to be fully
live: the network topology routing every bottle's proxy to the one shared
sidecar, git-gate multitenancy, and agent-side identity-token injection.

Tests: registry by_source_ip; orchestrator resolve (with/without token);
control-plane /resolve token-optional; resolver token-optional;
resolve_client_config fail-closed matrix. All 182 egress tests still pass
(single-tenant unchanged). Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
didericis approved these changes 2026-07-13 19:38:38 -04:00
Some checks are pending
lint / lint (push) Successful in 2m8s
test / unit (pull_request) Successful in 1m1s
test / integration (pull_request) Successful in 25s
test / coverage (pull_request) Successful in 1m25s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin orchestrator-slice8:orchestrator-slice8
git checkout orchestrator-slice8
Sign in to join this conversation.