Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions go/pkg/credhelper/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ func seedAuthHeaders(host docker.RegistryHost) error {
return nil
}

// bearerAuthFixTransport intercepts requests where containerd has set
// Authorization: Basic base64("Bearer:<JWT>") (from a credential helper returning
// Username="Bearer") and converts them to Authorization: Bearer <JWT>.
// 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
Expand Down Expand Up @@ -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==<JWT>). containerd's WithAuthCreds flow would construct
// Authorization: Basic base64("Bearer:<JWT>") for the token endpoint, which
// the registry rejects with 400 Bad Request.
//
// Fix: use a custom transport that converts Basic("Bearer", <JWT>) →
// Bearer <JWT> 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
}))

Expand Down
Loading