Merge pull request #4190 from thaJeztah/command_auth_cleanups

cli/command: some cleanups / refactoring, and fixes related to auth
This commit is contained in:
Sebastiaan van Stijn 2023-04-13 14:23:26 +02:00 committed by GitHub
commit c25115e968
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 67 additions and 78 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -19,7 +20,6 @@ import (
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
apiclient "github.com/docker/docker/client" apiclient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -112,41 +112,27 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, options *createOptio
return nil return nil
} }
func pullImage(ctx context.Context, dockerCli command.Cli, image string, platform string, out io.Writer) error { // FIXME(thaJeztah): this is the only code-path that uses APIClient.ImageCreate. Rewrite this to use the regular "pull" code (or vice-versa).
ref, err := reference.ParseNormalizedNamed(image) func pullImage(ctx context.Context, dockerCli command.Cli, image string, opts *createOptions) error {
encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
if err != nil { if err != nil {
return err return err
} }
// Resolve the Repository name from fqn to RepositoryInfo responseBody, err := dockerCli.Client().ImageCreate(ctx, image, types.ImageCreateOptions{
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImageCreateOptions{
RegistryAuth: encodedAuth, RegistryAuth: encodedAuth,
Platform: platform, Platform: opts.platform,
} })
responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options)
if err != nil { if err != nil {
return err return err
} }
defer responseBody.Close() defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream( out := dockerCli.Err()
responseBody, if opts.quiet {
out, out = io.Discard
dockerCli.Out().FD(), }
dockerCli.Out().IsTerminal(), return jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(out), nil)
nil)
} }
type cidFile struct { type cidFile struct {
@ -236,11 +222,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
} }
pullAndTagImage := func() error { pullAndTagImage := func() error {
pullOut := dockerCli.Err() if err := pullImage(ctx, dockerCli, config.Image, opts); err != nil {
if opts.quiet {
pullOut = io.Discard
}
if err := pullImage(ctx, dockerCli, config.Image, opts.platform, pullOut); err != nil {
return err return err
} }
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil { if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/streams"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -76,7 +77,7 @@ func RunPush(dockerCli command.Cli, opts pushOptions) error {
// Resolve the Auth config relevant for this server // Resolve the Auth config relevant for this server
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
encodedAuth, err := command.EncodeAuthToBase64(authConfig) encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
if err != nil { if err != nil {
return err return err
} }

View File

@ -262,20 +262,17 @@ func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth)
// imagePullPrivileged pulls the image and displays it to the output // imagePullPrivileged pulls the image and displays it to the output
func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, opts PullOptions) error { func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, opts PullOptions) error {
ref := reference.FamiliarString(imgRefAndAuth.Reference()) encodedAuth, err := registrytypes.EncodeAuthConfig(*imgRefAndAuth.AuthConfig())
encodedAuth, err := command.EncodeAuthToBase64(*imgRefAndAuth.AuthConfig())
if err != nil { if err != nil {
return err return err
} }
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull") requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
options := types.ImagePullOptions{ responseBody, err := cli.Client().ImagePull(ctx, reference.FamiliarString(imgRefAndAuth.Reference()), types.ImagePullOptions{
RegistryAuth: encodedAuth, RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege, PrivilegeFunc: requestPrivilege,
All: opts.all, All: opts.all,
Platform: opts.platform, Platform: opts.platform,
} })
responseBody, err := cli.Client().ImagePull(ctx, ref, options)
if err != nil { if err != nil {
return err return err
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/image"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -86,8 +87,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti
} }
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil { if err != nil {
return types.PluginInstallOptions{}, err return types.PluginInstallOptions{}, err
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/image"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -55,8 +56,7 @@ func runPush(dockerCli command.Cli, opts pushOptions) error {
return err return err
} }
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil { if err != nil {
return err return err
} }

View File

@ -3,8 +3,6 @@ package command
import ( import (
"bufio" "bufio"
"context" "context"
"encoding/base64"
"encoding/json"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -21,13 +19,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload // EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload.
func EncodeAuthToBase64(authConfig registrytypes.AuthConfig) (string, error) { func EncodeAuthToBase64(authConfig registrytypes.AuthConfig) (string, error) {
buf, err := json.Marshal(authConfig) return registrytypes.EncodeAuthConfig(authConfig)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
} }
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
@ -45,7 +39,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
if err != nil { if err != nil {
return "", err return "", err
} }
return EncodeAuthToBase64(authConfig) return registrytypes.EncodeAuthConfig(authConfig)
} }
} }
@ -89,7 +83,14 @@ func GetDefaultAuthConfig(cli Cli, checkCredStore bool, serverAddress string, is
// ConfigureAuth handles prompting of user's username and password if needed // ConfigureAuth handles prompting of user's username and password if needed
func ConfigureAuth(cli Cli, flUser, flPassword string, authconfig *registrytypes.AuthConfig, isDefaultRegistry bool) error { func ConfigureAuth(cli Cli, flUser, flPassword string, authconfig *registrytypes.AuthConfig, isDefaultRegistry bool) error {
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 // On Windows, force the use of the regular OS stdin stream.
//
// See:
// - https://github.com/moby/moby/issues/14336
// - https://github.com/moby/moby/issues/14210
// - https://github.com/moby/moby/pull/17738
//
// TODO(thaJeztah): we need to confirm if this special handling is still needed, as we may not be doing this in other places.
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
cli.SetIn(streams.NewIn(os.Stdin)) cli.SetIn(streams.NewIn(os.Stdin))
} }
@ -113,8 +114,11 @@ func ConfigureAuth(cli Cli, flUser, flPassword string, authconfig *registrytypes
fmt.Fprintln(cli.Out(), "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.") fmt.Fprintln(cli.Out(), "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.")
} }
promptWithDefault(cli.Out(), "Username", authconfig.Username) promptWithDefault(cli.Out(), "Username", authconfig.Username)
flUser = readInput(cli.In(), cli.Out()) var err error
flUser = strings.TrimSpace(flUser) flUser, err = readInput(cli.In())
if err != nil {
return err
}
if flUser == "" { if flUser == "" {
flUser = authconfig.Username flUser = authconfig.Username
} }
@ -128,12 +132,15 @@ func ConfigureAuth(cli Cli, flUser, flPassword string, authconfig *registrytypes
return err return err
} }
fmt.Fprintf(cli.Out(), "Password: ") fmt.Fprintf(cli.Out(), "Password: ")
term.DisableEcho(cli.In().FD(), oldState) _ = term.DisableEcho(cli.In().FD(), oldState)
defer func() {
flPassword = readInput(cli.In(), cli.Out()) _ = term.RestoreTerminal(cli.In().FD(), oldState)
}()
flPassword, err = readInput(cli.In())
if err != nil {
return err
}
fmt.Fprint(cli.Out(), "\n") fmt.Fprint(cli.Out(), "\n")
term.RestoreTerminal(cli.In().FD(), oldState)
if flPassword == "" { if flPassword == "" {
return errors.Errorf("Error: Password Required") return errors.Errorf("Error: Password Required")
} }
@ -145,14 +152,15 @@ func ConfigureAuth(cli Cli, flUser, flPassword string, authconfig *registrytypes
return nil return nil
} }
func readInput(in io.Reader, out io.Writer) string { // readInput reads, and returns user input from in. It tries to return a
reader := bufio.NewReader(in) // single line, not including the end-of-line bytes, and trims leading
line, _, err := reader.ReadLine() // and trailing whitespace.
func readInput(in io.Reader) (string, error) {
line, _, err := bufio.NewReader(in).ReadLine()
if err != nil { if err != nil {
fmt.Fprintln(out, err.Error()) return "", errors.Wrap(err, "error while reading input")
os.Exit(1)
} }
return string(line) return strings.TrimSpace(string(line)), nil
} }
func promptWithDefault(out io.Writer, prompt string, configDefault string) { func promptWithDefault(out io.Writer, prompt string, configDefault string) {
@ -163,14 +171,19 @@ func promptWithDefault(out io.Writer, prompt string, configDefault string) {
} }
} }
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete
// image. The auth configuration is serialized as a base64url encoded RFC4648,
// section 5) JSON string for sending through the X-Registry-Auth header.
//
// For details on base64url encoding, see:
// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
func RetrieveAuthTokenFromImage(ctx context.Context, cli Cli, image string) (string, error) { func RetrieveAuthTokenFromImage(ctx context.Context, cli Cli, image string) (string, error) {
// Retrieve encoded auth token from the image reference // Retrieve encoded auth token from the image reference
authConfig, err := resolveAuthConfigFromImage(ctx, cli, image) authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
if err != nil { if err != nil {
return "", err return "", err
} }
encodedAuth, err := EncodeAuthToBase64(authConfig) encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -54,25 +55,19 @@ func runSearch(dockerCli command.Cli, options searchOptions) error {
} }
ctx := context.Background() ctx := context.Background()
authConfig := command.ResolveAuthConfig(ctx, dockerCli, indexInfo) authConfig := command.ResolveAuthConfig(ctx, dockerCli, indexInfo)
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, indexInfo, "search") encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil { if err != nil {
return err return err
} }
searchOptions := types.ImageSearchOptions{ requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, indexInfo, "search")
results, err := dockerCli.Client().ImageSearch(ctx, options.term, types.ImageSearchOptions{
RegistryAuth: encodedAuth, RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege, PrivilegeFunc: requestPrivilege,
Filters: options.filter.Value(), Filters: options.filter.Value(),
Limit: options.limit, Limit: options.limit,
} })
clnt := dockerCli.Client()
results, err := clnt.ImageSearch(ctx, options.term, searchOptions)
if err != nil { if err != nil {
return err return err
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/theupdateframework/notary/client" "github.com/theupdateframework/notary/client"
@ -93,7 +94,7 @@ func runSignImage(cli command.Cli, options signOptions) error {
fmt.Fprintf(cli.Err(), "Signing and pushing trust data for local image %s, may overwrite remote trust data\n", imageName) fmt.Fprintf(cli.Err(), "Signing and pushing trust data for local image %s, may overwrite remote trust data\n", imageName)
authConfig := command.ResolveAuthConfig(ctx, cli, imgRefAndAuth.RepoInfo().Index) authConfig := command.ResolveAuthConfig(ctx, cli, imgRefAndAuth.RepoInfo().Index)
encodedAuth, err := command.EncodeAuthToBase64(authConfig) encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
if err != nil { if err != nil {
return err return err
} }