refactor(cred_proxy): flat routes, role-driven provisioning (PRD 0010)
Replace bottle.tokens (with Kind enum and hardcoded per-kind
route/auth tables) with bottle.cred_proxy.routes — each route
declares its own path, upstream, auth_scheme, token_ref, and
optional role[]. The manifest is now the source of truth for the
proxy's runtime route table; adding an upstream is a manifest edit,
not a code change.
Agent-side rewrites move from per-kind dispatch to per-role tags
on routes:
anthropic-base-url -> set ANTHROPIC_BASE_URL=<proxy><path>
npm-registry -> write ~/.npmrc registry=
git-insteadof -> write ~/.gitconfig [url] insteadOf, keyed
off route.upstream (suppressed when
bottle.git brokers the same host)
tea-login -> add a ~/.config/tea/config.yml login
Roles are a list (string accepted as sugar). A gitea route
typically carries ["git-insteadof", "tea-login"]. Singleton roles
(anthropic-base-url, npm-registry) appear on at most one route.
token_env slots are assigned per distinct TokenRef in declaration
order — two routes sharing a token_ref (e.g. github API + git
endpoints) share a slot.
Drops: TOKEN_KINDS, _KIND_ROUTES, _KIND_AUTH_SCHEME, _TOKEN_DEFAULT_HOST,
cred_proxy_route_path_for_gitea, the kind field on CredProxyUpstream,
and the kind-based hardcoding in pipelock_token_hosts (now derives
from route.UpstreamHost).
Legacy bottle.tokens manifests now die with a hint pointing at
bottle.cred_proxy.routes + this PRD. Tests rewritten end-to-end.
Docs + example.json + the dev ~/claude-bottle.json updated to match.
This commit is contained in:
@@ -37,54 +37,43 @@ class TestEffectiveAllowlist(unittest.TestCase):
|
||||
self.assertEqual(eff, sorted(eff), "sorted")
|
||||
|
||||
|
||||
def _routes(routes):
|
||||
return {"cred_proxy": {"routes": routes}}
|
||||
|
||||
|
||||
class TestTokenHosts(unittest.TestCase):
|
||||
def test_github_yields_both_hosts(self):
|
||||
hosts = pipelock_token_hosts(_bottle({
|
||||
"tokens": [{"Kind": "github", "TokenRef": "GH"}],
|
||||
}))
|
||||
def test_each_route_contributes_its_upstream_host(self):
|
||||
hosts = pipelock_token_hosts(_bottle(_routes([
|
||||
{"path": "/gh-api/", "upstream": "https://api.github.com",
|
||||
"auth_scheme": "Bearer", "token_ref": "GH"},
|
||||
{"path": "/gh-git/", "upstream": "https://github.com",
|
||||
"auth_scheme": "Bearer", "token_ref": "GH"},
|
||||
])))
|
||||
self.assertEqual(["api.github.com", "github.com"], hosts)
|
||||
|
||||
def test_gitea_yields_configured_host(self):
|
||||
hosts = pipelock_token_hosts(_bottle({
|
||||
"tokens": [{"Kind": "gitea", "TokenRef": "T",
|
||||
"Url": "https://gitea.dideric.is"}],
|
||||
}))
|
||||
self.assertEqual(["gitea.dideric.is"], hosts)
|
||||
def test_dedupe_across_routes(self):
|
||||
hosts = pipelock_token_hosts(_bottle(_routes([
|
||||
{"path": "/a/", "upstream": "https://x.example",
|
||||
"auth_scheme": "Bearer", "token_ref": "T1"},
|
||||
{"path": "/b/", "upstream": "https://x.example",
|
||||
"auth_scheme": "Bearer", "token_ref": "T2"},
|
||||
])))
|
||||
self.assertEqual(["x.example"], hosts)
|
||||
|
||||
def test_npm_yields_registry(self):
|
||||
hosts = pipelock_token_hosts(_bottle({
|
||||
"tokens": [{"Kind": "npm", "TokenRef": "N"}],
|
||||
}))
|
||||
self.assertEqual(["registry.npmjs.org"], hosts)
|
||||
|
||||
def test_anthropic_yields_api_host(self):
|
||||
hosts = pipelock_token_hosts(_bottle({
|
||||
"tokens": [{"Kind": "anthropic", "TokenRef": "A"}],
|
||||
}))
|
||||
self.assertEqual(["api.anthropic.com"], hosts)
|
||||
|
||||
def test_no_tokens_empty(self):
|
||||
def test_no_routes_empty(self):
|
||||
self.assertEqual([], pipelock_token_hosts(_bottle({})))
|
||||
|
||||
|
||||
class TestAllowlistWithTokens(unittest.TestCase):
|
||||
def test_token_hosts_added_to_allowlist(self):
|
||||
eff = pipelock_effective_allowlist(_bottle({
|
||||
"tokens": [
|
||||
{"Kind": "npm", "TokenRef": "N"},
|
||||
{"Kind": "github", "TokenRef": "G"},
|
||||
],
|
||||
}))
|
||||
def test_route_hosts_added_to_allowlist(self):
|
||||
eff = pipelock_effective_allowlist(_bottle(_routes([
|
||||
{"path": "/npm/", "upstream": "https://registry.npmjs.org",
|
||||
"auth_scheme": "Bearer", "token_ref": "N"},
|
||||
{"path": "/gh-api/", "upstream": "https://api.github.com",
|
||||
"auth_scheme": "Bearer", "token_ref": "G"},
|
||||
])))
|
||||
self.assertIn("registry.npmjs.org", eff)
|
||||
self.assertIn("api.github.com", eff)
|
||||
self.assertIn("github.com", eff)
|
||||
|
||||
def test_gitea_host_added(self):
|
||||
eff = pipelock_effective_allowlist(_bottle({
|
||||
"tokens": [{"Kind": "gitea", "TokenRef": "T",
|
||||
"Url": "https://gitea.dideric.is"}],
|
||||
}))
|
||||
self.assertIn("gitea.dideric.is", eff)
|
||||
|
||||
|
||||
class TestTlsPassthrough(unittest.TestCase):
|
||||
@@ -92,21 +81,17 @@ class TestTlsPassthrough(unittest.TestCase):
|
||||
passthrough = pipelock_effective_tls_passthrough(_bottle({}))
|
||||
self.assertEqual(["api.anthropic.com"], passthrough)
|
||||
|
||||
def test_token_hosts_NOT_added_to_passthrough(self):
|
||||
# cred-proxy now trusts pipelock's per-bottle CA (loaded into
|
||||
# its container's trust store via docker cp + update-ca-
|
||||
# certificates at start time), so pipelock can MITM the
|
||||
# cred-proxy -> upstream leg and body-scan it. Auto-adding
|
||||
# cred-proxy hosts to passthrough would silently disable that
|
||||
# second scanner for github / gitea / npm.
|
||||
passthrough = pipelock_effective_tls_passthrough(_bottle({
|
||||
"tokens": [
|
||||
{"Kind": "github", "TokenRef": "G"},
|
||||
{"Kind": "npm", "TokenRef": "N"},
|
||||
{"Kind": "gitea", "TokenRef": "T",
|
||||
"Url": "https://gitea.dideric.is"},
|
||||
],
|
||||
}))
|
||||
def test_route_hosts_NOT_added_to_passthrough(self):
|
||||
# cred-proxy now trusts pipelock's per-bottle CA, so pipelock
|
||||
# can MITM the cred-proxy -> upstream leg and body-scan it.
|
||||
# Auto-adding cred-proxy hosts to passthrough would silently
|
||||
# disable that second scanner.
|
||||
passthrough = pipelock_effective_tls_passthrough(_bottle(_routes([
|
||||
{"path": "/gh-api/", "upstream": "https://api.github.com",
|
||||
"auth_scheme": "Bearer", "token_ref": "G"},
|
||||
{"path": "/npm/", "upstream": "https://registry.npmjs.org",
|
||||
"auth_scheme": "Bearer", "token_ref": "N"},
|
||||
])))
|
||||
self.assertEqual(["api.anthropic.com"], passthrough)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user