Docker Hub names and upstream auth
Docker Hub expands short official-image names and returns 401 when its token realm rejects a repository scope.
Official-image namespace
Docker Hub namespaces every repository by its owner: grafana/grafana belongs to the grafana organization. The
curated set Docker maintains (official images) belongs to an
organization too, named library, so ubuntu on the registry is library/ubuntu.
The short form is a client-side convenience. When you type docker pull ubuntu, the Docker daemon expands the reference
before it touches the network: no registry host means Docker Hub, no namespace means library, no tag means latest.
The registry protocol has no short names, only library/ubuntu.
Routed-proxy expansion
peryx serves the container protocol under a route: /v2/hub/ubuntu/manifests/latest. The name that arrives is whatever
the client put after the route, and a client that would have expanded ubuntu against Docker Hub does not expand it
against peryx.internal:4433, because that host is a separate registry with a repository called hub/ubuntu. peryx
strips the route and would pass ubuntu upstream.
Hub answers 401 to a request for a repository named ubuntu, not 404, because its auth layer runs before its
lookup: the token realm will not issue a pull token for a scope it does not recognize. So the failure of a routed pull
of a short name therefore looks like an authorization failure.
The library_prefix setting makes peryx do the expansion the client skipped,
on the upstream request alone. auto, the default, recognizes a Hub upstream by its host and prefixes a single-segment
name.
Registry-mirror behavior
When the Docker daemon uses peryx through registry-mirrors, short names need no peryx setting. The daemon resolves
ubuntu to library/ubuntu as part of its own reference parsing, then sends that full name to the mirror, which serves
an empty route. peryx receives library/ubuntu and passes it upstream verbatim, because a name with a namespace is
never rewritten.
The two modes differ in who expands the name. In registry-mirror mode the daemon does it and peryx sees the result. In
routed mode nothing expands it, so peryx does. Both end up asking Hub for library/ubuntu.
Upstream 401 diagnosis
Earlier versions folded an upstream 401 into "this member does not have it", which reached the client as
MANIFEST_UNKNOWN: a pull of an official image by its short name reported a missing manifest, when the real cause was
Hub refusing the request. Since #108, an upstream 401 surfaces as
itself:
{
"errors": [
{
"code": "UNAUTHORIZED",
"message": "upstream registry refused authentication for this manifest"
}
]
}
The status is 401. A cached index asks no credentials of its own clients, so this response reports an upstream
rejection. Check these causes:
- The repository name reaching the upstream is not one it will serve. On a Hub proxy, check
library_prefixand the spelling of the name. - The index's upstream credentials (
username,password,token) are wrong or expired. - The account behind those credentials cannot see that repository.
A 404 still means absent, and still reaches the client as MANIFEST_UNKNOWN or BLOB_UNKNOWN. A 403 also counts as
absent, since a registry answers it for a repository it will not show anonymously, and a
virtual index walks on to its next member.
Cached-image fallback
A tag is mutable, so a cached index revalidates it upstream once its freshness window (cache_ttl_secs) elapses. If
that revalidation returns 401, peryx has failed to confirm whether the tag changed. It serves the cached manifest and
blobs within max_stale_secs past the freshness window, the same bound used when an upstream is unreachable (see
configuration).
With an expired upstream credential, cached content remains available within max_stale_secs; uncached content returns
401, and logs include the upstream status.
HTTPS token realms
The upstream 401 names its token realm in the WWW-Authenticate header, and peryx follows it to trade the index's
credentials for a bearer token. When those credentials are a username/password pair, peryx sends them as Basic
authentication, so a realm reached over plain http would put the secret on the wire in cleartext. A hostile or
compromised upstream that answers with realm="http://attacker/token" could harvest the mirror's credentials that way,
even over a TLS-valid connection to the registry itself.
peryx refuses to present Basic credentials to a token realm unless its scheme is https. The realm host is not
constrained: Docker Hub advertises auth.docker.io, a different host from the registry, and that stays valid. Every
token servers should use https. A loopback realm (localhost) is the only http exception because the credentials
remain on the machine.