Skip to content

fix(login): accept equivalent registry hosts in auth creds callback - #5128

Open
xianyuwu wants to merge 2 commits into
containerd:mainfrom
xianyuwu:fix/login-host-port-mismatch
Open

fix(login): accept equivalent registry hosts in auth creds callback#5128
xianyuwu wants to merge 2 commits into
containerd:mainfrom
xianyuwu:fix/login-host-port-mismatch

Conversation

@xianyuwu

@xianyuwu xianyuwu commented Aug 7, 2026

Copy link
Copy Markdown

PR Title

fix(login): accept equivalent registry hosts in auth creds callback

PR Body

Summary

nerdctl login fails against registries served on the default HTTPS port (443)
with:

FATA failed to call rh.Authorizer.AddResponses: expected acArg to be "harbor.example.io:443", got "harbor.example.io"

docker login works fine against the same registry.

Fixes #3992
Refs #3245

Root cause

In pkg/cmd/login/login.go, the credentials callback passed to the containerd
authorizer used a strict string equality check:

authCreds := func(acArg string) (string, string, error) {
    if acArg == host { ... }
    return "", "", fmt.Errorf("expected acArg to be %q, got %q", host, acArg)
}

However, the two sides of the comparison are built differently:

  • host comes from dockerconfigresolver.Parse(), which appends the
    standard HTTPS port explicitly
    when the user did not specify one
    (registryurl.go), so host is harbor.example.io:443.
  • acArg is passed by containerd's dockerAuthorizer.AddResponses() as
    last.Request.URL.Host, i.e. the host of the actual request, which is
    harbor.example.io — without the default port.

The same fragility produces #3245: logging in to docker.io resolves to
index.docker.io:443, while the actual registry endpoint calling back is
registry-1.docker.io.

Fix

Replace the strict equality check with an equivalence check
(isEquivalentRegistryHost) that additionally accepts:

  1. The same hostname with the default HTTPS port (443) omitted — when the user
    did not explicitly configure a non-default port.
  2. Docker Hub aliases: index.docker.io (the address docker.io resolves to)
    and registry-1.docker.io (the actual registry endpoint).

Callback hosts carrying an explicit non-standard port still must match
exactly, so credentials are never served to a different endpoint.

This mirrors the equivalence rules already encoded in
RegistryURL.AllIdentifiers() for credential lookup.

Test plan

Added pkg/cmd/login/login_test.go with table-driven cases covering:

$ go test -v -run TestLoginAuthCredsAcceptsEquivalentHosts ./pkg/cmd/login/...
--- PASS: TestLoginAuthCredsAcceptsEquivalentHosts (0.00s)
    --- PASS: .../exact_host_with_standard_port
    --- PASS: .../host_without_default_port
    --- PASS: .../docker.io_alias_without_port
    --- PASS: .../docker.io_alias_with_port
    --- PASS: .../mismatched_host
    --- PASS: .../explicit_non-standard_port_not_dropped
    --- PASS: .../different_explicit_port
PASS
ok      github.com/containerd/nerdctl/v2/pkg/cmd/login

The failing scenarios from both issues were reproduced against a private Harbor
registry served on port 443 (nerdctl login --insecure-registry); the unit
test cases above encode exactly those mismatches.

Parse() appends the standard HTTPS port to the registry address, but the
containerd authorizer calls the credentials callback with the request URL
host, which omits the default port (or uses the registry-1.docker.io alias
for Docker Hub). The strict equality check then fails and login aborts.

Replace the strict equality check with an equivalence check that accepts
the same hostname with the default port omitted, and the Docker Hub
index.docker.io/registry-1.docker.io alias pair. Callback hosts with an
explicit non-standard port must still match exactly.

Fixes containerd#3992
Refs containerd#3245

Signed-off-by: rainwu <xianyuwu@foxmail.com>
@xianyuwu

Copy link
Copy Markdown
Author

The failing in-host / rootless linux job appears to be a flake unrelated to this change: the only failing test is TestComposeUpWithExternalNetwork (1 out of 1385), where nerdctl exec con-1 wget -qO- http://con-2 failed with wget: bad address — a container DNS resolution hiccup in the rootless environment. The cmd/nerdctl/login tests (which cover this PR) pass, and the other rootless variants (arm, slirp4netns, old ubuntu) are all green.

Unfortunately I don't have permission to re-run the job. Could a maintainer re-run the failed check? A review would also be much appreciated. @AkihiroSuda PTAL when you have a moment, thanks!

@AkihiroSuda
AkihiroSuda requested a review from fahedouch August 11, 2026 19:59
@AkihiroSuda AkihiroSuda added this to the v2.4.0 milestone Aug 20, 2026
@AkihiroSuda AkihiroSuda added the area/login authentification/ login label Aug 20, 2026
@AkihiroSuda
AkihiroSuda requested a balanced review from Copilot August 20, 2026 06:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes login credential callbacks for equivalent registry hosts and Docker Hub aliases.

Changes:

  • Adds host-equivalence logic for omitted HTTPS ports and Docker Hub aliases.
  • Adds table-driven credential callback tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
pkg/cmd/login/login.go Implements equivalent-host credential matching.
pkg/cmd/login/login_test.go Tests accepted and rejected host variants.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/cmd/login/login.go Outdated
Comment on lines +249 to +251
if registryURL.Hostname() == "index.docker.io" && acHost == "registry-1.docker.io" {
return true
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e604593 — the Docker Hub alias is now only honored when the login target uses the standard HTTPS port, and the index.docker.io:8443 rejection cases are covered in the test table.

Comment thread pkg/cmd/login/login.go
Comment on lines +232 to +236
acHost, acPort, err := net.SplitHostPort(acArg)
if err != nil {
// acArg carries no port
acHost, acPort = acArg, ""
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e604593 — the port-less fallback now goes through url.URL.Hostname(), so [::1] matches ::1. Added [::1] and [::1]:443 cases to the test table.

Address review feedback on the equivalent-host check:

- Only honor the index.docker.io -> registry-1.docker.io alias when the
  login target uses the standard HTTPS port, so a login to
  index.docker.io:<port> no longer leaks credentials to
  registry-1.docker.io.
- Normalize port-less callback hosts through url.URL.Hostname so
  bracketed IPv6 literals (e.g. [::1]) can match registryURL.Hostname.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: rainwu <xianyuwu@foxmail.com>
@xianyuwu
xianyuwu force-pushed the fix/login-host-port-mismatch branch from 71507f5 to e604593 Compare August 21, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/login authentification/ login

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to log in to private registry

3 participants