diff --git a/go/pkg/credhelper/docker.go b/go/pkg/credhelper/docker.go index e4746d3..a3022bf 100644 --- a/go/pkg/credhelper/docker.go +++ b/go/pkg/credhelper/docker.go @@ -99,6 +99,23 @@ func seedAuthHeaders(host docker.RegistryHost) error { return nil } +// bearerAuthFixTransport intercepts requests where containerd has set +// Authorization: Basic base64("Bearer:") (from a credential helper returning +// Username="Bearer") and converts them to Authorization: Bearer . +// This allows the token endpoint to issue a proper scope-specific token, which +// containerd then uses for all subsequent registry requests (manifests and blobs). +type bearerAuthFixTransport struct { + token string +} + +func (t *bearerAuthFixTransport) RoundTrip(req *http.Request) (*http.Response, error) { + clone := req.Clone(req.Context()) + if username, _, ok := clone.BasicAuth(); ok && username == "Bearer" { + clone.Header.Set("Authorization", "Bearer "+t.token) + } + return http.DefaultTransport.RoundTrip(clone) +} + func RegistryHostsFromDockerConfig() docker.RegistryHosts { return func(host string) ([]docker.RegistryHost, error) { // FIXME This should be cached somewhere @@ -126,14 +143,42 @@ func RegistryHostsFromDockerConfig() docker.RegistryHosts { return []docker.RegistryHost{registryHost}, nil } - registryHost.Authorizer = docker.NewDockerAuthorizer(docker.WithAuthCreds(func(host string) (string, string, error) { - p := helperclient.NewShellProgramFunc(fmt.Sprintf("docker-credential-%s", helperName)) + p := helperclient.NewShellProgramFunc(fmt.Sprintf("docker-credential-%s", helperName)) + creds, err := helperclient.Get(p, fmt.Sprintf("%s://%s", registryHost.Scheme, registryHost.Host)) + if err != nil { + return nil, err + } - creds, err := helperclient.Get(p, fmt.Sprintf("%s://%s", registryHost.Scheme, registryHost.Host)) + if creds.Username == "Bearer" { + // The credential helper returned a pre-issued Bearer token (Username=="Bearer", + // Secret==). containerd's WithAuthCreds flow would construct + // Authorization: Basic base64("Bearer:") for the token endpoint, which + // the registry rejects with 400 Bad Request. + // + // Fix: use a custom transport that converts Basic("Bearer", ) → + // Bearer on the wire. This lets the token endpoint issue a proper + // scope-specific token, which containerd then uses for all registry requests + // (manifests and blobs). + customClient := &http.Client{ + Transport: &bearerAuthFixTransport{token: creds.Secret}, + } + registryHost.Client = customClient + registryHost.Authorizer = docker.NewDockerAuthorizer( + docker.WithAuthCreds(func(host string) (string, string, error) { + return creds.Username, creds.Secret, nil + }), + docker.WithAuthClient(customClient), + ) + + err = seedAuthHeaders(registryHost) if err != nil { - return "", "", err + return nil, err } + return []docker.RegistryHost{registryHost}, nil + } + + registryHost.Authorizer = docker.NewDockerAuthorizer(docker.WithAuthCreds(func(host string) (string, string, error) { return creds.Username, creds.Secret, nil }))