diff options
author | Valentin Rothberg <rothberg@redhat.com> | 2020-04-21 12:51:22 +0200 |
---|---|---|
committer | Valentin Rothberg <rothberg@redhat.com> | 2020-04-21 15:10:31 +0200 |
commit | 0138ecfba047d95466b4dd66295d7800412ff2b6 (patch) | |
tree | 45e2961089a4261137ff0e918fb029c110cb6148 | |
parent | 47d99fb6253238e7603fc96d5b9bbb14f1e8c948 (diff) | |
download | podman-0138ecfba047d95466b4dd66295d7800412ff2b6.tar.gz podman-0138ecfba047d95466b4dd66295d7800412ff2b6.tar.bz2 podman-0138ecfba047d95466b4dd66295d7800412ff2b6.zip |
v2: implement log{in,out}
Implement podman login and logout. Smoke tests were successful but the
system tests are currently failing as we seem unable to run a registry
at the moment.
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
-rw-r--r-- | cmd/podman/login.go | 68 | ||||
-rw-r--r-- | cmd/podman/logout.go | 57 | ||||
-rw-r--r-- | vendor/github.com/containers/common/pkg/auth/auth.go | 182 | ||||
-rw-r--r-- | vendor/github.com/containers/common/pkg/auth/cli.go | 47 | ||||
-rw-r--r-- | vendor/modules.txt | 1 |
5 files changed, 355 insertions, 0 deletions
diff --git a/cmd/podman/login.go b/cmd/podman/login.go new file mode 100644 index 000000000..1843a764d --- /dev/null +++ b/cmd/podman/login.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "os" + + "github.com/containers/common/pkg/auth" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +type loginOptionsWrapper struct { + auth.LoginOptions + tlsVerify bool +} + +var ( + loginOptions = loginOptionsWrapper{} + loginCommand = &cobra.Command{ + Use: "login [flags] REGISTRY", + Short: "Login to a container registry", + Long: "Login to a container registry on a specified server.", + RunE: login, + Args: cobra.ExactArgs(1), + Example: `podman login quay.io + podman login --username ... --password ... quay.io + podman login --authfile dir/auth.json quay.io`, + } +) + +func init() { + // Note that the local and the remote client behave the same: both + // store credentials locally while the remote client will pass them + // over the wire to the endpoint. + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: loginCommand, + }) + flags := loginCommand.Flags() + + // Flags from the auth package. + flags.AddFlagSet(auth.GetLoginFlags(&loginOptions.LoginOptions)) + + // Podman flags. + flags.BoolVarP(&loginOptions.tlsVerify, "tls-verify", "", false, "Require HTTPS and verify certificates when contacting registries") + flags.BoolVarP(&loginOptions.GetLoginSet, "get-login", "", false, "Return the current login user for the registry") + loginOptions.Stdin = os.Stdin + loginOptions.Stdout = os.Stdout +} + +// Implementation of podman-login. +func login(cmd *cobra.Command, args []string) error { + var skipTLS types.OptionalBool + + if cmd.Flags().Changed("tls-verify") { + skipTLS = types.NewOptionalBool(!loginOptions.tlsVerify) + } + + sysCtx := types.SystemContext{ + AuthFilePath: loginOptions.AuthFile, + DockerCertPath: loginOptions.CertDir, + DockerInsecureSkipTLSVerify: skipTLS, + } + + return auth.Login(context.Background(), &sysCtx, &loginOptions.LoginOptions, args[0]) +} diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go new file mode 100644 index 000000000..77bdc92b4 --- /dev/null +++ b/cmd/podman/logout.go @@ -0,0 +1,57 @@ +package main + +import ( + "os" + + "github.com/containers/common/pkg/auth" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + logoutOptions = auth.LogoutOptions{} + logoutCommand = &cobra.Command{ + Use: "logout [flags] REGISTRY", + Short: "Logout of a container registry", + Long: "Remove the cached username and password for the registry.", + RunE: logout, + Args: cobra.MaximumNArgs(1), + Example: `podman logout quay.io + podman logout --authfile dir/auth.json quay.io + podman logout --all`, + } +) + +func init() { + // Note that the local and the remote client behave the same: both + // store credentials locally while the remote client will pass them + // over the wire to the endpoint. + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: logoutCommand, + }) + flags := logoutCommand.Flags() + + // Flags from the auth package. + flags.AddFlagSet(auth.GetLogoutFlags(&logoutOptions)) + logoutOptions.Stdin = os.Stdin + logoutOptions.Stdout = os.Stdout +} + +// Implementation of podman-logout. +func logout(cmd *cobra.Command, args []string) error { + sysCtx := types.SystemContext{AuthFilePath: logoutOptions.AuthFile} + + registry := "" + if len(args) > 0 { + if logoutOptions.All { + return errors.New("--all takes no arguments") + } + registry = args[0] + } + + return auth.Logout(&sysCtx, &logoutOptions, registry) +} diff --git a/vendor/github.com/containers/common/pkg/auth/auth.go b/vendor/github.com/containers/common/pkg/auth/auth.go new file mode 100644 index 000000000..769e5a9fa --- /dev/null +++ b/vendor/github.com/containers/common/pkg/auth/auth.go @@ -0,0 +1,182 @@ +package auth + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" + + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/pkg/docker/config" + "github.com/containers/image/v5/types" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh/terminal" +) + +// GetDefaultAuthFile returns env value REGISTRY_AUTH_FILE as default --authfile path +// used in multiple --authfile flag definitions +func GetDefaultAuthFile() string { + return os.Getenv("REGISTRY_AUTH_FILE") +} + +// CheckAuthFile validates filepath given by --authfile +// used by command has --authfile flag +func CheckAuthFile(authfile string) error { + if authfile == "" { + return nil + } + if _, err := os.Stat(authfile); err != nil { + return errors.Wrapf(err, "error checking authfile path %s", authfile) + } + return nil +} + +// Login login to the server with creds from Stdin or CLI +func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginOptions, registry string) error { + server := getRegistryName(registry) + authConfig, err := config.GetCredentials(systemContext, server) + if err != nil { + return errors.Wrapf(err, "error reading auth file") + } + if opts.GetLoginSet { + if authConfig.Username == "" { + return errors.Errorf("not logged into %s", server) + } + fmt.Fprintf(opts.Stdout, "%s\n", authConfig.Username) + return nil + } + if authConfig.IdentityToken != "" { + return errors.Errorf("currently logged in, auth file contains an Identity token") + } + + password := opts.Password + if opts.StdinPassword { + var stdinPasswordStrBuilder strings.Builder + if opts.Password != "" { + return errors.Errorf("Can't specify both --password-stdin and --password") + } + if opts.Username == "" { + return errors.Errorf("Must provide --username with --password-stdin") + } + scanner := bufio.NewScanner(opts.Stdin) + for scanner.Scan() { + fmt.Fprint(&stdinPasswordStrBuilder, scanner.Text()) + } + password = stdinPasswordStrBuilder.String() + } + + // If no username and no password is specified, try to use existing ones. + if opts.Username == "" && password == "" && authConfig.Username != "" && authConfig.Password != "" { + fmt.Println("Authenticating with existing credentials...") + if err := docker.CheckAuth(ctx, systemContext, authConfig.Username, authConfig.Password, server); err == nil { + fmt.Fprintln(opts.Stdout, "Existing credentials are valid. Already logged in to", server) + return nil + } + fmt.Fprintln(opts.Stdout, "Existing credentials are invalid, please enter valid username and password") + } + + username, password, err := getUserAndPass(opts, password, authConfig.Username) + if err != nil { + return errors.Wrapf(err, "error getting username and password") + } + + if err = docker.CheckAuth(ctx, systemContext, username, password, server); err == nil { + // Write the new credentials to the authfile + if err = config.SetAuthentication(systemContext, server, username, password); err != nil { + return err + } + } + if err == nil { + fmt.Fprintln(opts.Stdout, "Login Succeeded!") + return nil + } + if unauthorized, ok := err.(docker.ErrUnauthorizedForCredentials); ok { + logrus.Debugf("error logging into %q: %v", server, unauthorized) + return errors.Errorf("error logging into %q: invalid username/password", server) + } + return errors.Wrapf(err, "error authenticating creds for %q", server) +} + +// getRegistryName scrubs and parses the input to get the server name +func getRegistryName(server string) string { + // removes 'http://' or 'https://' from the front of the + // server/registry string if either is there. This will be mostly used + // for user input from 'Buildah login' and 'Buildah logout'. + server = strings.TrimPrefix(strings.TrimPrefix(server, "https://"), "http://") + // gets the registry from the input. If the input is of the form + // quay.io/myuser/myimage, it will parse it and just return quay.io + split := strings.Split(server, "/") + if len(split) > 1 { + return split[0] + } + return split[0] +} + +// getUserAndPass gets the username and password from STDIN if not given +// using the -u and -p flags. If the username prompt is left empty, the +// displayed userFromAuthFile will be used instead. +func getUserAndPass(opts *LoginOptions, password, userFromAuthFile string) (string, string, error) { + var err error + reader := bufio.NewReader(opts.Stdin) + username := opts.Username + if username == "" { + if userFromAuthFile != "" { + fmt.Fprintf(opts.Stdout, "Username (%s): ", userFromAuthFile) + } else { + fmt.Fprint(opts.Stdout, "Username: ") + } + username, err = reader.ReadString('\n') + if err != nil { + return "", "", errors.Wrapf(err, "error reading username") + } + // If the user just hit enter, use the displayed user from the + // the authentication file. This allows to do a lazy + // `$ buildah login -p $NEW_PASSWORD` without specifying the + // user. + if strings.TrimSpace(username) == "" { + username = userFromAuthFile + } + } + if password == "" { + fmt.Fprint(opts.Stdout, "Password: ") + pass, err := terminal.ReadPassword(0) + if err != nil { + return "", "", errors.Wrapf(err, "error reading password") + } + password = string(pass) + fmt.Fprintln(opts.Stdout) + } + return strings.TrimSpace(username), password, err +} + +// Logout removes the authentication of server from authfile +// removes all authtication if specifies all in the options +func Logout(systemContext *types.SystemContext, opts *LogoutOptions, server string) error { + if server != "" { + server = getRegistryName(server) + } + if err := CheckAuthFile(opts.AuthFile); err != nil { + return err + } + + if opts.All { + if err := config.RemoveAllAuthentication(systemContext); err != nil { + return err + } + fmt.Fprintln(opts.Stdout, "Removed login credentials for all registries") + return nil + } + + err := config.RemoveAuthentication(systemContext, server) + switch err { + case nil: + fmt.Fprintf(opts.Stdout, "Removed login credentials for %s\n", server) + return nil + case config.ErrNotLoggedIn: + return errors.Errorf("Not logged into %s\n", server) + default: + return errors.Wrapf(err, "error logging out of %q", server) + } +} diff --git a/vendor/github.com/containers/common/pkg/auth/cli.go b/vendor/github.com/containers/common/pkg/auth/cli.go new file mode 100644 index 000000000..dffd06718 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/auth/cli.go @@ -0,0 +1,47 @@ +package auth + +import ( + "io" + + "github.com/spf13/pflag" +) + +// LoginOptions represents common flags in login +// caller should define bool or optionalBool fields for flags --get-login and --tls-verify +type LoginOptions struct { + AuthFile string + CertDir string + GetLoginSet bool + Password string + Username string + StdinPassword bool + Stdin io.Reader + Stdout io.Writer +} + +// LogoutOptions represents the results for flags in logout +type LogoutOptions struct { + AuthFile string + All bool + Stdin io.Reader + Stdout io.Writer +} + +// GetLoginFlags defines and returns login flags for containers tools +func GetLoginFlags(flags *LoginOptions) *pflag.FlagSet { + fs := pflag.FlagSet{} + fs.StringVar(&flags.AuthFile, "authfile", GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + fs.StringVar(&flags.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry") + fs.StringVarP(&flags.Password, "password", "p", "", "Password for registry") + fs.StringVarP(&flags.Username, "username", "u", "", "Username for registry") + fs.BoolVar(&flags.StdinPassword, "password-stdin", false, "Take the password from stdin") + return &fs +} + +// GetLogoutFlags defines and returns logout flags for containers tools +func GetLogoutFlags(flags *LogoutOptions) *pflag.FlagSet { + fs := pflag.FlagSet{} + fs.StringVar(&flags.AuthFile, "authfile", GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + fs.BoolVarP(&flags.All, "all", "a", false, "Remove the cached credentials for all registries in the auth file") + return &fs +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3b45161da..fc2179675 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -84,6 +84,7 @@ github.com/containers/buildah/pkg/umask github.com/containers/buildah/util # github.com/containers/common v0.9.1 github.com/containers/common/pkg/apparmor +github.com/containers/common/pkg/auth github.com/containers/common/pkg/capabilities github.com/containers/common/pkg/cgroupv2 github.com/containers/common/pkg/config |