diff --git a/allowlist.txt b/allowlist.txt index 5ef3743..3458c21 100644 --- a/allowlist.txt +++ b/allowlist.txt @@ -73,11 +73,10 @@ GET https://registry-1.docker.io/v2/ GET https://registry-1.docker.io/v2/library/hello-world/* # Auth tokens — scoped to the hello-world repository. GET https://auth.docker.io/token* -# Blob storage — the registry redirects layer downloads to either -# a Cloudflare R2 bucket or CloudFront CDN. Blob paths contain -# sha256 digests that can't be scoped per-image, but the registry -# only returns redirect URLs for layers belonging to images the -# client already resolved via the scoped manifest rules above. +# Blob storage — the registry redirects layer downloads to a CDN. +# Paths contain per-blob sha256 digests, so they are wildcarded, but +# scoped to the /registry-v2/ prefix both CDNs serve them under. +# Docker Hub hands out both of these depending on the request. GET https://docker-images-prod.6aa30f8b08e16409b46e0173d6de2f56.r2.cloudflarestorage.com/registry-v2/* GET https://production.cloudfront.docker.com/registry-v2/* diff --git a/tests/test_filter.py b/tests/test_filter.py index d53d58f..f31ae4b 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -12,6 +12,7 @@ import sys from pathlib import Path from unittest.mock import MagicMock +from urllib.parse import urlparse import pytest @@ -254,3 +255,41 @@ def test_allows_head_when_get_rule_matches(self, filter_env): flow = _make_flow("HEAD", "example.com", "https://example.com/page") addon.request(flow) assert flow.response is None # HEAD permitted by GET rule + + +# --------------------------------------------------------------------------- +# The shipped allowlist.txt (not synthetic rules) +# --------------------------------------------------------------------------- + +class TestShippedAllowlist: + """Checks the real allowlist.txt covers what the e2e suite needs. + + These are the URLs a `docker pull` actually requests. Docker Hub serves + blobs from more than one CDN and picks per request, so allowing only one of + them yields an intermittent-looking failure: the manifest fetch succeeds, + then the layer download dies with a proxy 418 and docker reports + `error pulling image configuration`, which reads as a network fault rather + than a missing allowlist rule. + """ + + @pytest.mark.parametrize("url", [ + # Registry API: manifests and config blobs. + "https://registry-1.docker.io/v2/library/hello-world/manifests/latest", + # Auth token for the anonymous pull. + "https://auth.docker.io/token?scope=repository%3Alibrary%2Fhello-world%3Apull", + # Blob CDNs — both are in play; paths carry per-blob sha256 digests. + "https://docker-images-prod.6aa30f8b08e16409b46e0173d6de2f56." + "r2.cloudflarestorage.com/registry-v2/docker/registry/v2/blobs/" + "sha256/58/58dee6a49ef1/data", + "https://production.cloudfront.docker.com/registry-v2/docker/registry/" + "v2/blobs/sha256/58/58dee6a49ef1/data", + ]) + def test_docker_pull_urls_allowed(self, url): + rules = fm.parse_allowlist(fm.ALLOWLIST_PATH) + host = urlparse(url).hostname + assert fm.is_allowed(rules, "GET", host, url), ( + f"allowlist.txt blocks a URL `docker pull hello-world` needs:\n" + f" {url}\n" + "test_docker_hello_world in the e2e suite will fail with a proxy " + "418 that looks like a network error." + )