Skip to content

Commit

Permalink
authprovider: Add support for expiring auth cache
Browse files Browse the repository at this point in the history
This exposes a mechanism to expire cached auth configs.

Signed-off-by: Brian Goff <[email protected]>
  • Loading branch information
cpuguy83 committed Jan 27, 2025
1 parent 81d49f7 commit be0ce1a
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 20 deletions.
6 changes: 5 additions & 1 deletion cmd/buildctl/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,11 @@ func buildAction(clicontext *cli.Context) error {
if err != nil {
return err
}
attachable := []session.Attachable{authprovider.NewDockerAuthProvider(dockerConfig, tlsConfigs)}

attachable := []session.Attachable{authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
ConfigFile: dockerConfig,
TLSConfigs: tlsConfigs,
})}

if ssh := clicontext.StringSlice("ssh"); len(ssh) > 0 {
configs, err := build.ParseSSH(ssh)
Expand Down
61 changes: 47 additions & 14 deletions session/auth/authprovider/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,43 @@ const (
dockerHubRegistryHost = "registry-1.docker.io"
)

func NewDockerAuthProvider(cfg *configfile.ConfigFile, tlsConfigs map[string]*AuthTLSConfig) session.Attachable {
type DockerAuthProviderConfig struct {
// ConfigFile is the docker config file
ConfigFile *configfile.ConfigFile
// 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
// instead of using a pre-cached result.
// If nil then the cached result will always be used.
// 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
}

func NewDockerAuthProvider(cfg DockerAuthProviderConfig) session.Attachable {
if cfg.ExpireCachedAuth == nil {
cfg.ExpireCachedAuth = func(time.Time, string) bool {
return false
}
}
return &authProvider{
authConfigCache: map[string]*types.AuthConfig{},
config: cfg,
authConfigCache: map[string]authConfigCacheEntry{},
expireAc: cfg.ExpireCachedAuth,
config: cfg.ConfigFile,
seeds: &tokenSeeds{dir: config.Dir()},
loggerCache: map[string]struct{}{},
tlsConfigs: tlsConfigs,
tlsConfigs: cfg.TLSConfigs,
}
}

type authProvider struct {
authConfigCache map[string]*types.AuthConfig
authConfigCache map[string]authConfigCacheEntry
expireAc func(time.Time, string) bool
config *configfile.ConfigFile
seeds *tokenSeeds
logger progresswriter.Logger
Expand Down Expand Up @@ -247,17 +272,25 @@ func (ap *authProvider) getAuthConfig(ctx context.Context, host string) (*types.
host = dockerHubConfigfileKey
}

if _, exists := ap.authConfigCache[host]; !exists {
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
}
ap.authConfigCache[host] = &ac
entry, exists := ap.authConfigCache[host]
if exists && !ap.expireAc(entry.Created, host) {
return entry.Auth, nil
}

return ap.authConfigCache[host], 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
}

func (ap *authProvider) getAuthorityKey(ctx context.Context, host string, salt []byte) (ed25519.PrivateKey, error) {
Expand Down
39 changes: 34 additions & 5 deletions session/auth/authprovider/authprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package authprovider
import (
"context"
"testing"
"time"

"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
Expand All @@ -12,12 +13,18 @@ import (
)

func TestFetchTokenCaching(t *testing.T) {
cfg := &configfile.ConfigFile{
AuthConfigs: map[string]types.AuthConfig{
dockerHubConfigfileKey: {Username: "user", RegistryToken: "hunter2"},
},
newCfg := func() *configfile.ConfigFile {
return &configfile.ConfigFile{
AuthConfigs: map[string]types.AuthConfig{
dockerHubConfigfileKey: {Username: "user", RegistryToken: "hunter2"},
},
}
}
p := NewDockerAuthProvider(cfg, nil).(*authProvider)

cfg := newCfg()
p := NewDockerAuthProvider(DockerAuthProviderConfig{
ConfigFile: cfg,
}).(*authProvider)
res, err := p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: dockerHubRegistryHost})
require.NoError(t, err)
assert.Equal(t, "hunter2", res.Token)
Expand All @@ -28,4 +35,26 @@ func TestFetchTokenCaching(t *testing.T) {

// Verify that we cached the result instead of returning hunter3.
assert.Equal(t, "hunter2", res.Token)

// Now again but this time expire the auth.

cfg = newCfg()
p = NewDockerAuthProvider(DockerAuthProviderConfig{
ConfigFile: cfg,
ExpireCachedAuth: func(_ time.Time, host string) bool {
require.Equal(t, dockerHubConfigfileKey, host)
return true
},
}).(*authProvider)

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})
require.NoError(t, err)

// Verify that we re-fetched the token after it expired.
assert.Equal(t, "hunter3", res.Token)
}

0 comments on commit be0ce1a

Please sign in to comment.