diff --git a/util/imagetools/auth.go b/util/imagetools/auth.go index 11ac0d874..0ddeac8bc 100644 --- a/util/imagetools/auth.go +++ b/util/imagetools/auth.go @@ -1,28 +1,70 @@ package imagetools import ( + "context" "encoding/base64" "encoding/json" + "net/http" + "sync" + "time" + "github.com/containerd/containerd/v2/core/remotes/docker" "github.com/distribution/reference" + "github.com/docker/cli/cli/config/types" ) -func toCredentialsFunc(a Auth) func(string) (string, string, error) { - return func(host string) (string, string, error) { - if host == "registry-1.docker.io" { - host = "https://index.docker.io/v1/" - } - ac, err := a.GetAuthConfig(host) - if err != nil { - return "", "", err - } - if ac.IdentityToken != "" { - return "", ac.IdentityToken, nil - } - return ac.Username, ac.Password, nil +type authConfig struct { + mu sync.Mutex + authConfigCache map[string]authConfigCacheEntry + cfg Auth +} + +type authConfigCacheEntry struct { + Created time.Time + Auth types.AuthConfig +} + +func newAuthConfig(a Auth) *authConfig { + return &authConfig{ + authConfigCache: map[string]authConfigCacheEntry{}, + cfg: a, } } +func (a *authConfig) credentials(host string) (string, string, error) { + ac, err := a.authConfig(host) + if err != nil { + return "", "", err + } + if ac.IdentityToken != "" { + return "", ac.IdentityToken, nil + } + return ac.Username, ac.Password, nil +} + +func (a *authConfig) authConfig(host string) (types.AuthConfig, error) { + const defaultExpiration = 2 * time.Minute + + if host == "registry-1.docker.io" { + host = "https://index.docker.io/v1/" + } + a.mu.Lock() + defer a.mu.Unlock() + + if c, ok := a.authConfigCache[host]; ok && time.Since(c.Created) <= defaultExpiration { + return c.Auth, nil + } + ac, err := a.cfg.GetAuthConfig(host) + if err != nil { + return types.AuthConfig{}, err + } + a.authConfigCache[host] = authConfigCacheEntry{ + Created: time.Now(), + Auth: ac, + } + return ac, nil +} + func RegistryAuthForRef(ref string, a Auth) (string, error) { if a == nil { return "", nil @@ -45,3 +87,17 @@ func RegistryAuthForRef(ref string, a Auth) (string, error) { } return base64.URLEncoding.EncodeToString(buf), nil } + +type withBearerAuthorizer struct { + docker.Authorizer + AuthConfig *authConfig +} + +func (a *withBearerAuthorizer) Authorize(ctx context.Context, req *http.Request) error { + ac, err := a.AuthConfig.authConfig(req.Host) + if err == nil && ac.RegistryToken != "" { + req.Header.Set("Authorization", "Bearer "+ac.RegistryToken) + return nil + } + return a.Authorizer.Authorize(ctx, req) +} diff --git a/util/imagetools/inspect.go b/util/imagetools/inspect.go index 09de3b916..c6e9c1b32 100644 --- a/util/imagetools/inspect.go +++ b/util/imagetools/inspect.go @@ -34,8 +34,14 @@ type Resolver struct { } func New(opt Opt) *Resolver { + ac := newAuthConfig(opt.Auth) + dockerAuth := docker.NewDockerAuthorizer(docker.WithAuthCreds(ac.credentials), docker.WithAuthClient(http.DefaultClient)) + auth := &withBearerAuthorizer{ + Authorizer: dockerAuth, + AuthConfig: ac, + } return &Resolver{ - auth: docker.NewDockerAuthorizer(docker.WithAuthCreds(toCredentialsFunc(opt.Auth)), docker.WithAuthClient(http.DefaultClient)), + auth: auth, hosts: resolver.NewRegistryConfig(opt.RegistryConfig), buffer: contentutil.NewBuffer(), }