eab7d6ccbc15306d24f65685d37fda10bf826143
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8827c64a83 |
feat(supervise+orchestrator): slice 11 — per-bottle supervise queue + DLP safelist
Consolidated egress ran every bottle through one process but keyed the supervise proposal queue off a single SUPERVISE_BOTTLE_SLUG env and kept one *global* DLP safelist — so in the shared gateway an operator's token approval for bottle A would (a) be attributed to the wrong bottle and (b) leak into bottle B's DLP scan (A's approved secret passes B's egress). This slice keys both per bottle, resolved by source IP. - policy_resolver: add `resolve_policy_and_bottle_id` — policy + bottle id in one `/resolve`, so egress keys routing *and* the supervise queue/safelist from a single round-trip. Fail-closed (403 -> (None,None)). - egress_addon_core: add `resolve_client_context` (+ `ContextResolverLike`) returning `(Config, bottle_id)`, sharing the fail-closed parse with `resolve_client_config` via `_config_from_policy`. - egress_addon: `_active_config` -> `_resolve_flow` returns `(Config, slug)`; `safe_tokens` set -> per-bottle `_safe_tokens_for(slug)`; the token-allow write/await/archive + the approved-token add all use the resolved slug. Single-tenant (no resolver) unchanged — slug = the env SUPERVISE_BOTTLE_SLUG. New tests cover the resolver, the fail-closed context matrix, and the cross-tenant isolation (an approval lands only in the calling bottle's safelist; the proposal is keyed by the source-IP-attributed bottle; unattributed IPs can't supervise). Out of scope (noted): the git-gate gitleaks-allow hook + supervise_server agent-proposal paths, and websocket DLP (still self.config-only, inert in consolidated mode) — follow-up slices. pyright 0 errors; pylint 9.83/10; unit suite green (1700 tests; the 13 test_sidecar_init /bin/sleep errors are pre-existing NixOS-local noise). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck |
||
|
|
08aef30d87 |
feat(git-gate): source-IP-keyed multi-tenant repo namespace (PRD 0070)
First slice of git-gate consolidation: make the smart-HTTP git backend serve every bottle from one process, selecting each request's repo root by the calling bottle's source IP — the same attribution invariant + resolver the multi-tenant egress addon uses. `git daemon` can't source-IP-route per connection, so the consolidated gateway serves git-gate over this HTTP backend (the transport firecracker/macOS already use); wiring the docker path onto it lands with the launch-integration slice. - policy_resolver: factor out `_post_resolve`; add `resolve_bottle_id` (source IP -> bottle id, same fail-closed 403->None contract as `resolve`) — the git-gate has no policy blob to parse, the bottle *is* the namespace. - git_http_backend: `resolve_repo_root(resolver, base, source_ip, token)` — single-tenant passthrough when no resolver; else `<base>/<bottle_id>`, fail-closed on unattributed / resolver error / namespace escape. `BOT_BOTTLE_ORCHESTRATOR_URL` (same env as egress) flips the shared gateway multi-tenant; the identity header is read for attribution and never forwarded to the CGI. Per-repo creds + hooks scope by repo dir, so isolating the root per bottle isolates its creds too. Single-tenant path unchanged (existing real-git-push tests green). New unit coverage for the resolver + repo-root selection matrix. Full unit suite green (1690 tests; the 13 test_sidecar_init /bin/sleep errors are the pre-existing NixOS-local noise). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck |
||
|
|
e0b0429cd1 |
refactor(orchestrator): rename Sidecar -> Gateway for the consolidated data plane
Retire "sidecar" for the consolidated per-host path (PRD 0070 naming decision): the orchestrator is the umbrella/control plane, and the egress/git/supervise data-plane unit it runs is the "gateway". - git mv sidecar.py -> gateway.py and the two integration + one unit test files; DockerSidecar->DockerGateway, Sidecar->Gateway, SidecarError->GatewayError, SIDECAR_*->GATEWAY_*, ensure_sidecar-> ensure_gateway, sidecar_status->gateway_status, container name bot-bottle-orch-sidecar->bot-bottle-orch-gateway. - Prose rename across broker/registry/egress/policy_resolver + PRD 0070. - Preserved: the image name bot-bottle-sidecars, the BOT_BOTTLE_SIDECAR_IMAGE env var, Dockerfile.sidecars, and PRD 0069's own stage-name cross-references (that doc still uses "sidecar"). No behavior change. Full unit suite green (1679 tests; the 13 test_sidecar_init /bin/sleep errors are pre-existing NixOS-local noise). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck |
||
|
|
d3e08cf039 |
feat(orchestrator+egress): slice 8 — multi-tenant egress via the resolver (#352)
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
|
||
|
|
de9da29027 |
refactor(orchestrator): drop the PolicyResolver cache (#352)
Review: the resolver is called rarely enough that a round-trip doesn't matter, and correctness beats speed — always fetching means a revocation, policy change, or teardown the orchestrator knows about is honored immediately instead of lingering for a cache TTL. Remove the TTL cache (and `invalidate`); `resolve` now hits the orchestrator every call. Noted in the docstring that any future caching should use orchestrator-driven invalidation, not a blind TTL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck |
||
|
|
72ea500342 |
feat(orchestrator): slice 7 — sidecar-side PolicyResolver (#352)
The data-plane bridge that lets the consolidated sidecar apply each bottle's policy per request. `PolicyResolver` resolves a client's policy from the orchestrator's `POST /resolve` keyed on (source_ip, identity token) and caches it briefly (short TTL) so it isn't a round-trip per request; `invalidate()` drops an entry on teardown / live reload. Fail-closed: an unattributed client (orchestrator answers 403) resolves to None so the caller denies; unreachable / unexpected status raises so the caller can fail closed too rather than serve stale/empty policy. Stdlib only and free of bot-bottle imports, so it can be COPYed flat into the sidecar bundle. Scope note: this is the sidecar-side *client*. Wiring it into the live egress mitmproxy addon (select `Config` per client IP in the request path) and git-gate, plus routing all bottles' egress to the one shared sidecar, are the remaining data-plane pieces — a heavier change to the sidecar bundle's adversarial-input code, taken next. Tests: resolve returns/caches/expires/invalidates; 403 -> None (fail closed); other HTTP status + unreachable raise; missing policy -> empty; posts source_ip + identity_token. Full suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck |