fix(egress+orchestrator): inject per-bottle auth tokens in the shared gateway

The cut-over dropped the per-bottle token flow, so an authed egress route on
the shared gateway failed with 'env var EGRESS_TOKEN_0 is unset' — the gateway
reads the token from its env, but a shared gateway has no per-bottle env.

Now the bottle's egress auth tokens travel to the gateway over /resolve and
the addon injects from them, mirroring what the per-bottle sidecar's env did:
- launch resolves the token values from the host env and hands them to the
  orchestrator, which holds them IN MEMORY (keyed by bottle_id, never written
  to the registry DB) and serves them on /resolve;
- PolicyResolver.resolve_policy_and_bottle_id + resolve_client_context now
  return the token map alongside policy + bottle_id (one round-trip);
- the egress addon overlays the process env with the bottle's tokens per
  request and uses that env for auth injection AND DLP — the agent never sees
  the credential.

Secrets stay off disk (validated: /resolve returns the token, the registry DB
does not contain it). SecretProvider (#355) is the future hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
2026-07-14 01:55:49 -04:00
parent 485d101430
commit b2f61053ad
12 changed files with 212 additions and 78 deletions
+9 -5
View File
@@ -89,13 +89,17 @@ class TestPolicyResolver(unittest.TestCase):
self.r.resolve_bottle_id("10.243.0.1", "tok")
def test_resolve_policy_and_bottle_id_one_call(self) -> None:
with patch(_URLOPEN, return_value=_resp({"bottle_id": "b1", "policy": "P"})) as m:
self.assertEqual(("P", "b1"), self.r.resolve_policy_and_bottle_id("10.243.0.1", "t"))
self.assertEqual(1, m.call_count) # both from a single /resolve
payload = {"bottle_id": "b1", "policy": "P", "tokens": {"EGRESS_TOKEN_0": "s"}}
with patch(_URLOPEN, return_value=_resp(payload)) as m:
self.assertEqual(
("P", "b1", {"EGRESS_TOKEN_0": "s"}),
self.r.resolve_policy_and_bottle_id("10.243.0.1", "t"),
)
self.assertEqual(1, m.call_count) # policy + id + tokens from a single /resolve
def test_resolve_policy_and_bottle_id_403_is_none_none(self) -> None:
def test_resolve_policy_and_bottle_id_403_is_none_none_empty(self) -> None:
with patch(_URLOPEN, side_effect=_http_error(403)):
self.assertEqual((None, None), self.r.resolve_policy_and_bottle_id("10.243.0.9"))
self.assertEqual((None, None, {}), self.r.resolve_policy_and_bottle_id("10.243.0.9"))
def test_resolve_policy_and_bottle_id_error_raises(self) -> None:
with patch(_URLOPEN, side_effect=urllib.error.URLError("refused")):