docs(readme): make the registry route example correct and complete
test / integration-docker (pull_request) Successful in 14s
lint / lint (push) Successful in 49s
test / unit (pull_request) Successful in 1m37s
test / integration-firecracker (pull_request) Successful in 3m25s
test / coverage (pull_request) Successful in 19s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Successful in 7s

The example previously left DLP enabled on the blob CDNs, which is
exactly backwards: those hosts carry the multi-hundred-MB layers whose
buffering triggers #455, while the registry hosts they hang off carry
little. It also listed one of quay's three CDNs and omitted the CDN
requirement from the prose, so a reader following it would authenticate
fine and then 403 partway through a pull.

Adds the by-design DNS behaviour and the BusyBox wget caveat, both of
which cost real time during the #392 verification: nslookup failing
inside a nested container looks like breakage but is expected, and
BusyBox wget reports "error getting response" for requests the proxy
served successfully.

Verified by feeding the snippet through parse_frontmatter +
ManifestBottle.from_dict: all nine routes parse with the intended
preserve_auth and DLP settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 22:22:04 -04:00
parent 0ba25352b9
commit cc094765fd
+41 -14
View File
@@ -97,38 +97,65 @@ agent user outside it. Nested containers are a build/test convenience, not a
security boundary. The bottle remains the boundary. security boundary. The bottle remains the boundary.
Pulling images goes through the bottle's egress proxy like every other Pulling images goes through the bottle's egress proxy like every other
request, so each registry needs a route. Docker Hub and GHCR additionally need request, so each registry needs a route — **and so does the CDN it redirects
`preserve_auth: true` (their token dance uses a client-fetched per-scope bearer layer blobs to**, which is a different host. Without the CDN route the pull
token that the proxy would otherwise strip), and large layer pulls should skip authenticates, fetches the manifest, then 403s partway through.
body scanning:
Docker Hub and GHCR additionally need `preserve_auth: true`: their token dance
uses a client-fetched per-scope bearer token that the proxy would otherwise
strip. Turn DLP off on every registry and CDN route — the bodies are
compressed layer blobs that no detector can read, and buffering them is what
triggers the shared-proxy OOM in #455.
```yaml ```yaml
nested_containers: true nested_containers: true
egress: egress:
routes: routes:
# Docker Hub: registry, token endpoint, blob CDN.
- host: registry-1.docker.io - host: registry-1.docker.io
preserve_auth: true preserve_auth: true
dlp: { outbound_detectors: false, inbound_detectors: false } dlp: { outbound_detectors: false, inbound_detectors: false }
- host: auth.docker.io - host: auth.docker.io
preserve_auth: true preserve_auth: true
dlp: { outbound_detectors: false, inbound_detectors: false }
- host: production.cloudfront.docker.com
dlp: { outbound_detectors: false, inbound_detectors: false }
# GHCR: registry + blob CDN.
- host: ghcr.io - host: ghcr.io
preserve_auth: true preserve_auth: true
dlp: { outbound_detectors: false, inbound_detectors: false } dlp: { outbound_detectors: false, inbound_detectors: false }
- host: pkg-containers.githubusercontent.com
dlp: { outbound_detectors: false, inbound_detectors: false }
# quay.io: registry + blob CDNs. No preserve_auth needed for public pulls.
- host: quay.io - host: quay.io
dlp: { outbound_detectors: false, inbound_detectors: false } dlp: { outbound_detectors: false, inbound_detectors: false }
# Registries redirect layer blobs to a separate CDN host, which needs its
# own route or the pull 403s partway through.
- host: production.cloudfront.docker.com
- host: pkg-containers.githubusercontent.com
- host: cdn01.quay.io - host: cdn01.quay.io
dlp: { outbound_detectors: false, inbound_detectors: false }
- host: cdn02.quay.io
dlp: { outbound_detectors: false, inbound_detectors: false }
- host: cdn03.quay.io
dlp: { outbound_detectors: false, inbound_detectors: false }
``` ```
Inside a nested container the same allowlist applies — an allowlisted host `mcr.microsoft.com` and `registry.k8s.io` follow the same shape and also
returns 200 and anything else gets a 403 from the proxy. The gateway's CA and redirect blobs elsewhere (`*.data.mcr.microsoft.com` and
proxy settings are wired in automatically, so `docker run … curl https://…` `us-*-docker.pkg.dev` respectively); route whichever host the 403 names.
works with no extra flags. One caveat: Alpine's BusyBox `wget` drops the
connection after TLS interception and reports "error getting response"; `curl` Inside a nested container the same allowlist applies: an allowlisted host
against the same host works, so reach for `curl` when testing egress. returns 200 and anything else gets a 403 straight from the proxy. The
gateway's CA bundle and proxy settings are wired in automatically, so
`docker run … curl https://…` works with no extra flags — no `--add-host`,
`-e`, or `-v`.
Two things worth knowing when testing that:
- Public DNS inside a nested container fails **by design**. Everything
egresses through the proxy, so `nslookup` failing is expected and is not
evidence of a problem.
- Alpine's BusyBox `wget` drops the connection after the proxy's TLS
interception and reports `error getting response`, even though the proxy
logs the decrypted request and returns a response. Use `curl` to test
egress; BusyBox `wget` will lie to you.
### Firecracker on Linux ### Firecracker on Linux