fix(manifest): allow token + git on the same host (PRD 0010)
git-gate holds an SSH IdentityFile for push/fetch; cred-proxy holds a PAT for HTTPS REST API calls. The two brokers are orthogonal — the common dev setup names both on the same host (e.g. gitea.dideric.is SSH for push, gitea.dideric.is PAT for `tea pr create`). The original PRD 0010 wording called this a "configuration smell" and rejected it at parse time. That was wrong; this drops the overlap rejection from the validator and updates the PRD prose to match. Tests flip from "rejection" to "coexistence" assertions.
This commit is contained in:
@@ -570,11 +570,14 @@ def _validate_tokens(
|
|||||||
|
|
||||||
- At most one entry per Kind, except `gitea` which may have
|
- At most one entry per Kind, except `gitea` which may have
|
||||||
multiple entries (one per Gitea instance) with distinct Urls.
|
multiple entries (one per Gitea instance) with distinct Urls.
|
||||||
- No overlap with `bottle.git` hosts: a `github` or `gitea` token
|
|
||||||
whose host matches a `bottle.git` upstream host would put two
|
A `github` or `gitea` token MAY name the same host as a
|
||||||
credential brokers on the same remote (git-gate's gitleaks-
|
`bottle.git` entry: the two paths broker different protocols
|
||||||
scanning gate AND cred-proxy's bearer injection). Pick one.
|
(git-gate handles SSH push/fetch with an IdentityFile; cred-proxy
|
||||||
|
handles HTTPS REST API calls with a PAT), so declaring both on
|
||||||
|
one host is a legitimate dev setup, not a configuration error.
|
||||||
"""
|
"""
|
||||||
|
del git # cross-host overlap is intentionally not rejected.
|
||||||
by_kind: dict[str, list[TokenEntry]] = {}
|
by_kind: dict[str, list[TokenEntry]] = {}
|
||||||
for t in tokens:
|
for t in tokens:
|
||||||
by_kind.setdefault(t.Kind, []).append(t)
|
by_kind.setdefault(t.Kind, []).append(t)
|
||||||
@@ -595,15 +598,6 @@ def _validate_tokens(
|
|||||||
f"that may have multiple entries)."
|
f"that may have multiple entries)."
|
||||||
)
|
)
|
||||||
|
|
||||||
git_hosts = {g.UpstreamHost for g in git}
|
|
||||||
for t in tokens:
|
|
||||||
if t.Kind in ("github", "gitea") and t.UpstreamHost in git_hosts:
|
|
||||||
die(
|
|
||||||
f"bottle '{bottle_name}' token ({t.Kind}, host {t.UpstreamHost!r}) "
|
|
||||||
f"overlaps a bottle.git upstream on the same host. git-gate already "
|
|
||||||
f"brokers this remote; drop the token entry or remove the git entry."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_unique_git_names(bottle_name: str, git: tuple[GitEntry, ...]) -> None:
|
def _validate_unique_git_names(bottle_name: str, git: tuple[GitEntry, ...]) -> None:
|
||||||
seen: dict[str, None] = {}
|
seen: dict[str, None] = {}
|
||||||
|
|||||||
@@ -315,12 +315,12 @@ Validation:
|
|||||||
documented upstream.
|
documented upstream.
|
||||||
- At most one entry per `Kind` except `gitea`, which may have
|
- At most one entry per `Kind` except `gitea`, which may have
|
||||||
multiple distinct `Url`s.
|
multiple distinct `Url`s.
|
||||||
- No silent overlap with `bottle.git` upstreams that already
|
- A `github` or `gitea` token MAY name the same host as a
|
||||||
flow through git-gate; if a `tokens[].Kind: github|gitea`
|
`bottle.git` entry. The two paths broker different protocols —
|
||||||
entry's `Url` collides with a `git[].Upstream`'s host, parse
|
git-gate holds an SSH `IdentityFile` for push/fetch and runs
|
||||||
fails with a "git-gate already brokers this remote, drop one"
|
gitleaks; cred-proxy holds a PAT for HTTPS REST API calls (`tea`,
|
||||||
hint. (Both paths broker credentials; doubling up is a
|
`gh`, octokit). The common dev setup uses both on the same host
|
||||||
configuration smell, not a feature.)
|
and is not a configuration error.
|
||||||
|
|
||||||
### Routing table
|
### Routing table
|
||||||
|
|
||||||
|
|||||||
@@ -129,13 +129,14 @@ class TestTokenEntryValidation(unittest.TestCase):
|
|||||||
]))
|
]))
|
||||||
|
|
||||||
|
|
||||||
class TestTokenGitOverlap(unittest.TestCase):
|
class TestTokenGitCoexistence(unittest.TestCase):
|
||||||
def test_github_token_collides_with_github_git_entry(self):
|
"""git-gate brokers SSH push/fetch via an IdentityFile; cred-proxy
|
||||||
# bottle.git already brokers github.com via the gate; declaring
|
brokers HTTPS REST API calls via a PAT. Declaring both on the same
|
||||||
# a github token on top would put two credential brokers on
|
host is the common dev setup (SSH key for git ops, PAT for `tea` /
|
||||||
# the same remote.
|
`gh` API calls), not a configuration error."""
|
||||||
with self.assertRaises(Die):
|
|
||||||
Manifest.from_json_obj(_manifest(
|
def test_github_token_and_github_git_entry_coexist(self):
|
||||||
|
m = Manifest.from_json_obj(_manifest(
|
||||||
tokens=[{"Kind": "github", "TokenRef": "GITHUB_TOKEN"}],
|
tokens=[{"Kind": "github", "TokenRef": "GITHUB_TOKEN"}],
|
||||||
git=[{
|
git=[{
|
||||||
"Name": "myrepo",
|
"Name": "myrepo",
|
||||||
@@ -143,10 +144,11 @@ class TestTokenGitOverlap(unittest.TestCase):
|
|||||||
"IdentityFile": "/dev/null",
|
"IdentityFile": "/dev/null",
|
||||||
}],
|
}],
|
||||||
))
|
))
|
||||||
|
self.assertEqual(1, len(m.bottles["dev"].tokens))
|
||||||
|
self.assertEqual(1, len(m.bottles["dev"].git))
|
||||||
|
|
||||||
def test_gitea_token_collides_with_same_host_git_entry(self):
|
def test_gitea_token_and_same_host_git_entry_coexist(self):
|
||||||
with self.assertRaises(Die):
|
m = Manifest.from_json_obj(_manifest(
|
||||||
Manifest.from_json_obj(_manifest(
|
|
||||||
tokens=[{
|
tokens=[{
|
||||||
"Kind": "gitea", "TokenRef": "GITEA_TOKEN",
|
"Kind": "gitea", "TokenRef": "GITEA_TOKEN",
|
||||||
"Url": "https://gitea.dideric.is",
|
"Url": "https://gitea.dideric.is",
|
||||||
@@ -157,9 +159,11 @@ class TestTokenGitOverlap(unittest.TestCase):
|
|||||||
"IdentityFile": "/dev/null",
|
"IdentityFile": "/dev/null",
|
||||||
}],
|
}],
|
||||||
))
|
))
|
||||||
|
self.assertEqual("gitea.dideric.is", m.bottles["dev"].tokens[0].UpstreamHost)
|
||||||
|
self.assertEqual("gitea.dideric.is", m.bottles["dev"].git[0].UpstreamHost)
|
||||||
|
|
||||||
def test_anthropic_token_does_not_collide_with_git(self):
|
def test_anthropic_token_and_git_unrelated(self):
|
||||||
# api.anthropic.com isn't a git host; no overlap possible.
|
# api.anthropic.com isn't a git host; coexistence is trivial.
|
||||||
m = Manifest.from_json_obj(_manifest(
|
m = Manifest.from_json_obj(_manifest(
|
||||||
tokens=[{"Kind": "anthropic", "TokenRef": "CLAUDE_BOTTLE_OAUTH_TOKEN"}],
|
tokens=[{"Kind": "anthropic", "TokenRef": "CLAUDE_BOTTLE_OAUTH_TOKEN"}],
|
||||||
git=[{
|
git=[{
|
||||||
|
|||||||
Reference in New Issue
Block a user