imagetools: use dockerconfig for auth

Enables fallback for DHI and Scout registries and
repo/scope specific credentials like supported for builds.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-01-26 10:15:14 -08:00
parent e254cf1985
commit 27dde04ab5
7 changed files with 39 additions and 82 deletions
-5
View File
@@ -8,7 +8,6 @@ import (
"github.com/docker/buildx/store" "github.com/docker/buildx/store"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
clitypes "github.com/docker/cli/cli/config/types"
controlapi "github.com/moby/buildkit/api/services/control" controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -58,10 +57,6 @@ type Info struct {
DynamicNodes []store.Node DynamicNodes []store.Node
} }
type Auth interface {
GetAuthConfig(registryHostname string) (clitypes.AuthConfig, error)
}
type Driver interface { type Driver interface {
Factory() Factory Factory() Factory
Bootstrap(context.Context, progress.Logger) error Bootstrap(context.Context, progress.Logger) error
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/context/store" "github.com/docker/cli/cli/context/store"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/tracing/delegated" "github.com/moby/buildkit/util/tracing/delegated"
dockerclient "github.com/moby/moby/client" dockerclient "github.com/moby/moby/client"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1" ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
@@ -35,7 +36,7 @@ type InitConfig struct {
BuildkitdFlags []string BuildkitdFlags []string
Files map[string][]byte Files map[string][]byte
DriverOpts map[string]string DriverOpts map[string]string
Auth Auth Auth authprovider.AuthConfigProvider
Platforms []ocispecs.Platform Platforms []ocispecs.Platform
ContextPathHash string ContextPathHash string
DialMeta map[string][]string DialMeta map[string][]string
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/docker/buildx/store" "github.com/docker/buildx/store"
"github.com/docker/buildx/util/confutil" "github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/dockerutil/dockerconfig"
"github.com/docker/buildx/util/imagetools" "github.com/docker/buildx/util/imagetools"
"github.com/docker/buildx/util/resolver" "github.com/docker/buildx/util/resolver"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
@@ -109,7 +110,7 @@ func GetNodeGroup(txn *store.Txn, dockerCli command.Cli, name string) (*store.No
} }
func GetImageConfig(dockerCli command.Cli, ng *store.NodeGroup) (opt imagetools.Opt, err error) { func GetImageConfig(dockerCli command.Cli, ng *store.NodeGroup) (opt imagetools.Opt, err error) {
opt.Auth = dockerCli.ConfigFile() opt.Auth = dockerconfig.LoadAuthConfig(dockerCli)
if ng == nil || len(ng.Nodes) == 0 { if ng == nil || len(ng.Nodes) == 0 {
return opt, nil return opt, nil
@@ -43,6 +43,13 @@ type authConfigProvider struct {
} }
func (ap *authConfigProvider) load(ctx context.Context, host string, scopes []string, cacheExpireCheck authprovider.ExpireCachedAuthCheck) (types.AuthConfig, error) { func (ap *authConfigProvider) load(ctx context.Context, host string, scopes []string, cacheExpireCheck authprovider.ExpireCachedAuthCheck) (types.AuthConfig, error) {
if cacheExpireCheck == nil {
cacheExpireCheck = func(created time.Time, _ string) bool {
// Tokens for Google Artifact Registry via Workload Identity expire after 5 minutes.
return time.Since(created) > 4*time.Minute+50*time.Second
}
}
ac, err := ap.loadHost(ctx, host, scopes, cacheExpireCheck) ac, err := ap.loadHost(ctx, host, scopes, cacheExpireCheck)
if err != nil { if err != nil {
return types.AuthConfig{}, err return types.AuthConfig{}, err
+6 -60
View File
@@ -5,68 +5,14 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"net/http" "net/http"
"sync"
"time"
"github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/v2/core/remotes/docker"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/docker/cli/cli/config/types" "github.com/moby/buildkit/session/auth/authprovider"
) )
type authConfig struct { func RegistryAuthForRef(ref string, auth authprovider.AuthConfigProvider) (string, error) {
mu sync.Mutex if auth == nil {
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 return "", nil
} }
r, err := parseRef(ref) r, err := parseRef(ref)
@@ -77,7 +23,7 @@ func RegistryAuthForRef(ref string, a Auth) (string, error) {
if host == "docker.io" { if host == "docker.io" {
host = "https://index.docker.io/v1/" host = "https://index.docker.io/v1/"
} }
ac, err := a.GetAuthConfig(host) ac, err := auth(context.TODO(), host, nil, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -90,11 +36,11 @@ func RegistryAuthForRef(ref string, a Auth) (string, error) {
type withBearerAuthorizer struct { type withBearerAuthorizer struct {
docker.Authorizer docker.Authorizer
AuthConfig *authConfig AuthConfig authprovider.AuthConfigProvider
} }
func (a *withBearerAuthorizer) Authorize(ctx context.Context, req *http.Request) error { func (a *withBearerAuthorizer) Authorize(ctx context.Context, req *http.Request) error {
ac, err := a.AuthConfig.authConfig(req.Host) ac, err := a.AuthConfig(ctx, req.Host, nil, nil)
if err == nil && ac.RegistryToken != "" { if err == nil && ac.RegistryToken != "" {
req.Header.Set("Authorization", "Bearer "+ac.RegistryToken) req.Header.Set("Authorization", "Bearer "+ac.RegistryToken)
return nil return nil
+4 -9
View File
@@ -12,19 +12,15 @@ import (
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/docker/buildx/util/resolver" "github.com/docker/buildx/util/resolver"
"github.com/docker/buildx/util/resolver/auth" "github.com/docker/buildx/util/resolver/auth"
clitypes "github.com/docker/cli/cli/config/types" "github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/contentutil"
"github.com/moby/buildkit/util/tracing" "github.com/moby/buildkit/util/tracing"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1" ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type Auth interface {
GetAuthConfig(registryHostname string) (clitypes.AuthConfig, error)
}
type Opt struct { type Opt struct {
Auth Auth Auth authprovider.AuthConfigProvider
RegistryConfig map[string]resolver.RegistryConfig RegistryConfig map[string]resolver.RegistryConfig
} }
@@ -35,11 +31,10 @@ type Resolver struct {
} }
func New(opt Opt) *Resolver { func New(opt Opt) *Resolver {
ac := newAuthConfig(opt.Auth) dockerAuth := auth.NewDockerAuthorizer(auth.WithAuthProvider(opt.Auth), auth.WithAuthClient(http.DefaultClient))
dockerAuth := auth.NewDockerAuthorizer(auth.WithAuthCreds(ac.credentials), auth.WithAuthClient(http.DefaultClient))
auth := &withBearerAuthorizer{ auth := &withBearerAuthorizer{
Authorizer: dockerAuth, Authorizer: dockerAuth,
AuthConfig: ac, AuthConfig: opt.Auth,
} }
return &Resolver{ return &Resolver{
auth: auth, auth: auth,
+18 -6
View File
@@ -14,13 +14,15 @@ import (
"github.com/containerd/containerd/v2/core/remotes/docker/auth" "github.com/containerd/containerd/v2/core/remotes/docker/auth"
remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors" remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors"
"github.com/containerd/errdefs" "github.com/containerd/errdefs"
"github.com/docker/cli/cli/config/types"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/bklog" "github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type dockerAuthorizer struct { type dockerAuthorizer struct {
credentials func(string) (string, string, error) credentials authprovider.AuthConfigProvider
client *http.Client client *http.Client
header http.Header header http.Header
@@ -31,7 +33,7 @@ type dockerAuthorizer struct {
} }
type authorizerConfig struct { type authorizerConfig struct {
credentials func(string) (string, string, error) credentials authprovider.AuthConfigProvider
client *http.Client client *http.Client
header http.Header header http.Header
} }
@@ -47,9 +49,9 @@ func WithAuthClient(client *http.Client) AuthorizerOpt {
} }
// WithAuthCreds provides a credential function to the authorizer // WithAuthCreds provides a credential function to the authorizer
func WithAuthCreds(creds func(string) (string, string, error)) AuthorizerOpt { func WithAuthProvider(provider authprovider.AuthConfigProvider) AuthorizerOpt {
return func(opt *authorizerConfig) { return func(opt *authorizerConfig) {
opt.credentials = creds opt.credentials = provider
} }
} }
@@ -143,10 +145,11 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
var username, secret string var username, secret string
if a.credentials != nil { if a.credentials != nil {
var err error var err error
username, secret, err = a.credentials(host) ac, err := a.credentials(ctx, host, strings.Split(c.Parameters["scope"], " "), nil)
if err != nil { if err != nil {
return err return err
} }
username, secret = parseAuthConfig(ac)
} }
common, err := auth.GenerateTokenOptions(ctx, host, username, secret, c) common, err := auth.GenerateTokenOptions(ctx, host, username, secret, c)
@@ -157,11 +160,13 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common) a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common)
return nil return nil
} else if c.Scheme == auth.BasicAuth && a.credentials != nil { } else if c.Scheme == auth.BasicAuth && a.credentials != nil {
username, secret, err := a.credentials(host) ac, err := a.credentials(ctx, host, nil, nil)
if err != nil { if err != nil {
return err return err
} }
username, secret := parseAuthConfig(ac)
if username == "" || secret == "" { if username == "" || secret == "" {
return errors.Wrap(err, "no basic auth credentials") return errors.Wrap(err, "no basic auth credentials")
} }
@@ -176,6 +181,13 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
return errors.Wrap(errdefs.ErrNotImplemented, "failed to find supported auth scheme") return errors.Wrap(errdefs.ErrNotImplemented, "failed to find supported auth scheme")
} }
func parseAuthConfig(ac types.AuthConfig) (string, string) {
if ac.IdentityToken != "" {
return "", ac.IdentityToken
}
return ac.Username, ac.Password
}
// authResult is used to control limit rate. // authResult is used to control limit rate.
type authResult struct { type authResult struct {
sync.WaitGroup sync.WaitGroup