mirror of
https://github.com/moby/buildkit.git
synced 2026-11-06 05:16:45 +00:00
authprovider: make it possible to provide custom AuthConfig providers
This enables client to provide their own functionality for AuthConfig lookup, rather than just passing in dockercli.ConfigFile. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
4 files changed
+114
-73
No files matched your search
@@ -189,8 +189,8 @@ func buildAction(clicontext *cli.Context) error {
|
||||
}
|
||||
|
||||
attachable := []session.Attachable{authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
|
||||
ConfigFile: dockerConfig,
|
||||
TLSConfigs: tlsConfigs,
|
||||
AuthConfigProvider: authprovider.LoadAuthConfig(dockerConfig),
|
||||
TLSConfigs: tlsConfigs,
|
||||
})}
|
||||
|
||||
if ssh := clicontext.StringSlice("ssh"); len(ssh) > 0 {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package authprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
)
|
||||
|
||||
func LoadAuthConfig(config *configfile.ConfigFile) AuthConfigProvider {
|
||||
acp := &authConfigProvider{
|
||||
config: config,
|
||||
authConfigCache: map[string]authConfigCacheEntry{},
|
||||
}
|
||||
return acp.load
|
||||
}
|
||||
|
||||
type authConfigProvider struct {
|
||||
config *configfile.ConfigFile
|
||||
authConfigCache map[string]authConfigCacheEntry
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (ap *authConfigProvider) load(ctx context.Context, host string, scopes []string, cacheExpireCheck ExpireCachedAuthCheck) (types.AuthConfig, error) {
|
||||
ap.mu.Lock()
|
||||
defer ap.mu.Unlock()
|
||||
|
||||
entry, exists := ap.authConfigCache[host]
|
||||
if exists && (cacheExpireCheck == nil || !cacheExpireCheck(entry.Created, host)) {
|
||||
return *entry.Auth, nil
|
||||
}
|
||||
|
||||
hostKey := host
|
||||
if host == DockerHubRegistryHost {
|
||||
hostKey = DockerHubConfigfileKey
|
||||
}
|
||||
|
||||
ac, err := ap.config.GetAuthConfig(hostKey)
|
||||
if err != nil {
|
||||
return types.AuthConfig{}, err
|
||||
}
|
||||
|
||||
entry = authConfigCacheEntry{
|
||||
Created: time.Now(),
|
||||
Auth: &ac,
|
||||
}
|
||||
|
||||
ap.authConfigCache[host] = entry
|
||||
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
type authConfigCacheEntry struct {
|
||||
Created time.Time
|
||||
Auth *types.AuthConfig
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
authutil "github.com/containerd/containerd/v2/core/remotes/docker/auth"
|
||||
remoteserrors "github.com/containerd/containerd/v2/core/remotes/errors"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/moby/buildkit/session"
|
||||
@@ -36,13 +35,17 @@ import (
|
||||
|
||||
const (
|
||||
defaultExpiration = 60
|
||||
dockerHubConfigfileKey = "https://index.docker.io/v1/"
|
||||
dockerHubRegistryHost = "registry-1.docker.io"
|
||||
DockerHubConfigfileKey = "https://index.docker.io/v1/"
|
||||
DockerHubRegistryHost = "registry-1.docker.io"
|
||||
)
|
||||
|
||||
type AuthConfigProvider func(ctx context.Context, host string, scope []string, cacheCheck ExpireCachedAuthCheck) (types.AuthConfig, error)
|
||||
|
||||
type ExpireCachedAuthCheck func(created time.Time, serverURL string) bool
|
||||
|
||||
type DockerAuthProviderConfig struct {
|
||||
// ConfigFile is the docker config file
|
||||
ConfigFile *configfile.ConfigFile
|
||||
// AuthConfigProvider is a function that provides auth config for a given host and scope
|
||||
AuthConfigProvider AuthConfigProvider
|
||||
// TLSConfigs is a map of host to TLS config
|
||||
TLSConfigs map[string]*AuthTLSConfig
|
||||
// ExpireCachedAuth is a function that returns true auth config should be refreshed
|
||||
@@ -50,12 +53,7 @@ type DockerAuthProviderConfig struct {
|
||||
// If nil then the cached result will expire after 4 minutes and 50 seconds.
|
||||
// The function is called with the time the cached auth config was created
|
||||
// and the server URL the auth config is for.
|
||||
ExpireCachedAuth func(created time.Time, serverURL string) bool
|
||||
}
|
||||
|
||||
type authConfigCacheEntry struct {
|
||||
Created time.Time
|
||||
Auth *types.AuthConfig
|
||||
ExpireCachedAuth ExpireCachedAuthCheck
|
||||
}
|
||||
|
||||
func NewDockerAuthProvider(cfg DockerAuthProviderConfig) session.Attachable {
|
||||
@@ -66,23 +64,21 @@ func NewDockerAuthProvider(cfg DockerAuthProviderConfig) session.Attachable {
|
||||
}
|
||||
}
|
||||
return &authProvider{
|
||||
authConfigCache: map[string]authConfigCacheEntry{},
|
||||
expireAc: cfg.ExpireCachedAuth,
|
||||
config: cfg.ConfigFile,
|
||||
seeds: &tokenSeeds{dir: config.Dir()},
|
||||
loggerCache: map[string]struct{}{},
|
||||
tlsConfigs: cfg.TLSConfigs,
|
||||
expireAc: cfg.ExpireCachedAuth,
|
||||
provider: cfg.AuthConfigProvider,
|
||||
seeds: &tokenSeeds{dir: config.Dir()},
|
||||
loggerCache: map[string]struct{}{},
|
||||
tlsConfigs: cfg.TLSConfigs,
|
||||
}
|
||||
}
|
||||
|
||||
type authProvider struct {
|
||||
authConfigCache map[string]authConfigCacheEntry
|
||||
expireAc func(time.Time, string) bool
|
||||
config *configfile.ConfigFile
|
||||
seeds *tokenSeeds
|
||||
logger progresswriter.Logger
|
||||
loggerCache map[string]struct{}
|
||||
tlsConfigs map[string]*AuthTLSConfig
|
||||
expireAc func(time.Time, string) bool
|
||||
provider AuthConfigProvider
|
||||
seeds *tokenSeeds
|
||||
logger progresswriter.Logger
|
||||
loggerCache map[string]struct{}
|
||||
tlsConfigs map[string]*AuthTLSConfig
|
||||
|
||||
// The need for this mutex is not well understood.
|
||||
// Without it, the docker cli on OS X hangs when
|
||||
@@ -102,7 +98,7 @@ func (ap *authProvider) Register(server *grpc.Server) {
|
||||
}
|
||||
|
||||
func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequest) (rr *auth.FetchTokenResponse, err error) {
|
||||
ac, err := ap.getAuthConfig(ctx, req.Host)
|
||||
ac, err := ap.getAuthConfig(ctx, req.Host, req.Scopes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -112,11 +108,7 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ
|
||||
return toTokenResponse(ac.RegistryToken, time.Time{}, 0), nil
|
||||
}
|
||||
|
||||
creds, err := ap.credentials(ctx, req.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
creds := toCredentials(*ac)
|
||||
to := authutil.TokenOptions{
|
||||
Realm: req.Realm,
|
||||
Service: req.Service,
|
||||
@@ -215,11 +207,7 @@ func (ap *authProvider) tlsConfig(host string) (*tls.Config, error) {
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func (ap *authProvider) credentials(ctx context.Context, host string) (*auth.CredentialsResponse, error) {
|
||||
ac, err := ap.getAuthConfig(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func toCredentials(ac types.AuthConfig) *auth.CredentialsResponse {
|
||||
res := &auth.CredentialsResponse{}
|
||||
if ac.IdentityToken != "" {
|
||||
res.Secret = ac.IdentityToken
|
||||
@@ -227,12 +215,16 @@ func (ap *authProvider) credentials(ctx context.Context, host string) (*auth.Cre
|
||||
res.Username = ac.Username
|
||||
res.Secret = ac.Password
|
||||
}
|
||||
return res, nil
|
||||
return res
|
||||
}
|
||||
|
||||
func (ap *authProvider) Credentials(ctx context.Context, req *auth.CredentialsRequest) (*auth.CredentialsResponse, error) {
|
||||
resp, err := ap.credentials(ctx, req.Host)
|
||||
if err != nil || resp.Secret != "" {
|
||||
ac, err := ap.getAuthConfig(ctx, req.Host, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := toCredentials(*ac)
|
||||
if resp.Secret != "" {
|
||||
ap.mu.Lock()
|
||||
defer ap.mu.Unlock()
|
||||
_, ok := ap.loggerCache[req.Host]
|
||||
@@ -267,33 +259,22 @@ func (ap *authProvider) VerifyTokenAuthority(ctx context.Context, req *auth.Veri
|
||||
return &auth.VerifyTokenAuthorityResponse{Signed: sign.Sign(nil, req.Payload, priv)}, nil
|
||||
}
|
||||
|
||||
func (ap *authProvider) getAuthConfig(ctx context.Context, host string) (*types.AuthConfig, error) {
|
||||
func (ap *authProvider) getAuthConfig(ctx context.Context, host string, scopes []string) (*types.AuthConfig, error) {
|
||||
ap.mu.Lock()
|
||||
defer ap.mu.Unlock()
|
||||
|
||||
if host == dockerHubRegistryHost {
|
||||
host = dockerHubConfigfileKey
|
||||
var ac types.AuthConfig
|
||||
if ap.provider != nil {
|
||||
span, _ := tracing.StartSpan(ctx, fmt.Sprintf("load credentials for %s", host))
|
||||
res, err := ap.provider(ctx, host, scopes, ap.expireAc)
|
||||
tracing.FinishWithError(span, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ac = res
|
||||
}
|
||||
|
||||
entry, exists := ap.authConfigCache[host]
|
||||
if exists && !ap.expireAc(entry.Created, host) {
|
||||
return entry.Auth, nil
|
||||
}
|
||||
|
||||
span, _ := tracing.StartSpan(ctx, fmt.Sprintf("load credentials for %s", host))
|
||||
ac, err := ap.config.GetAuthConfig(host)
|
||||
tracing.FinishWithError(span, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry = authConfigCacheEntry{
|
||||
Created: time.Now(),
|
||||
Auth: &ac,
|
||||
}
|
||||
|
||||
ap.authConfigCache[host] = entry
|
||||
|
||||
return entry.Auth, nil
|
||||
return &ac, nil
|
||||
}
|
||||
|
||||
func (ap *authProvider) getAuthorityKey(ctx context.Context, host string, salt []byte) (ed25519.PrivateKey, error) {
|
||||
@@ -301,10 +282,12 @@ func (ap *authProvider) getAuthorityKey(ctx context.Context, host string, salt [
|
||||
return nil, status.Errorf(codes.Unavailable, "client side tokens disabled")
|
||||
}
|
||||
|
||||
creds, err := ap.credentials(ctx, host)
|
||||
ac, err := ap.getAuthConfig(ctx, host, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
creds := toCredentials(*ac)
|
||||
seed, err := ap.seeds.getSeed(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -16,21 +16,21 @@ func TestFetchTokenCaching(t *testing.T) {
|
||||
newCfg := func() *configfile.ConfigFile {
|
||||
return &configfile.ConfigFile{
|
||||
AuthConfigs: map[string]types.AuthConfig{
|
||||
dockerHubConfigfileKey: {Username: "user", RegistryToken: "hunter2"},
|
||||
DockerHubConfigfileKey: {Username: "user", RegistryToken: "hunter2"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
cfg := newCfg()
|
||||
p := NewDockerAuthProvider(DockerAuthProviderConfig{
|
||||
ConfigFile: cfg,
|
||||
AuthConfigProvider: LoadAuthConfig(cfg),
|
||||
}).(*authProvider)
|
||||
res, err := p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: dockerHubRegistryHost})
|
||||
res, err := p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: DockerHubRegistryHost})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hunter2", res.Token)
|
||||
|
||||
cfg.AuthConfigs[dockerHubConfigfileKey] = types.AuthConfig{Username: "user", RegistryToken: "hunter3"}
|
||||
res, err = p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: dockerHubRegistryHost})
|
||||
cfg.AuthConfigs[DockerHubConfigfileKey] = types.AuthConfig{Username: "user", RegistryToken: "hunter3"}
|
||||
res, err = p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: DockerHubRegistryHost})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify that we cached the result instead of returning hunter3.
|
||||
@@ -40,19 +40,19 @@ func TestFetchTokenCaching(t *testing.T) {
|
||||
|
||||
cfg = newCfg()
|
||||
p = NewDockerAuthProvider(DockerAuthProviderConfig{
|
||||
ConfigFile: cfg,
|
||||
AuthConfigProvider: LoadAuthConfig(cfg),
|
||||
ExpireCachedAuth: func(_ time.Time, host string) bool {
|
||||
require.Equal(t, dockerHubConfigfileKey, host)
|
||||
require.Equal(t, DockerHubRegistryHost, host)
|
||||
return true
|
||||
},
|
||||
}).(*authProvider)
|
||||
|
||||
res, err = p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: dockerHubRegistryHost})
|
||||
res, err = p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: DockerHubRegistryHost})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hunter2", res.Token)
|
||||
|
||||
cfg.AuthConfigs[dockerHubConfigfileKey] = types.AuthConfig{Username: "user", RegistryToken: "hunter3"}
|
||||
res, err = p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: dockerHubRegistryHost})
|
||||
cfg.AuthConfigs[DockerHubConfigfileKey] = types.AuthConfig{Username: "user", RegistryToken: "hunter3"}
|
||||
res, err = p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: DockerHubRegistryHost})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify that we re-fetched the token after it expired.
|
||||
|
||||
Reference in new issue
Block a user