Skip to content

Use a privilegeFunc fall-back pattern on push/pull #6172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions cli/command/image/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/internal/tui"
Expand Down Expand Up @@ -109,14 +110,21 @@ To push the complete multi-platform image, remove the --platform flag.

// Resolve the Auth config relevant for this server
authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index)
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)

hostname := command.GetRegistryHostname(repoInfo.Index)

encoded, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
Username: "test",
Password: "test",
})
if err != nil {
return err
}

options := image.PushOptions{
All: opts.all,
RegistryAuth: encodedAuth,
PrivilegeFunc: nil,
RegistryAuth: encoded, // we already pass all configs from privilegeFuncs
PrivilegeFunc: configfile.RegistryAuthPrivilegeFunc(dockerCli.ConfigFile(), hostname),
Platform: platform,
}

Expand Down
23 changes: 15 additions & 8 deletions cli/command/image/trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/distribution/reference"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/jsonstream"
Expand All @@ -17,7 +18,7 @@ import (
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary/client"
notaryclient "github.com/theupdateframework/notary/client"
"github.com/theupdateframework/notary/tuf/data"
)

Expand All @@ -29,11 +30,11 @@ type target struct {

// notaryClientProvider is used in tests to provide a dummy notary client.
type notaryClientProvider interface {
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error)
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
}

// newNotaryClient provides a Notary Repository to interact with signed metadata for an image.
func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (client.Repository, error) {
func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (notaryclient.Repository, error) {
if ncp, ok := cli.(notaryClientProvider); ok {
// notaryClientProvider is used in tests to provide a dummy notary client.
return ncp.NotaryClient(imgRefAndAuth, []string{"pull"})
Expand Down Expand Up @@ -145,13 +146,19 @@ func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth)

// imagePullPrivileged pulls the image and displays it to the output
func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, opts pullOptions) error {
encodedAuth, err := registrytypes.EncodeAuthConfig(*imgRefAndAuth.AuthConfig())
hostname := command.GetRegistryHostname(imgRefAndAuth.RepoInfo().Index)

encoded, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
Username: "test",
Password: "test",
})
if err != nil {
return err
}

responseBody, err := cli.Client().ImagePull(ctx, reference.FamiliarString(imgRefAndAuth.Reference()), image.PullOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: nil,
RegistryAuth: encoded, // we already have credentials resolved by privilegeFuncs
PrivilegeFunc: configfile.RegistryAuthPrivilegeFunc(cli.ConfigFile(), hostname),
All: opts.all,
Platform: opts.platform,
})
Expand Down Expand Up @@ -186,7 +193,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
// Only list tags in the top level targets role or the releases delegation role - ignore
// all other delegation roles
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), client.ErrNoSuchTarget(ref.Tag()))
return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), notaryclient.ErrNoSuchTarget(ref.Tag()))
}
r, err := convertTarget(t.Target)
if err != nil {
Expand All @@ -195,7 +202,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
return reference.WithDigest(reference.TrimNamed(ref), r.digest)
}

func convertTarget(t client.Target) (target, error) {
func convertTarget(t notaryclient.Target) (target, error) {
h, ok := t.Hashes["sha256"]
if !ok {
return target{}, errors.New("no valid hash, expecting sha256")
Expand Down
8 changes: 8 additions & 0 deletions cli/command/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
}
}

func GetRegistryHostname(index *registrytypes.IndexInfo) string {
configKey := index.Name
if index.Official {
configKey = authConfigKey
}
return configKey
}

// ResolveAuthConfig returns auth-config for the given registry from the
// credential-store. It returns an empty AuthConfig if no credentials were
// found.
Expand Down
96 changes: 83 additions & 13 deletions cli/config/configfile/file.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package configfile

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
Expand All @@ -9,9 +10,12 @@ import (
"path/filepath"
"strings"

cerrdefs "github.com/containerd/errdefs"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/cli/config/memorystore"
"github.com/docker/cli/cli/config/types"
"github.com/docker/docker/api/types/registry"
c "github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -301,20 +305,9 @@ func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) crede
return store
}

authConfig, err := parseEnvConfig(envConfig)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
return store
}

// use DOCKER_AUTH_CONFIG if set
// it uses the native or file store as a fallback to fetch and store credentials
envStore, err := memorystore.New(
memorystore.WithAuthConfig(authConfig),
memorystore.WithFallbackStore(store),
)
envStore, err := getEnvStore(envConfig, store)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
_, _ = fmt.Fprintln(os.Stderr, err)
return store
}

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

func getEnvStore(envConfig string, fallbackStore credentials.Store) (credentials.Store, error) {
authConfig, err := parseEnvConfig(envConfig)
if err != nil {
return nil, fmt.Errorf("failed to create credential store from %s: %w", DockerEnvConfigKey, err)
}

// use DOCKER_AUTH_CONFIG if set
// it uses the native or file store as a fallback to fetch and store credentials
envStore, err := memorystore.New(
memorystore.WithAuthConfig(authConfig),
memorystore.WithFallbackStore(fallbackStore),
)
if err != nil {
return nil, fmt.Errorf("failed to create credential store from %s: %w", DockerEnvConfigKey, err)
}
return envStore, nil
}

func getEncodedAuth(store credentials.Store, registryHostname string) (string, error) {
c, err := store.Get(registryHostname)
if err != nil {
return "", err
}
return registry.EncodeAuthConfig(registry.AuthConfig(c))
}

func envRequestAuthConfig(registryHostname string) registry.RequestAuthConfig {
return func(ctx context.Context) (string, error) {
fmt.Fprintln(os.Stdout, "Trying env")

envConfig := os.Getenv(DockerEnvConfigKey)
if envConfig == "" {
return "", cerrdefs.ErrNotFound.WithMessage(DockerEnvConfigKey + " not defined")
}

envStore, err := getEnvStore(envConfig, nil)
if err != nil {
return "", err
}
return getEncodedAuth(envStore, registryHostname)
}
}

func nativeRequestAuthConfig(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig {
return func(ctx context.Context) (string, error) {
fmt.Fprintln(os.Stdout, "Trying native")
helper := getConfiguredCredentialStore(configFile, registryHostname)
if helper == "" {
return "", cerrdefs.ErrNotFound.WithMessage("native credential helper not supported")
}
store := newNativeStore(configFile, helper)
return getEncodedAuth(store, registryHostname)
}
}

func fileRequestAuthConfig(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig {
return func(ctx context.Context) (string, error) {
fmt.Fprintln(os.Stdout, "Trying file")
store := credentials.NewFileStore(configFile)
return getEncodedAuth(store, registryHostname)
}
}

// RegistryAuthPrivilegeFunc returns an ordered slice of [registry.RequestAuthConfig]
//
// The order of precedence for resolving credentials are as follows:
// - Credentials stored in the environment through DOCKER_AUTH_CONFIG
// - Native credentials through the credential-helper
// - Filestore credentials stored in `~/.docker/config.json`
func RegistryAuthPrivilegeFunc(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig {
return c.ChainPrivilegeFuncs(
envRequestAuthConfig(registryHostname),
nativeRequestAuthConfig(configFile, registryHostname),
fileRequestAuthConfig(configFile, registryHostname),
)
}

// getConfiguredCredentialStore returns the credential helper configured for the
// given registry, the default credsStore, or the empty string if neither are
// configured.
Expand Down
3 changes: 2 additions & 1 deletion vendor/github.com/docker/docker/api/swagger.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions vendor/github.com/docker/docker/client/auth.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading