diff --git a/docs/ci-cd.md b/docs/ci-cd.md index e73683619..ad710297a 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -2454,7 +2454,7 @@ because they send you to different places: | HTTP 200, body is not a manifest | **red** | something is answering for the registry (proxy, login page) | | 401 / 403 that survives a bearer the run actually obtained | **red** | the registry `refused an ANONYMOUS read ... even after a Bearer token was obtained` — about the PACKAGE: check that `timothy/ersatztv` and its `ersatztv-ci` package are still PUBLIC | | 401 whose token leg yielded no bearer — no `Www-Authenticate` at all, a challenge naming no realm, or a token endpoint answering with no token | **red** | `could NOT OBTAIN an anonymous pull token` — about the registry's TOKEN ENDPOINT, not the package's visibility. Not retried | -| 403 on the FIRST read, before any token leg — `probe` enters it on a `401` only, so nothing was ever asked for | **red** | `refused an ANONYMOUS read ... WITHOUT issuing a Bearer challenge` — about ACCESS to the registry: visibility, or something answering for it | +| 403 on the FIRST read, before any token leg — `probe` enters it on a `401` only | **red** | `refused an ANONYMOUS read ... and NO TOKEN WAS EVER REQUESTED` — it reports what the RUN did (never followed this answer as a challenge), not what the registry sent. About ACCESS to the registry: visibility, or something answering for it | | anything else (5xx, unreachable, no `curl`) | **red** after `ETV_CI_ATTEMPTS` tries | `could NOT VERIFY` — check the registry's health, NOT the pin | **The three refusal rows say only what actually ran**, and that is the point of wording them apart diff --git a/scripts/ci-toolchain-image-resolves.sh b/scripts/ci-toolchain-image-resolves.sh index edf8265de..41ea0dab1 100755 --- a/scripts/ci-toolchain-image-resolves.sh +++ b/scripts/ci-toolchain-image-resolves.sh @@ -203,10 +203,13 @@ for pin in $pins; do # * a challenge that yielded no usable token is an infrastructure answer about the TOKEN # ENDPOINT; # * a FIRST-READ 403 never reached the token leg at all — `probe` enters it on a 401 only — - # so nothing was ever asked for. This is an answer about ACCESS to the registry. Note the - # boundary: a 401 carrying no `Www-Authenticate` DOES enter the token leg and abandon it, - # so it lands in the row above, not this one. The branch order below is `token` first - # precisely so this case cannot borrow either of the other two mechanisms. + # so nothing was ever asked for. This is an answer about ACCESS to the registry. Its + # message says NO TOKEN WAS EVER REQUESTED rather than "the registry issued no challenge": + # a 403 MAY carry a `Www-Authenticate` and this script would still not follow it, so only + # the first is something the run observed. Note the boundary too: a 401 carrying no + # challenge DOES enter the token leg and abandon it, so it lands in the arm above, not + # this one. The branch order below is `token` first precisely so this case cannot borrow + # either of the other two mechanisms. # # A failed token leg is NOT retried, deliberately: `token_leg_done` is set before the attempt # so a registry genuinely refusing anonymous reads is asked once rather than once per pin. The @@ -218,7 +221,7 @@ for pin in $pins; do if [ "$token_leg_done" -eq 1 ]; then fail "could NOT OBTAIN an anonymous pull token for $registry/$image_repo:$pin — the registry answered HTTP $code and the token leg produced none: either there was no Www-Authenticate challenge, or it named no realm, or the token endpoint did not answer with a token. The pin was NOT checked. Look at the registry's token endpoint, not at the pin." fi - fail "the registry refused an ANONYMOUS read (HTTP $code) of $registry/$image_repo:$pin WITHOUT issuing a Bearer challenge, so no token was ever requested and the pin could not be checked. A challenge is what tells a client where a token can be had; an outright refusal is an answer about ACCESS to the registry, not about the pin. Every container: job pulls this image without a credential too, so they will fail at image pull. Check that timothy/ersatztv and its ersatztv-ci package are still PUBLIC, and that nothing (a proxy, an ACL) is answering for the registry — do not read this as a pass." + fail "the registry refused an ANONYMOUS read (HTTP $code) of $registry/$image_repo:$pin and NO TOKEN WAS EVER REQUESTED: the token leg is entered on a 401 only, so this answer was never followed as a Bearer challenge and the pin could not be checked. An outright refusal is an answer about ACCESS to the registry, not about the pin. Every container: job pulls this image without a credential too, so they will fail at image pull. Check that timothy/ersatztv and its ersatztv-ci package are still PUBLIC, and that nothing (a proxy, an ACL) is answering for the registry — do not read this as a pass." ;; *) # NOT gone, and NOT a pass either. Deliberately worded apart from the 404 message: this sends diff --git a/scripts/tests/test_ci_toolchain_image_resolves.py b/scripts/tests/test_ci_toolchain_image_resolves.py index 9a117eaa6..b974186c0 100644 --- a/scripts/tests/test_ci_toolchain_image_resolves.py +++ b/scripts/tests/test_ci_toolchain_image_resolves.py @@ -318,7 +318,7 @@ def test_a_refusal_with_NO_CHALLENGE_never_claims_a_token_was_obtained(preflight assert preflight.authenticated_manifest_calls() == [], "no read can have carried a token here" if refusal == "403": assert preflight.token_calls() == [], "a 403 first read must not even ask for a token" - assert "WITHOUT issuing a Bearer challenge" in result.stderr, result.stderr + assert "NO TOKEN WAS EVER REQUESTED" in result.stderr, result.stderr assert "PUBLIC" in result.stderr, "the message must still name a cause an operator can act on" else: # A challenge-less 401 DOES enter the token leg (and abandons it for want of a realm), so it