Commit Graph

13 Commits

Author SHA1 Message Date
didericis d4b27ebf1f feat(gateway): mandatory identity-token attribution on every data plane (PR #354 review)
lint / lint (push) Successful in 2m12s
test / unit (pull_request) Successful in 1m12s
test / integration (pull_request) Successful in 27s
test / coverage (pull_request) Successful in 1m21s
Codex review: the /31 TAP doesn't make source IP unspoofable, and the
app-layer token was returned by launch but never delivered or enforced, so
a spoofed source could select a victim bottle's policy/tokens. Make the
token mandatory and deliver it on each attributed plane (anti-spoof landed
separately as the network boundary).

Enforcement (control plane):
- `Orchestrator.resolve` now requires a matching (source_ip, identity_token)
  pair (constant-time) — no source-IP-only fallback. `/resolve` fail-closes
  (403) on a missing/empty/mismatched token.

Delivery, per plane (the token is `token_urlsafe`, safe in a URL):
- egress: proxy credentials (`HTTPS_PROXY=http://bottle:<token>@gw`). The
  addon reads `Proxy-Authorization` — from the request (HTTP) or captured at
  the CONNECT for HTTPS tunnels (keyed by client conn, cleared on disconnect)
  — validates, and strips it (+ the legacy header) before upstream.
- git-http: a URL-scoped `http.<gate>/.extraHeader: x-bot-bottle-identity`
  in the agent's git config (only over the http transport).
- supervise: `mcp add --header x-bot-bottle-identity: <token>` (claude +
  codex); the server reads the header and passes it to resolve.

Wiring: thread `ctx.identity_token` onto the firecracker + docker plans and
into the agent env/config at launch.

Verified on a KVM host: egress with the correct proxy-cred token returns
200 (HTTP and HTTPS/CONNECT), and no-token / wrong-token return 403; a real
`cli.py start --backend=firecracker` launch provisions git config + the
supervise MCP header and reaches the agent session, all under mandatory
enforcement. Fixed a `claude mcp add` arg-order bug (--header must follow
the positional name/url) found by that launch.

Transparent proxy for tools that ignore proxy env is deferred to a
follow-up (see thread); anti-spoof + host firewall remain the fail-closed
boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
2026-07-16 17:02:41 -04:00
didericis 69db629e16 feat(backend): slice 13d — cut docker launch over to the consolidated gateway (e2e green)
Rewire DockerBottleBackend.launch to the consolidated model, replacing the
per-bottle sidecar bundle. VALIDATED END-TO-END on real docker:
test_sandbox_escape passes all 5 attacks (egress DLP + git-gate gitleaks)
through the shared gateway.

launch now:
  - mints git-gate dynamic keys (if any), then launch_consolidated() to
    register the bottle + provision its git-gate state into the gateway and
    get the agent's attach context (pinned source IP, gateway address);
  - installs the SHARED gateway CA (gateway.ca_cert_pem) into the agent;
  - renders the agent-only consolidated compose on the gateway network at the
    pinned IP, proxied through the gateway;
  - points the agent's git-gate insteadOf (http://<gw>:9420) and supervise
    MCP (http://<gw>:9100) at the gateway (DockerBottlePlan.agent_git_gate_url
    / agent_supervise_url);
  - teardown = compose down + teardown_consolidated (dereg + deprovision).
Dropped the per-bottle networks, per-bottle egress CA, and bundle service.

Fixes surfaced by the real e2e (couldn't be caught by unit mocks):
  - provision_git_gate installs the bottle-agnostic pre-receive/access hooks
    into the gateway (were cp'd per-bundle before);
  - source-IP allocation reads the *actual* container IPs on the network
    (gateway + orchestrator + agents), not just gateway + registry — the
    orchestrator container sits on the network and was colliding.

Unit tests for the old bundle launch rewritten against the new collaborators.

NOTE for reviewers: the gateway/bundle image (bot-bottle-sidecars) must be
rebuilt when its flat sources change — `ensure_built` only builds-if-missing,
so a stale image silently runs the OLD single-tenant daemons. A content-hash
/ --rebuild path is a follow-up.

pyright 0 errors; pylint 9.83/10; unit suite green (1760 tests; the 13
test_sidecar_init /bin/sleep errors are pre-existing NixOS-local noise);
test_sandbox_escape green on real docker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
2026-07-14 02:38:20 -04:00
didericis-claude a64e3170cd refactor: make AgentProvisionPlan the source of truth for instance_name, prompt_file, image, dockerfile, guest_home
Drop the parallel fields passed through prepare() → _resolve_plan and
read everything from agent_provision instead. The provider plugin now
declares its own guest_home (so the backend stops hardcoding
"/home/node") and the wrapper that builds the provision plan accepts
instance_name and prompt_file, which providers store on the plan.

DockerBottlePlan and SmolmachinesBottlePlan expose container_name /
machine_name, image / agent_image, dockerfile_path /
agent_dockerfile_path, and prompt_file as properties that delegate to
agent_provision so existing call sites keep working unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:05:14 -04:00
didericis 74efb1c143 chore: sketch out desired refactor
Manual refactor into the rough shape we want/how we want the
resolve_plan logic to be consolidated. Needs subsequent fixes.
2026-06-08 23:05:14 -04:00
didericis-claude bbd6ec85ac chore: strip pipelock from Docker backend
lint / lint (push) Failing after 1m29s
test / unit (pull_request) Failing after 35s
test / integration (pull_request) Failing after 17s
- Remove pipelock_state_dir, _PIPELOCK_SUBDIR from bottle_state.py
- Remove proxy_plan: PipelockProxyPlan from DockerBottlePlan
- Remove EGRESS_PIPELOCK_CA_IN_CONTAINER from docker/egress.py
- Remove pipelock TLS init and proxy_plan population from launch.py
- Remove PipelockProxy import and pipelock_dir setup from prepare.py
- Remove pipelock volumes, daemon entry, and network alias from compose.py
- Remove pipelock mirroring entirely from egress_apply.py
- Agent HTTP_PROXY now always points at egress (no pipelock fallback)
2026-06-04 21:20:07 +00:00
didericis-claude 53219a55e1 refactor: hoist plan fields and print to BottlePlan base class (PRD 0044)
Move git_gate_plan, egress_plan, supervise_plan, and agent_provision
from DockerBottlePlan and SmolmachinesBottlePlan into BottlePlan.
Replace the abstract print method with a single concrete implementation
that renders git gate entries as "name → upstream_host:upstream_port"
and egress routes with conditional "[auth:scheme]" annotations.
2026-06-02 12:12:08 -04:00
didericis-claude de9bd7eb83 feat(manifest): add agent_provider.auth_token for Claude OAuth via egress
Operators can now declare:

  agent_provider:
    template: claude
    auth_token: BOT_BOTTLE_CLAUDE_OAUTH_TOKEN

and the provisioner injects a provider-owned api.anthropic.com egress
route (Bearer, tls_passthrough) rather than requiring a manually
declared route with the former claude_code_oauth role.

Changes:
- Add auth_token field to AgentProvider; validate claude-only.
- Remove claude_code_oauth from EGRESS_ROLES / PROVIDER_EGRESS_ROLES.
  Manifests that declare the role now fail at parse time with "unknown
  role" — the provisioner owns the route.
- agent_provision_plan: replace manifest_egress_routes/has_provider_auth
  with auth_token; Claude branch injects the api.anthropic.com route,
  placeholder env, and nonessential-traffic flags when auth_token is set.
- Add hidden_env_names: frozenset[str] to AgentProvisionPlan; Claude
  branch populates it with CLAUDE_CODE_OAUTH_TOKEN.
- Remove auth_role from AgentProviderRuntime and placeholder_env_for().
- print_util.visible_agent_env_names: accept hidden_env_names from the
  plan instead of dispatching on agent_provider_template.
- Both backends: drop manifest_egress_routes call, pass auth_token.
- PRD 0029 rescoped to cover both Codex and Claude provider auth.

Assisted-by: Claude Code
2026-06-01 22:24:17 -04:00
didericis-codex e808e81b87 refactor(agent): group provider provisioning into plan 2026-06-01 22:24:17 -04:00
didericis-codex a6332b9535 fix(codex): provision dummy user auth state 2026-06-01 22:24:17 -04:00
didericis 0708e99e4e feat(manifest): lift git.user to the agent layer
Agents may declare git.user (name/email); it overlays the referenced
bottle's git.user per-field at Manifest.bottle_for (agent wins on
non-empty), mirroring the extends: merge. git.remotes is rejected on
agents — it carries credentials and host trust and stays bottle-only.

The overlay lives at bottle_for, the single chokepoint both backends
use, so the docker/smolmachines git provisioners are unchanged. Adds
Manifest.git_identity_summary with per-field (agent)/(bottle)
provenance, printed in both preflights and `info`.

Refs #94

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-28 21:10:47 -04:00
didericis-codex 9399626ba6 fix(agent): hide auth placeholder env in preflight
test / unit (pull_request) Successful in 31s
test / integration (pull_request) Successful in 55s
2026-05-28 19:00:39 -04:00
didericis-codex 1cbedc91c0 refactor(agent): use agent-neutral runtime names
Assisted-by: Codex
2026-05-28 17:59:24 -04:00
didericis-codex c08b09dc9f refactor!: rename project to bot-bottle
Assisted-by: Codex
2026-05-28 17:56:14 -04:00