From da426ecd3a355bec7f34dcf3af9bbb76dd136c2a Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 10 Jun 2025 23:20:08 -0700 Subject: [PATCH] imagetools: support registrytoken auth in docker config This is not supported by the Authorizer from containerd and needs to be added manually. Build authentication happens through BuildKit session that already supports this. Signed-off-by: Tonis Tiigi --- util/imagetools/auth.go | 82 ++++++++++++++++++++++++++++++++------ util/imagetools/inspect.go | 8 +++- 2 files changed, 76 insertions(+), 14 deletions(-) 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(), }