diff --git a/internal/ocidocker/dockerhub.go b/internal/ocidocker/dockerhub.go index 8994861..b56df8c 100644 --- a/internal/ocidocker/dockerhub.go +++ b/internal/ocidocker/dockerhub.go @@ -1,9 +1,24 @@ package ocidocker -var DockerHubHosts = map[string]struct{}{ - "": {}, - "docker.io": {}, - "index.docker.io": {}, - "registry-1.docker.io": {}, - "registry.hub.docker.com": {}, -} +import ( + "sync" +) + +var ( + // DockerHubHosts is a map of hostnames associated with Docker Hub's registry. The number is a priority ranking + // used to determine the order we will search for auth credentials + DockerHubHosts = map[string]byte{ + "docker.io": 0, + "index.docker.io": 1, + "registry-1.docker.io": 2, + "registry.hub.docker.com": 3, + } + // DockerHubHostsSorted is a slice of hostnames associated with Docker Hub's registry, sorted by their ranking + DockerHubHostsSorted = sync.OnceValue(func() []string { + sorted := make([]string, len(DockerHubHosts)) + for host, rank := range DockerHubHosts { + sorted[rank] = host + } + return sorted + }) +) diff --git a/internal/ocidocker/dockerhub_test.go b/internal/ocidocker/dockerhub_test.go new file mode 100644 index 0000000..44e3abd --- /dev/null +++ b/internal/ocidocker/dockerhub_test.go @@ -0,0 +1,12 @@ +package ocidocker + +import "testing" + +func TestHostsSorted(t *testing.T) { + sorted := DockerHubHostsSorted() + for i, host := range sorted { + if i != int(DockerHubHosts[host]) { + t.Errorf("host %s not sorted", host) + } + } +} diff --git a/ociauth/auth.go b/ociauth/auth.go index c809bca..e83e9f6 100644 --- a/ociauth/auth.go +++ b/ociauth/auth.go @@ -293,7 +293,7 @@ func (r *registry) setAuthorizationFromChallenge(ctx context.Context, req *http. // the outer context is cancelled, but we'll ignore that. We probably shouldn't. func (r *registry) init() error { inner := func() error { - info, err := r.config.EntryForRegistry(r.host) + info, err := dockerWrapper{r.config}.EntryForRegistry(r.host) if err != nil { return fmt.Errorf("cannot acquire auth info for registry %q: %v", r.host, err) } diff --git a/ociauth/docker.go b/ociauth/docker.go new file mode 100644 index 0000000..44984c2 --- /dev/null +++ b/ociauth/docker.go @@ -0,0 +1,31 @@ +package ociauth + +import ( + "github.com/docker/oci/internal/ocidocker" +) + +// DockerWrapper is an ociauth.Config implementation that wraps an underlying ociauth.Config in order to check +// credentials for every Docker Hub domain when credentials for any Hub domain are requested. +type dockerWrapper struct { + Config +} + +// EntryForRegistry returns credentials for a given host. +func (w dockerWrapper) EntryForRegistry(host string) (ConfigEntry, error) { + var zero ConfigEntry // "EntryForRegistry" doesn't return an error on a miss - it just returns an empty object (so we create this to have something to trivially compare against for our fallback) + if entry, err := w.Config.EntryForRegistry(host); err == nil && entry != zero { + return entry, err + } else if _, ok := ocidocker.DockerHubHosts[host]; ok { + for _, dockerHubHost := range ocidocker.DockerHubHostsSorted() { + if dockerHubHost == "" { + continue + } + if entry, err = w.Config.EntryForRegistry(dockerHubHost); err == nil && entry != zero { + return entry, err + } + } + return entry, err + } else { + return entry, err + } +} diff --git a/ociref/reference.go b/ociref/reference.go index 4c7ac1a..5c18850 100644 --- a/ociref/reference.go +++ b/ociref/reference.go @@ -196,7 +196,8 @@ func ParseRelative(refStr string) (Reference, error) { return Reference{}, fmt.Errorf("invalid digest %q: %v", ref.Digest, err) } } - if _, ok := ocidocker.DockerHubHosts[ref.Host]; ok { + // Normalize Docker Hub registry hosts, also default to Docker if none is provided + if _, ok := ocidocker.DockerHubHosts[ref.Host]; ok || ref.Host == "" { ref.Host = "docker.io" } if ref.Host == "docker.io" && !strings.Contains(ref.Repository, "/") {