fix(egress): emit preserve_auth into the generated routes

The flag from #453 was parsed off the manifest and honored by the
addon, but `_route_to_yaml_fields` never wrote it, so it was dropped on
the way to the proxy and has never worked end to end. Every registry
route silently kept the unconditional Authorization strip.

That is the whole of the Docker Hub / GHCR "unauthorized" blocker in
#392: podman fetched a valid 2658-byte bearer from auth.docker.io, the
proxy stripped it, and registry-1.docker.io answered the resulting
anonymous request with a fresh WWW-Authenticate challenge — which reads
exactly like a credential problem rather than a serializer gap.

Found by diffing a bottle's rendered routes.yaml against its manifest:
preserve_auth was in the manifest and absent from the output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 20:17:04 -04:00
parent 82cf9bab5a
commit 96f5be48a6
2 changed files with 23 additions and 0 deletions
+4
View File
@@ -255,6 +255,8 @@ def _route_to_yaml_fields(r: Route) -> dict[str, object]:
fields["matches"] = matches_data fields["matches"] = matches_data
if r.git_fetch: if r.git_fetch:
fields["git"] = {"fetch": True} fields["git"] = {"fetch": True}
if r.preserve_auth:
fields["preserve_auth"] = True
if ( if (
r.outbound_detectors is not None r.outbound_detectors is not None
or r.inbound_detectors is not None or r.inbound_detectors is not None
@@ -335,6 +337,8 @@ def egress_render_routes(
lines.append(" git:") lines.append(" git:")
if git_dict.get("fetch") is True: if git_dict.get("fetch") is True:
lines.append(" fetch: true") lines.append(" fetch: true")
if f.get("preserve_auth") is True:
lines.append(" preserve_auth: true")
if "dlp" in f: if "dlp" in f:
dlp_dict: dict[str, object] = f["dlp"] # type: ignore dlp_dict: dict[str, object] = f["dlp"] # type: ignore
lines.append(" dlp:") lines.append(" dlp:")
+19
View File
@@ -379,6 +379,25 @@ class TestRenderRoutes(unittest.TestCase):
addon_routes = load_config(rendered).routes addon_routes = load_config(rendered).routes
self.assertTrue(addon_routes[0].git_fetch) self.assertTrue(addon_routes[0].git_fetch)
def test_preserve_auth_round_trips_to_the_addon(self):
"""Regression: the manifest parsed preserve_auth and the addon honored
it, but the renderer in between dropped it — so the flag never reached
the proxy and registry pulls kept failing with "unauthorized" while
the config looked correct everywhere it was inspected."""
from bot_bottle.egress_addon_core import load_config
b = _bottle([{"host": "registry-1.docker.io", "preserve_auth": True}])
routes = egress_routes_for_bottle(b)
rendered = egress_render_routes(routes)
self.assertIn("preserve_auth: true", rendered)
self.assertTrue(load_config(rendered).routes[0].preserve_auth)
def test_preserve_auth_omitted_when_unset(self):
b = _bottle([{"host": "x.example"}])
rendered = egress_render_routes(egress_routes_for_bottle(b))
self.assertNotIn("preserve_auth", rendered)
from bot_bottle.egress_addon_core import load_config
self.assertFalse(load_config(rendered).routes[0].preserve_auth)
def test_log_zero_omitted_from_render(self): def test_log_zero_omitted_from_render(self):
b = _bottle([{"host": "x.example"}]) b = _bottle([{"host": "x.example"}])
routes = egress_routes_for_bottle(b) routes = egress_routes_for_bottle(b)