Skip to content

Commit 70746c0

Browse files
committed
Use a privilegeFunc fall-back pattern on push/pull
Signed-off-by: Alano Terblanche <[email protected]>
1 parent 578ccf6 commit 70746c0

File tree

12 files changed

+325
-67
lines changed

12 files changed

+325
-67
lines changed

cli/command/image/push.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import (
1414
"github.com/docker/cli/cli"
1515
"github.com/docker/cli/cli/command"
1616
"github.com/docker/cli/cli/command/completion"
17+
"github.com/docker/cli/cli/config/configfile"
1718
"github.com/docker/cli/cli/streams"
1819
"github.com/docker/cli/internal/jsonstream"
1920
"github.com/docker/cli/internal/tui"
2021
"github.com/docker/docker/api/types/auxprogress"
2122
"github.com/docker/docker/api/types/image"
2223
registrytypes "github.com/docker/docker/api/types/registry"
24+
"github.com/docker/docker/client"
2325
"github.com/docker/docker/registry"
2426
"github.com/morikuni/aec"
2527
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -111,18 +113,26 @@ To push the complete multi-platform image, remove the --platform flag.
111113

112114
// Resolve the Auth config relevant for this server
113115
authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index)
114-
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
116+
117+
hostname := command.GetRegistryHostname(repoInfo.Index)
118+
119+
privilegeFuncs := configfile.RegistryAuthPrivilegeFunc(dockerCli.ConfigFile(), hostname)
120+
if dockerCli.In().IsTerminal() {
121+
privilegeFuncs = client.ChainPrivilegeFuncs(privilegeFuncs, command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push"))
122+
}
123+
124+
encoded, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
125+
Username: "test",
126+
Password: "test",
127+
})
115128
if err != nil {
116129
return err
117130
}
118-
var requestPrivilege registrytypes.RequestAuthConfig
119-
if dockerCli.In().IsTerminal() {
120-
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
121-
}
131+
122132
options := image.PushOptions{
123133
All: opts.all,
124-
RegistryAuth: encodedAuth,
125-
PrivilegeFunc: requestPrivilege,
134+
RegistryAuth: encoded, // we already pass all configs from privilegeFuncs
135+
PrivilegeFunc: privilegeFuncs,
126136
Platform: platform,
127137
}
128138

cli/command/image/trust.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import (
88

99
"github.com/distribution/reference"
1010
"github.com/docker/cli/cli/command"
11+
"github.com/docker/cli/cli/config/configfile"
1112
"github.com/docker/cli/cli/streams"
1213
"github.com/docker/cli/cli/trust"
1314
"github.com/docker/cli/internal/jsonstream"
1415
"github.com/docker/docker/api/types/image"
1516
registrytypes "github.com/docker/docker/api/types/registry"
17+
"github.com/docker/docker/client"
1618
"github.com/docker/docker/registry"
1719
"github.com/opencontainers/go-digest"
1820
"github.com/pkg/errors"
1921
"github.com/sirupsen/logrus"
20-
"github.com/theupdateframework/notary/client"
22+
notaryclient "github.com/theupdateframework/notary/client"
2123
"github.com/theupdateframework/notary/tuf/data"
2224
)
2325

@@ -29,11 +31,11 @@ type target struct {
2931

3032
// notaryClientProvider is used in tests to provide a dummy notary client.
3133
type notaryClientProvider interface {
32-
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error)
34+
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
3335
}
3436

3537
// newNotaryClient provides a Notary Repository to interact with signed metadata for an image.
36-
func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (client.Repository, error) {
38+
func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (notaryclient.Repository, error) {
3739
if ncp, ok := cli.(notaryClientProvider); ok {
3840
// notaryClientProvider is used in tests to provide a dummy notary client.
3941
return ncp.NotaryClient(imgRefAndAuth, []string{"pull"})
@@ -145,17 +147,24 @@ func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth)
145147

146148
// imagePullPrivileged pulls the image and displays it to the output
147149
func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, opts pullOptions) error {
148-
encodedAuth, err := registrytypes.EncodeAuthConfig(*imgRefAndAuth.AuthConfig())
150+
hostname := command.GetRegistryHostname(imgRefAndAuth.RepoInfo().Index)
151+
152+
privilegeFuncs := configfile.RegistryAuthPrivilegeFunc(cli.ConfigFile(), hostname)
153+
if cli.In().IsTerminal() {
154+
privilegeFuncs = client.ChainPrivilegeFuncs(privilegeFuncs, command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull"))
155+
}
156+
157+
encoded, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
158+
Username: "test",
159+
Password: "test",
160+
})
149161
if err != nil {
150162
return err
151163
}
152-
var requestPrivilege registrytypes.RequestAuthConfig
153-
if cli.In().IsTerminal() {
154-
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
155-
}
164+
156165
responseBody, err := cli.Client().ImagePull(ctx, reference.FamiliarString(imgRefAndAuth.Reference()), image.PullOptions{
157-
RegistryAuth: encodedAuth,
158-
PrivilegeFunc: requestPrivilege,
166+
RegistryAuth: encoded, // we already have credentials resolved by privilegeFuncs
167+
PrivilegeFunc: privilegeFuncs,
159168
All: opts.all,
160169
Platform: opts.platform,
161170
})
@@ -190,7 +199,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
190199
// Only list tags in the top level targets role or the releases delegation role - ignore
191200
// all other delegation roles
192201
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
193-
return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), client.ErrNoSuchTarget(ref.Tag()))
202+
return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), notaryclient.ErrNoSuchTarget(ref.Tag()))
194203
}
195204
r, err := convertTarget(t.Target)
196205
if err != nil {
@@ -199,7 +208,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
199208
return reference.WithDigest(reference.TrimNamed(ref), r.digest)
200209
}
201210

202-
func convertTarget(t client.Target) (target, error) {
211+
func convertTarget(t notaryclient.Target) (target, error) {
203212
h, ok := t.Hashes["sha256"]
204213
if !ok {
205214
return target{}, errors.New("no valid hash, expecting sha256")

cli/command/registry.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
6060
}
6161
}
6262

63+
func GetRegistryHostname(index *registrytypes.IndexInfo) string {
64+
configKey := index.Name
65+
if index.Official {
66+
configKey = authConfigKey
67+
}
68+
return configKey
69+
}
70+
6371
// ResolveAuthConfig returns auth-config for the given registry from the
6472
// credential-store. It returns an empty AuthConfig if no credentials were
6573
// found.

cli/config/configfile/file.go

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package configfile
22

33
import (
4+
"context"
45
"encoding/base64"
56
"encoding/json"
67
"fmt"
@@ -9,9 +10,12 @@ import (
910
"path/filepath"
1011
"strings"
1112

13+
cerrdefs "github.com/containerd/errdefs"
1214
"github.com/docker/cli/cli/config/credentials"
1315
"github.com/docker/cli/cli/config/memorystore"
1416
"github.com/docker/cli/cli/config/types"
17+
"github.com/docker/docker/api/types/registry"
18+
c "github.com/docker/docker/client"
1519
"github.com/pkg/errors"
1620
"github.com/sirupsen/logrus"
1721
)
@@ -301,20 +305,9 @@ func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) crede
301305
return store
302306
}
303307

304-
authConfig, err := parseEnvConfig(envConfig)
305-
if err != nil {
306-
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
307-
return store
308-
}
309-
310-
// use DOCKER_AUTH_CONFIG if set
311-
// it uses the native or file store as a fallback to fetch and store credentials
312-
envStore, err := memorystore.New(
313-
memorystore.WithAuthConfig(authConfig),
314-
memorystore.WithFallbackStore(store),
315-
)
308+
envStore, err := getEnvStore(envConfig, store)
316309
if err != nil {
317-
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
310+
_, _ = fmt.Fprintln(os.Stderr, err)
318311
return store
319312
}
320313

@@ -360,6 +353,83 @@ func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.Auth
360353
return configFile.GetCredentialsStore(registryHostname).Get(registryHostname)
361354
}
362355

356+
func getEnvStore(envConfig string, fallbackStore credentials.Store) (credentials.Store, error) {
357+
authConfig, err := parseEnvConfig(envConfig)
358+
if err != nil {
359+
return nil, fmt.Errorf("failed to create credential store from %s: %w", DockerEnvConfigKey, err)
360+
}
361+
362+
// use DOCKER_AUTH_CONFIG if set
363+
// it uses the native or file store as a fallback to fetch and store credentials
364+
envStore, err := memorystore.New(
365+
memorystore.WithAuthConfig(authConfig),
366+
memorystore.WithFallbackStore(fallbackStore),
367+
)
368+
if err != nil {
369+
return nil, fmt.Errorf("failed to create credential store from %s: %w", DockerEnvConfigKey, err)
370+
}
371+
return envStore, nil
372+
}
373+
374+
func getEncodedAuth(store credentials.Store, registryHostname string) (string, error) {
375+
c, err := store.Get(registryHostname)
376+
if err != nil {
377+
return "", err
378+
}
379+
return registry.EncodeAuthConfig(registry.AuthConfig(c))
380+
}
381+
382+
func envRequestAuthConfig(registryHostname string) registry.RequestAuthConfig {
383+
return func(ctx context.Context) (string, error) {
384+
fmt.Fprintln(os.Stdout, "Trying env")
385+
386+
envConfig := os.Getenv(DockerEnvConfigKey)
387+
if envConfig == "" {
388+
return "", cerrdefs.ErrNotFound.WithMessage(DockerEnvConfigKey + " not defined")
389+
}
390+
391+
envStore, err := getEnvStore(envConfig, nil)
392+
if err != nil {
393+
return "", err
394+
}
395+
return getEncodedAuth(envStore, registryHostname)
396+
}
397+
}
398+
399+
func nativeRequestAuthConfig(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig {
400+
return func(ctx context.Context) (string, error) {
401+
fmt.Fprintln(os.Stdout, "Trying native")
402+
helper := getConfiguredCredentialStore(configFile, registryHostname)
403+
if helper == "" {
404+
return "", cerrdefs.ErrNotFound.WithMessage("native credential helper not supported")
405+
}
406+
store := newNativeStore(configFile, helper)
407+
return getEncodedAuth(store, registryHostname)
408+
}
409+
}
410+
411+
func fileRequestAuthConfig(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig {
412+
return func(ctx context.Context) (string, error) {
413+
fmt.Fprintln(os.Stdout, "Trying file")
414+
store := credentials.NewFileStore(configFile)
415+
return getEncodedAuth(store, registryHostname)
416+
}
417+
}
418+
419+
// RegistryAuthPrivilegeFunc returns an ordered slice of [registry.RequestAuthConfig]
420+
//
421+
// The order of precedence for resolving credentials are as follows:
422+
// - Credentials stored in the environment through DOCKER_AUTH_CONFIG
423+
// - Native credentials through the credential-helper
424+
// - Filestore credentials stored in `~/.docker/config.json`
425+
func RegistryAuthPrivilegeFunc(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig {
426+
return c.ChainPrivilegeFuncs(
427+
envRequestAuthConfig(registryHostname),
428+
nativeRequestAuthConfig(configFile, registryHostname),
429+
fileRequestAuthConfig(configFile, registryHostname),
430+
)
431+
}
432+
363433
// getConfiguredCredentialStore returns the credential helper configured for the
364434
// given registry, the default credsStore, or the empty string if neither are
365435
// configured.

vendor/github.com/docker/docker/api/swagger.yaml

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/client/auth.go

Lines changed: 85 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)